我为CMS创建了一组Django模型,以显示一系列Product
s。
每个页面都包含一系列行,所以我有一个通用的
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
s:
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
ing AttributeError
,没有帮助)。
有人可以建议我如何正确处理包含共同父类的各种模型类型列表,并能够访问相应子模型类型的字段吗?
答案 0 :(得分:5)
这通常(读“总是”)是一个糟糕的设计,有这样的东西:
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),这对夫妇('row', 'page')
的出现次数不能超过5次。
然而,除非您有充分的理由让100%确定无法通过任何方式在数据库中添加超过N
行(包括直接SQL查询)在DBMS控制台上输入),无需“锁定”此级别。
所有“不受信任”的用户很可能只能通过HTML表单输入更新您的数据库。在填写表单时,您可以使用 formsets 强制最小行数和最大行数。
注意:这也适用于您的其他型号。任意一组字段命名
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,
})
答案 1 :(得分:1)
您的type()
方法无效,因为您正在使用multi-table inheritance:ProductRow
个孩子中的每一个都是与{{{{}}连接的独立模型1}}使用自动生成的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
和{{1}中的多个实例相关联}} 同时。您必须改为使用特定实例:
ProductMagazineRow
将此与Antonio Pinsard的答案相结合,以改善您的数据库设计。