5

(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 和 Dogs,但是在 AnimalHome 的管理页面中,我对 Animals 的选择显示为“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 并没有在幕后执行任何工作,例如:“我是动物。我知道动物有子类Dog和Cat。具体来说,我是3号动物,而且我刚刚查了一下,也有3号猫。这意味着我实际上是3号猫”。尽管使用 Python 的内省,这似乎完全有可能并且非常合理(因为 Cat 不会做 Animal 自己做不到的任何事情)。谢谢你们。

4

6 回答 6

6

你想要一个抽象基类(“虚拟”在 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 的工作方式。每个类都是一个不同的表。Cat 和 Dog——根据定义——是独立的表,具有独立的查询。您没有使用对象存储。您正在将 ORM 用于关系表。


编辑

首先,您的查询仅在 Dog 和 Cat 共享一个公共密钥生成器并且没有一组重叠的 PK 时才有效。

如果你有一只 PK 为 42 的狗和一只 PK 为 42 的猫,你就有问题了。而且由于您无法轻松控制密钥生成,因此您的解决方案无法正常工作。

运行时类型标识不好。它在很多方面都不是面向对象的。几乎你可以做的任何事情来避免 RTTI 都比不断扩展的 if 语句序列来区分子类要好。

但是,您尝试构建的模型——特别是——对于 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 )
于 2008-11-23T23:23:44.277 回答
6

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。您将不得不与开发人员讨论这个问题。:)

于 2008-11-24T16:40:56.197 回答
3

Django(以及一般的关系数据库)不能以这种方式工作。即使使用像 Django 这样的 ORM,您也不会使用这样的类层次结构。

您的问题有两种可能的解决方案:

(1) 给Animal模型一个“名称”属性,然后添加名称来自['Dog', 'Cat']的实体。这将在外键选择框中显示动物的名称。

(2) 如果您确实需要将外键链接到不同的模型(这确实不是使用 RDBMS 的常用方法),您应该阅读有关内容类型框架的文档中的通用关系。

不过,我的建议是(1)。

于 2008-11-23T23:22:06.480 回答
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!'        

我个人更喜欢第二种选择,因为子类的实例将自动神奇地在父类中定义所有字段,这允许更清晰和更简洁的代码。(例如,如果 Animal 类有一个 'gender' 字段,那么 Cat.objects.filter(gender='MALE') 会起作用)。

于 2008-11-24T12:28:56.733 回答
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)
于 2008-11-25T08:44:04.147 回答
1

您可以使用django 内容框架

我举了一个例子来说明如何在这里实现你的模型->

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

于 2013-11-14T20:41:18.933 回答