Ale*_*yer 2 python django inheritance model
我为CMS创建了一组Django模型来显示一系列Products.
每个页面都包含一系列行,所以我有一个泛型
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)
Run Code Online (Sandbox Code Playgroud)
对于不同类型的行,我有一系列这个模型的孩子:
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)
...
Run Code Online (Sandbox Code Playgroud)
等等.
然后在我的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)
Run Code Online (Sandbox Code Playgroud)
我遇到的问题是,我想允许这5行中的ProductPage任何一种是不同的子类型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]
Run Code Online (Sandbox Code Playgroud)
然后在模板中:
{% for row in product_page_rows %}
<pre>{{ row.XXXX }}</pre>
{% endfor %}
Run Code Online (Sandbox Code Playgroud)
我不能将任何子字段引用为XXXX.
我尝试向type父节点和子节点添加" ()"方法,尝试区分每一行的类:
class ProductRow(models.Model):
...
@classmethod
def type(cls):
return "generic"
Run Code Online (Sandbox Code Playgroud)
和
class ProductTextGridRow(TourRow):
...
@classmethod
def type(cls):
return "text-grid"
Run Code Online (Sandbox Code Playgroud)
但是如果我在模板中更改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(...)
Run Code Online (Sandbox Code Playgroud)
它不可扩展.如果您想要允许6行或4行而不是5天,一天(谁知道?),您将不得不添加/删除新行并更改数据库方案(并处理具有5行的现有对象).并且它不是DRY,您的代码量取决于您处理的行数,并且涉及大量的复制粘贴.
这很清楚,这是一个不好的设计,如果你想知道你会怎么做,如果你必须处理100行,而不是5.
您必须使用一个ManyToManyField()和一些自定义逻辑来确保至少有一行,最多五行.
class ProductPage(models.Model):
...
rows = models.ManyToManyField(ProductRow)
Run Code Online (Sandbox Code Playgroud)
如果要对行进行排序,可以使用如下的显式中间模型:
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)
Run Code Online (Sandbox Code Playgroud)
我想只允许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),
],
)
Run Code Online (Sandbox Code Playgroud)
('row', 'page', 'ordering')强制执行元组唯一性,并且排序限制为五个值(从0到4),这对夫妇的出现次数不能超过5次('row', 'page').
但是,除非您有充分的理由100%确定无法N通过任何方式在数据库中添加多行(包括DBMS控制台上的直接SQL查询输入),否则无需"锁定"这是一个水平.
所有"不受信任"的用户很可能只能通过HTML表单输入更新您的数据库.并且您可以在填写表单时使用formset强制最小和最大行数.
注意:这也适用于您的其他型号.任何一组名称
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
Run Code Online (Sandbox Code Playgroud)
但它涉及到每次尝试点击数据库.它仍然不是很干.获得它的最有效方法是添加一个字段,告诉你孩子的类型:
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,
})
Run Code Online (Sandbox Code Playgroud)