<p>这通常是一个糟糕的设计,有这样的东西:</p>
<pre><code>class MyModel(models.Model):
...
row_1 = models.ForeignKey(...)
row_2 = models.ForeignKey(...)
row_3 = models.ForeignKey(...)
row_4 = models.ForeignKey(...)
row_5 = models.ForeignKey(...)
</code></pre>
<p>它是不可扩展的。如果你想允许6行或4行而不是5行,那就有一天(谁知道呢?),则必须添加/删除新行并更改数据库方案(并处理具有5行的现有对象)。它不是干的,你的代码量取决于你处理的行数,它涉及大量的复制粘贴。在</p>
<p>很明显,如果您想知道如果必须处理100行而不是5行,那么这是一个糟糕的设计。在</p>
<p>您必须使用<a href="https://docs.djangoproject.com/en/stable/ref/models/fields/#manytomanyfield" rel="nofollow">^{<cd1>}</a>和一些自定义逻辑来确保至少有一行,最多五行。在</p>
^{pr2}$
<p>如果要对行进行排序,可以使用如下显式中间模型:</p>
<pre><code>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)
</code></pre>
<p>我只允许<code>N</code>行(假设5行),您可以实现自己的<code>order_with_respect_to</code>逻辑:</p>
<pre><code>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),
],
)
</code></pre>
<p>元组<code>('row', 'page', 'ordering')</code>的唯一性被强制执行,并且排序被限制为5个值(从0到4),成对的<code>('row', 'page')</code>的出现次数不能超过5次。在</p>
<p><strong>但是,</strong>除非您有很好的理由100%确保不可能通过任何方式在数据库中添加超过<code>N</code>行(包括DBMS控制台上的直接SQL查询输入),否则没有必要将其“锁定”到该级别。在</p>
<p>很可能所有“不受信任”的用户只能通过HTML表单输入更新数据库。在填写表单时,您可以使用<strong><a href="https://docs.djangoproject.com/en/stable/topics/forms/formsets/" rel="nofollow">formsets</a></strong>强制执行最小和最大行数。在</p>
<blockquote>
<p><strong>Note:</strong> This also applies to your other models. Any bunch of fields named
<code>foobar_N</code>, where <code>N</code> is an incrementing integer, betrays a very bad
database design.</p>
</blockquote>
<hr/>
<p>你的问题还没有解决。在</p>
<p>从父模型实例中获取子模型实例的最简单方法(阅读“想到的第一个”)是遍历每个可能的子模型,直到得到匹配的实例。在</p>
<pre><code>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
</code></pre>
<p><strong>但是</strong>每次尝试都要访问数据库。现在还不是很干燥。获取它的最有效方法是添加一个字段,该字段将告诉您子对象的类型:</p>
<pre><code>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,
})
</code></pre>