django模型继承中的一致性需求
Posted
技术标签:
【中文标题】django模型继承中的一致性需求【英文标题】:The want of consistency in django models inheritance 【发布时间】:2013-12-15 17:31:14 【问题描述】:让我们考虑两个简单的模型(假设为 Django v1.5.5),一个继承自另一个:
from django.db import models
class StreetAddress(models.Model):
street_name = models.CharField(max_length=64)
building_number = models.PositiveSmallIntegerField()
def __str__(self):
return "%s %d" % (self.street_name, self.building_number)
class Cafe(StreetAddress):
name = models.CharField(max_length=64)
StreetAddress 故意创建为非抽象的,因为我认为它的实例是独立于咖啡馆实例或其他可能的后代创建的。 让我们尝试创建一些位于某个地址的咖啡馆:
Python 2.7.4 (default, Apr 19 2013, 18:28:01)
[GCC 4.7.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from heir_demo.models import StreetAddress, Cafe
>>> addr = StreetAddress.objects.create(street_name='Piccadilly', building_number=5)
>>> addr
<StreetAddress: Piccadilly 5>
>>> mollys = Cafe.objects.create(streetaddress_ptr=addr, name='Mollys')
Traceback (most recent call last):
File "<console>", line 1, in <module>
... stacktrace goes here ...
Warning: Column 'building_number' cannot be null
问题是——当我明确指定这个模型的实例时,为什么 django 需要祖先模型的字段?
我知道可以通过这样的小解决方法来解决它:
class Cafe(StreetAddress):
name = models.CharField(max_length=64)
def save(self, *args, **kwargs):
if self.streetaddress_ptr is not None:
for field in self.streetaddress_ptr._meta.local_fields:
if field.name != 'id':
setattr(
self,
field.name,
getattr(self.streetaddress_ptr, field.name)
)
return super(Cafe, self).save(*args, **kwargs)
现在它可以正常工作了:
>>> from heir_demo.models import StreetAddress, Cafe
>>> addr = StreetAddress.objects.all()[0]
>>> mollys = Cafe.objects.create(streetaddress_ptr=addr, name='Mollys')
>>> mollys
<Cafe: Piccadilly 5>
请解释一下——为什么没有任何解决方法它就不能工作?这不是最一致和可预测的行为吗?
非常感谢。
【问题讨论】:
如果您想要这种行为,请尝试使用OneToOne
关系而不是继承。
是的,它有效,但根据django documentation:继承关系引入了子模型与其每个父模型之间的链接(通过自动创建的 OneToOneField)。因此,我认为没有其他模型继承行为的原因。
你的模型(不是双关语)有缺陷。咖啡馆不是街道地址,它有街道地址。封装,而不是继承。
作曲!我的意思是作曲! 羞愧地低着头
@IgnacioVazquez-Abrams 我很抱歉,如果这个例子让你感到困惑,它只是为了演示目的而设计的。 问题是——当我明确指定这个模型的实例时,为什么 django 需要祖先模型的字段?
【参考方案1】:
我敢自己回答。虽然,它可能是一个提议,而不是一个答案。
这是 Django v.1.5.x 的一个小补丁(它也可以很容易地适应 Django master 分支)。
我建议在 django.db.models.Model 类的 save_base 方法中替换 these two lines 通过以下代码(只是一个想法):
related_object = getattr(self, field.name)
if related_object is None:
self.save_base(cls=parent, origin=org, using=using,
update_fields=update_fields)
else:
for related_field in parent._meta.local_fields:
if related_field is not parent._meta.pk:
setattr(
self,
related_field.attname,
getattr(related_object, related_field.attname)
)
这个小补丁允许基于父模型的现有实例创建派生模型的实例。
>>> from heir_demo.models import StreetAddress, Cafe
>>> addr = StreetAddress.objects.create(street_name='Piccadilly', building_number=5)
>>> mollys = Cafe.objects.create(streetaddress_ptr=addr, name='Mollys')
>>> mollys
<Cafe: Piccadilly 5>
【讨论】:
【参考方案2】:您的提议将允许创建共享其父部分的单独对象(考虑一个看起来像您的咖啡馆的模型餐厅)。也就是说,一般来说,在面向对象的世界中很奇怪,但在关系世界中很好。
但是,您会发现您可以在同一个 StreetAddress 中拥有一家咖啡馆和一家餐厅,但不能同时拥有两家咖啡馆或两家餐馆。这将比 Django 目前的预期更令人惊讶,即(基本上)每个对象都有自己的身份。
【讨论】:
我不建议违反继承所基于的一对一关系。我相信没有必要担心这种违规的可能性,因为在数据库级别上应用于这个外键的唯一约束。顺便说一句,这个例子(街道和咖啡馆)只是一个例子,考虑到你的反应,它似乎绝对是低效和尴尬的。我还建议扩展而不是改变当前的行为。 唯一约束在每个表中单独执行。如果有餐馆和咖啡馆,则没有唯一约束会阻止它们之间共享 id ——除非你不能突然在现有街道地址上添加咖啡馆;然后唯一的街道地址 ID 处理它。 现在我明白你的意思了。尽管如此,在我看来,可以明确启用某些模型的这种行为。无论如何,谢谢你的解释。以上是关于django模型继承中的一致性需求的主要内容,如果未能解决你的问题,请参考以下文章
有没有一种简单的方法可以将 Django 的模型和迁移链与 db 验证一致性进行比较?