(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__并让它调用子类'__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的内省。谢谢大家。
答案 0 :(得分:6)
你想要一个Abstract base class(“虚拟”在Python中没有任何意义。)
来自文档:
class CommonInfo(models.Model):
name = models.CharField(max_length=100)
age = models.PositiveIntegerField()
class Meta:
abstract = True
修改
“在OO编程中,当你调用object.method()时,你应该得到最低子类的实现。”
真。但不是整个故事。
这不是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不能做你认为应该做的事情。所以你有两个选择。
考虑这种方式来做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 )
答案 1 :(得分:5)
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'
这也向Ber回答了关于依赖于其他子类属性的 unicode ()的问题。您现在实际上正在子类实例上调用适当的方法。
现在,这确实表明,由于Django已经在幕后寻找子类实例,因此代码可以直接返回并返回Cat或Dog实例而不是Animal。你必须与开发者讨论这个问题。 :)
答案 2 :(得分:3)
Django(以及一般的关系数据库)不能以这种方式工作。即使使用像Django这样的ORM,你也不能使用像这样的类层次结构。
您的问题有两种可能的解决方案:
(1)给动物模型提供一个“名称”,然后添加名为['Dog','Cat']的实体。这将在外键选择框中显示动物的名字。
(2)如果您确实需要将您的外键链接到不同的模型(这通常不是使用RDBMS的常用方法),您应该阅读文档中的Generic Relations在contenttypes框架上。
但我的建议是(1)。
答案 3 :(得分:2)
这与S.Lott所建议的一致,但没有if / elif / ......,随着你需要支持的子类数量的增加,这会变得越来越难以维护。
class Cat(models.Model):
def __unicode__(self):
return u'A Cat!'
class Dog(models.Model):
def __unicode__(self):
return u'A Dog!'
class Eel(models.Model):
def __unicode__(self):
return u'An Eel!'
ANIMALS = {
'CAT': {'model': Cat, 'name': 'Cat'},
'DOG': {'model': Dog, 'name': 'Dog'},
'EEL': {'model': Eel, 'name': 'Eel'},
}
KIND_CHOICES = tuple((key, ANIMALS[key]['name']) for key in ANIMALS)
class Animal(models.Model):
kind = models.CharField(max_length=3, choices=KIND_CHOICES)
fk = models.IntegerField()
def get_kind(self):
return ANIMALS[self.kind]['model'].objects.get(pk=self.fk)
def __unicode__(self):
return unicode(self.get_kind())
使用Django的多表继承也可以完成类似的事情(搜索Django的文档)。例如:
ANIMALS = {
'CAT': {'model_name': 'Cat', 'name': 'Cat'},
'DOG': {'model_name': 'Dog', 'name': 'Dog'},
'EEL': {'model_name': 'Eel', 'name': 'Eel'},
}
KIND_CHOICES = tuple((key, ANIMALS[key]['name']) for key in ANIMALS)
class Animal(models.Model):
kind = models.CharField(max_length=3, choices=KIND_CHOICES)
def get_kind(self):
return getattr(self, ANIMALS[self.kind]['model_name'].lower())
def __unicode__(self):
return unicode(self.get_kind())
class Cat(Animal):
def __unicode__(self):
return u'A Cat!'
class Dog(Animal):
def __unicode__(self):
return u'A Dog!'
class Eel(Animal):
def __unicode__(self):
return u'An Eel!'
我个人更喜欢第二个选项,因为子类的实例将自动神奇地在父类中定义所有字段,这样可以获得更清晰,更简洁的代码。 (对于instace,如果Animal类有一个'gender'字段,那么Cat.objects.filter(gender ='MALE')就可以了。)
答案 4 :(得分:1)
关于通用关系,请注意正常的Django查询不能跨越GenerecForeignKey关系。使用多表继承避免了这个问题,但代价是不太通用的解决方案。
来自文档:
由于GenericForeignKey的实现方式,您不能通过数据库API直接将这些字段用于过滤器(例如filter()和exclude())。它们不是普通的野外物体。这些例子不起作用:
# This will fail
>>> TaggedItem.objects.filter(content_object=guido)
# This will also fail
>>> TaggedItem.objects.get(content_object=guido)
答案 5 :(得分:1)
我举了一个如何在这里实现模型的例子 - >
https://github.com/jmg/django_content_types_example/blob/master/generic_models/models.py
在这里你可以看到如何使用orm - >
https://github.com/jmg/django_content_types_example/blob/master/generic_models/tests.py