Django:拥有多个子模型类型的父模型

2024-10-01 17:38:56 发布

您现在位置:Python中文网/ 问答频道 /正文

我为CMS创建了一组Django模型来显示一系列的Product

每个页面都包含一系列行,因此我有一个通用的

class ProductRow(models.Model):
  slug = models.SlugField(max_length=100, null=False, blank=False, unique=True, primary_key=True)
  name = models.CharField(max_length=200,null=False,blank=False,unique=True)
  active = models.BooleanField(default=True, null=False, blank=False)

对于不同类型的行,我有一系列此模型的子对象:

^{pr2}$

等等。在

在我的ProductPage中,我有一系列ProductRows:

class ProductPage(models.Model):
  slug = models.SlugField(max_length=100, null=False, blank=False, unique=True, primary_key=True)
  name = models.CharField(max_length=200, null=False, blank=False, unique=True)
  title = models.CharField(max_length=80, null=False, blank=False)
  description = models.CharField(max_length=80, null=False, blank=False)
  row_1 = models.ForeignKey(ProductRow, related_name='+', null=False, blank=False)
  row_2 = models.ForeignKey(ProductRow, related_name='+', null=True, blank=True)
  row_3 = models.ForeignKey(ProductRow, related_name='+', null=True, blank=True)
  row_4 = models.ForeignKey(ProductRow, related_name='+', null=True, blank=True)
  row_5 = models.ForeignKey(ProductRow, related_name='+', null=True, blank=True)

