簡體   English   中英

Django:具有多種子模型類型的父模型

[英]Django: Parent Model with multiple child model types

我為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)

然后針對不同類型的行,我有一系列此模型的子代:

class ProductBanner(ProductRow):
  wide_image = models.ImageField(upload_to='product_images/banners/', max_length=100, null=False, blank=False)
  top_heading_text = models.CharField(max_length=100, null=False, blank=False)
  main_heading_text = models.CharField(max_length=200, null=False, blank=False)
  ...

class ProductMagazineRow(ProductRow):
  title = models.CharField(max_length=50, null=False, blank=False)
  show_descriptions = models.BooleanField(null=False, blank=False, default=False)
  panel_1_product = models.ForeignKey(Product, related_name='+', null=False, blank=False)
  panel_2_product = models.ForeignKey(Product, related_name='+', null=False, blank=False)
  panel_3_product = models.ForeignKey(Product, related_name='+', null=False, blank=False)
  ...

class ProductTextGridRow(ProductRow):
  title = models.CharField(max_length=50, null=False, blank=False)
  col1_title = models.CharField(max_length=50, null=False, blank=False)
  col1_product_1 = models.ForeignKey(Product, related_name='+', null=False, blank=False)
  col1_product_2 = models.ForeignKey(Product, related_name='+', null=False, blank=False)
  col1_product_3 = models.ForeignKey(Product, related_name='+', null=False, blank=False)
  ...

等等。

然后在我的ProductPage我有一系列ProductRow

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)

我遇到的問題是,我想允許ProductPage中的那5行成為ProductRow任何不同子類型。 但是,當我遍歷它們時,例如

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"

但如果我改變XXXX.type()的模板,然后它顯示"generic"列表中的每一項(我曾在數據定義的各種行類型的),所以我想一切都回來為ProductRow而而不是適當的孩子類型。 我找不到讓子項作為正確的子類型(而不是父類型)進行訪問或確定它們實際是哪種子類型的方法(我也嘗試catch AttributeError ,這沒有幫助)。

有人可以建議我如何正確處理各種類型的模型列表,這些列表都包含一個公共父模型,並能夠訪問適當的子模型類型的字段嗎?

這通常是(總是讀到)一個不好的設計,需要具有以下內容:

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行的現有對象)。 而且它不是DRY,您的代碼量取決於您處理的行數,並且涉及很多復制粘貼。

這清楚地表明,如果您想知道如果必須處理100行而不是5行將如何處理,那將是一個糟糕的設計。

您必須使用ManyToManyField()和一些自定義邏輯來確保至少有一行,最多有五行。

class ProductPage(models.Model):
    ...
    rows = models.ManyToManyField(ProductRow)

如果要對行進行排序,則可以使用如下所示的顯式中間模型:

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')唯一性,並且將排序限制為五個值(從0到4),這對夫婦的出現次數不能超過5個('row', 'page')

但是,除非您有充分的理由確保100%確保沒有辦法以任何方式在數據庫中添加超過N行(包括在DBMS控制台上直接輸入SQL查詢),否則無需“鎖定” “這個水平。

所有“不可信”用戶很可能只能通過HTML表單輸入來更新數據庫。 填寫表單時,您可以使用表單集來強制最小和最大行數。

注意:這也適用於您的其他型號。 任何名為foobar_N的字段(其中N是一個遞增整數)都背叛了非常糟糕的數據庫設計。


但是,這不能解決您的問題。

從父模型實例取回子模型實例的最簡單方法(請閱讀“首先想到的”)是遍歷每個可能的子模型,直到獲得匹配的實例為止。

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()方法不起作用,因為您使用的是多表繼承 :每個ProductRow的子代都是一個單獨的模型,該模型使用自動生成的OneToOneField連接到ProductRow

  • 如果確保每個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一個實例鏈接到ProductBannerProductMagazineRowProductTextGridRow中的多個實例。 您必須改為使用特定實例:

     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的答案結合起來可以改善數據庫設計。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM