(Django 1.x,Python 2.6.x)
我有以下模型:
class Animal(models.Model): pass class Cat(Animal): def __unicode__(self): return "This is a cat" class Dog(Animal): def __unicode__(self): return "This is a dog" class AnimalHome(models.Model): animal = models.ForeignKey(Animal)
我没有实例化动物,因为这应该是一个虚拟类.我已经实例化了Cats and Dogs,但是在AnimalHome的Admin Page中,我对Animal的选择显示为"Animal object"(我猜是默认的__unicode __()),而不是我为两个子类定义的__unicode__.救命.
我想,抽象的基类问题是这个问题的一个问题.即使Animal不应该是抽象的,我仍然有一个问题,由于某种原因,由于ForeignKey是在Animal上定义的而不是其子类之一,因此调用了超类方法而不是子类.在OO编程中,当你调用object.method()时,你应该得到最低子类的实现,你必须做额外的工作来获得任何超类的实现.那么为什么在子类上定义__unicode__是不够的---实际上问题可能是__unicode__根本没有被调用,因为对Animal类的内省显示它没有被定义.所以,如果我为Animal定义__unicode__并让它调用子类'
好的,我认为我理解ORM问题.这两个答案都帮助我理解了这一点,谢谢.在尝试这个时,我发现当Django保存子类模型时,它会做两件事:(1)它为超类表中的子类对象创建一行,(2)它使子类表中的PK与超类表中指定的PK.子类表中的这个PK名为superclass_ptr.基于此,我编造了以下内容.我很感激反馈.
Class Animal(models.Model) def __unicode__(self): if Dog.objects.filter(pk=self.pk).count() > 0: return unicode(Dog.objects.get(pk=self.pk)) elif Cat.objects.filter(pk=self.pk).count() > 0: return unicode(Cat.objects.get(pk=self.pk)) else: return "An Animal!"
劳伦斯似乎最关注这个问题.Cat和Dog将有不相交的PK集(并且Animal的任何子类将具有与其超类的记录相同的PK),但不幸的是Django不会在幕后执行任何工作:"我是动物.我知道动物有狗和猫的子类.具体来说,我是动物3号,而且我刚刚检查过,它也是一个Cat 3号.这意味着我实际上是Cat 3号.尽管这看起来完全可能且非常合理(因为Cat不会做任何动物无法做到的事情)使用Python的内省.谢谢你们.
你想要一个抽象基类("虚拟"并不代表Python中的任何东西.)
从文档:
class CommonInfo(models.Model): name = models.CharField(max_length=100) age = models.PositiveIntegerField() class Meta: abstract = True
编辑
"在调用object.method()时,在OO编程中,你应该得到最低子类的实现."
真正.但不是整个故事.
这不是OO问题.甚至是Python或Django问题.这是一个ORM问题.
问题是"在FK参考结束时重建了什么对象?" 答案是,如何处理从FK值到对象的转换没有标准的,明显的答案.
我AnimalHome
有一个animals
值为42 的行.它指的是Animal.objects.get(pk=42)
.动物的哪个子类?猫?狗?如何ORM层知道,如果它应该做的Dog.objects.get(pk=42)
还是Cat.objects.get(pk=42)
?
"但是等等,"你说."它应该获取Animal对象,而不是Dog或Cat对象." 你可以希望如此,但这不是Django ORM的工作方式.每个类都是一个独特的表.根据定义,猫和狗是单独的表,具有单独的查询.您没有使用对象库.您正在将ORM用于关系表.
编辑
首先,只有当Dog和Cat共享一个公共密钥生成器,并且没有重叠的PK集时,您的查询才有效.
如果你有一只PK为42的狗和一只PK为42的猫,那你就有问题了.由于您无法轻松控制密钥生成,因此您的解决方案无法正常工作.
运行时类型识别很糟糕.它在很多方面都不是面向对象的.几乎任何可以避免RTTI的事情都比if-statements不断扩展的序列更能区分子类.
但是,您尝试构建的模型 - 特别是 - ORM系统的病理问题.事实上,如此具体的病态,我几乎愿意打赌它的功课.[纯SQL系统也存在病理问题.他们经常出现在家庭作业中.]
问题是ORM不能做你认为应该做的事情.所以你有两个选择.
停止使用Django.
做Django直接做的事.
打破OO设计指南并采用像RTTI这样的脆弱的东西,这使得添加另一个动物子类变得非常困难.
考虑这种方式来做RTTI - 它包括类名和PK
KIND_CHOICES = ( ( "DOG", "Dog" ), ( "CAT", "Cat" ), ) class Animal( models.Model ): kind = models.CharField( max_length= 1, choices=KIND_CHOICES ) fk = models.IntegerField() def get_kind( self ): if kind == "DOG": return Dog.objects.get( pk = fk ) elif kind == "CAT": return Cat.objects.get( pk = fk )
ForeignKey(Animal)就是这样,一个外键引用Animal表中的一行.底层SQL模式中没有任何内容表明该表被用作超类,因此您将获得一个Animal对象.
要解决这个问题:
首先,您希望基类是非抽象的.无论如何,这对于ForeignKey是必要的,并且还确保Dog和Cat将具有分离的主键集.
现在,Django使用OneToOneField实现继承.因此,具有子类实例的基类实例将获得对该实例的引用,并对其进行适当命名.这意味着您可以:
class Animal(models.Model): def __unicode__(self): if hasattr(self, 'dog'): return self.dog.__unicode__() elif hasattr(self, 'cat'): return self.cat.__unicode__() else: return 'Animal'
这也回答了你关于一个依赖于其他子类属性的unicode()的问题.您现在实际上正在子类实例上调用适当的方法.
现在,这确实表明,由于Django已经在幕后寻找子类实例,因此代码可以直接返回并返回Cat或Dog实例而不是Animal.你必须与开发者讨论这个问题.:)