[英]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
實例只有一種子類型(在三種可能的子類型中),則有一種簡單的方法可以找出該子類型是ProductBanner
, ProductMagazineRow
還是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
一個實例鏈接到ProductBanner
, ProductMagazineRow
和ProductTextGridRow
中的多個實例。 您必須改為使用特定實例:
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.