{{cd2>中的任何一行都是不同的。但是,当我在它们上面迭代时,比如

views.py中:

product_page_rows = [product_page.row_1,product_page.row_2,product_page.row_3,product_page.row_4,product_page.row_5]

然后在模板中:

{% for row in product_page_rows %}
  <pre>{{ row.XXXX }}</pre>
{% endfor %}

我不能将任何子字段引用为XXXX。在

我尝试向父级和子级添加“type()”方法,以尝试区分每行是哪个类:

class ProductRow(models.Model):

  ...

  @classmethod
  def type(cls):
      return "generic"

以及

class ProductTextGridRow(TourRow):

  ...

  @classmethod
  def type(cls):
      return "text-grid"

但是,如果我在模板中为.type()更改XXXX,那么它会为列表中的每个项显示{}(我在数据中定义了各种行类型),所以我想所有内容都会返回为ProductRow,而不是相应的子类型。我找不到任何方法来让这些子类型作为正确的子类型而不是父类型来访问,或者确定它们实际上是哪个子类型(我也尝试了catching AttributeError),但这没用)。在

有人能建议我如何正确地处理各种模型类型的列表,所有这些模型类型都包含一个共同的父模型,并且能够访问相应子模型类型的字段?在


Tags: name模型falsetrue类型modelspageproduct
2条回答

这通常是一个糟糕的设计,有这样的东西:

class MyModel(models.Model):
    ...
    row_1 = models.ForeignKey(...)
    row_2 = models.ForeignKey(...)
    row_3 = models.ForeignKey(...)
    row_4 = models.ForeignKey(...)
    row_5 = models.ForeignKey(...)

它是不可扩展的。如果你想允许6行或4行而不是5行,那就有一天(谁知道呢?),则必须添加/删除新行并更改数据库方案(并处理具有5行的现有对象)。它不是干的,你的代码量取决于你处理的行数,它涉及大量的复制粘贴。在

很明显,如果您想知道如果必须处理100行而不是5行,那么这是一个糟糕的设计。在

您必须使用^{}和一些自定义逻辑来确保至少有一行,最多五行。在

^{pr2}$

如果要对行进行排序,可以使用如下显式中间模型:

class ProductPageRow(models.Model):

    class Meta:
        order_with_respect_to = 'page'

    row = models.ForeignKey(ProductRow)
    page = models.ForeignKey(ProductPage)

class ProductPage(models.Model):
    ...
    rows = model.ManyToManyField(ProductRow, through=ProductPageRow)

我只允许N行(假设5行),您可以实现自己的order_with_respect_to逻辑:

from django.core.validators import MaxValueValidator

class ProductPageRow(models.Model):

    class Meta:
        unique_together = ('row', 'page', 'ordering')

    MAX_ROWS = 5

    row = models.ForeignKey(ProductRow)
    page = models.ForeignKey(ProductPage)
    ordering = models.PositiveSmallIntegerField(
        validators=[
            MaxValueValidator(MAX_ROWS - 1),
        ],
    )

元组('row', 'page', 'ordering')的唯一性被强制执行,并且排序被限制为5个值(从0到4),成对的('row', 'page')的出现次数不能超过5次。在

但是,除非您有很好的理由100%确保不可能通过任何方式在数据库中添加超过N行(包括DBMS控制台上的直接SQL查询输入),否则没有必要将其“锁定”到该级别。在

很可能所有“不受信任”的用户只能通过HTML表单输入更新数据库。在填写表单时,您可以使用formsets强制执行最小和最大行数。在

Note: This also applies to your other models. Any bunch of fields named foobar_N, where N is an incrementing integer, betrays a very bad database design.


你的问题还没有解决。在

从父模型实例中获取子模型实例的最简单方法(阅读“想到的第一个”)是遍历每个可能的子模型,直到得到匹配的实例。在

class ProductRow(models.Model):
    ...
    def get_actual_instance(self):
        if type(self) != ProductRow:
            # If it's not a ProductRow, its a child
            return self
        attr_name = '{}_ptr'.format(ProductRow._meta.model_name)
        for possible_class in self.__subclasses__():
            field_name = possible_class._meta.get_field(attr_name).related_query_name()
            try:
                return getattr(self, field_name)
            except possible_class.DoesNotExist:
                pass
         # If no child found, it was a ProductRow
         return self

但是每次尝试都要访问数据库。现在还不是很干燥。获取它的最有效方法是添加一个字段,该字段将告诉您子对象的类型:

from django.contrib.contenttypes.models import ContentType

class ProductRow(models.Model):
    ...
    actual_type = models.ForeignKey(ContentType, editable=False)

    def save(self, *args, **kwargs):
        if self._state.adding:
            self.actual_type = ContentType.objects.get_for_model(type(self))
         super().save(*args, **kwargs)

    def get_actual_instance(self):
        my_info = (self._meta.app_label, self._meta.model_name)
        actual_info = (self.actual_type.app_label, self.actual_type.model)
        if type(self) != ProductRow or my_info == actual_info:
            # If this is already the actual instance
            return self
        # Otherwise
        attr_name = '{}_ptr_id'.format(ProductRow._meta.model_name)
        return self.actual_type.get_object_for_this_type(**{
            attr_name: self.pk,
        })

您的type()方法不起作用,因为您使用的是multi-table inheritanceProductRow的每个子级都是一个独立的模型,使用自动生成的^{}连接到{}。在

  • 如果您确保每个ProductRow实例只有一种类型的子实例(三种可能的类型中的一种),那么有一种简单的方法来确定该子实例是ProductBannerProductMagazineRow还是ProductTextGridRow,然后使用适当的字段:

    class ProductRow(models.Model):
        ...
        def get_type(self):
            try:
                self.productbanner
                return 'product-banner'
            except ProductBanner.DoesNotExist:
                pass
    
            try:
                self.productmagazinerow
                return 'product-magazine'
            except ProductMagazineRow.DoesNotExist:
                pass
    
            try:
                self.producttextgridrow
                return 'product-text-grid'
            except ProductTextGridRow.DoesNotExist:
                pass
    
            return 'generic'
    
  • 但是,如果不强制执行,则ProductRow的一个实例可以同时链接到ProductBannerProductMagazineRow和{}中的多个。您将不得不使用特定实例:

    class ProductRow(models.Model):
        ...
        def get_productbanner(self):
            try:
                return self.productbanner
            except ProductBanner.DoesNotExist:
                return None
    
        def get_productmagazinerow(self):
            try:
                return self.productmagazinerow
            except ProductMagazineRow.DoesNotExist:
                return None
    
        def get_producttextgridrow(self)
            try:
                return self.producttextgridrow
            except ProductTextGridRow.DoesNotExist:
                return None
    

结合Antonio Pinsard的回答来改进数据库设计。在

相关问题 更多 >

    热门问题