30

假设我有模型:

class Animal(models.Model):
    type = models.CharField(max_length=255)

class Dog(Animal):
    def make_sound(self):
        print "Woof!"
    class Meta:
        proxy = True

class Cat(Animal):
    def make_sound(self):
        print "Meow!"
    class Meta:
        proxy = True

假设我想做:

 animals = Animal.objects.all()
 for animal in animals:
     animal.make_sound()

我想找回一系列的呜呜声和喵喵声。显然,我可以在基于animal_type的分叉的原始模型中定义一个make_sound,但是每次我添加一个新的动物类型(想象它们在不同的应用程序中)时,我都必须进入并编辑那个make_sound函数. 我宁愿只定义代理模型并让它们自己定义行为。据我所知,没有办法返回混合的 Cat 或 Dog 实例,但我想也许我可以在返回 cat 或 dog 模型的主类上定义一个“get_proxy_model”方法。

你当然可以这样做,传递主键之类的东西,然后只做 Cat.objects.get(pk = pass_in_primary_key)。但这意味着对您已经拥有的数据进行额外的查询,这似乎是多余的。有什么方法可以有效地将动物变成猫或狗实例?做我想要实现的目标的正确方法是什么?

4

5 回答 5

11

thedk 提出的 Metaclass 方法确实是一种非常强大的方法,但是,我必须将它与此处问题的答案结合起来,以使查询返回代理模型实例。适用于前面示例的代码的简化版本将是:

from django.db.models.base import ModelBase

class InheritanceMetaclass(ModelBase):
    def __call__(cls, *args, **kwargs):
        obj = super(InheritanceMetaclass, cls).__call__(*args, **kwargs)
        return obj.get_object()

class Animal(models.Model):
    __metaclass__ = InheritanceMetaclass
    type = models.CharField(max_length=255)
    object_class = models.CharField(max_length=20)

    def save(self, *args, **kwargs):
        if not self.object_class:
            self.object_class = self._meta.module_name
        super(Animal, self).save( *args, **kwargs)

    def get_object(self):
        if self.object_class in SUBCLASSES_OF_ANIMAL:
            self.__class__ = SUBCLASSES_OF_ANIMAL[self.object_class]
        return self

class Dog(Animal):
    class Meta:
        proxy = True
    def make_sound(self):
        print "Woof!"


class Cat(Animal):
    class Meta:
        proxy = True
    def make_sound(self):
        print "Meow!"


SUBCLASSES_OF_ANIMAL = dict([(cls.__name__, cls) for cls in ANIMAL.__subclasses__()])

这种代理方法的优点是在创建新子类时不需要进行数据库迁移。缺点是不能将特定字段添加到子类中。

我很乐意收到有关此方法的反馈。

于 2014-05-14T19:05:08.783 回答
5

人类已知的唯一方法是使用元类编程。

这是简短的回答:

from django.db.models.base import ModelBase

class InheritanceMetaclass(ModelBase):
    def __call__(cls, *args, **kwargs):
        obj = super(InheritanceMetaclass, cls).__call__(*args, **kwargs)
        return obj.get_object()

class Animal(models.Model):
    __metaclass__ = InheritanceMetaclass
    type = models.CharField(max_length=255)
    object_class = models.CharField(max_length=20)

    def save(self, *args, **kwargs):
        if not self.object_class:
            self.object_class = self._meta.module_name
        super(Animal, self).save( *args, **kwargs)
    def get_object(self):
        if not self.object_class or self._meta.module_name == self.object_class:
            return self
        else:
            return getattr(self, self.object_class)

class Dog(Animal):
    def make_sound(self):
        print "Woof!"


class Cat(Animal):
    def make_sound(self):
        print "Meow!"

和期望的结果:

shell$ ./manage.py shell_plus
From 'models' autoload: Animal, Dog, Cat
Python 2.6.5 (r265:79063, Apr 16 2010, 13:57:41) 
[GCC 4.4.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> dog1=Dog(type="Ozzie").save()
>>> cat1=Cat(type="Kitty").save()
>>> dog2=Dog(type="Dozzie").save()
>>> cat2=Cat(type="Kinnie").save()
>>> Animal.objects.all()
[<Dog: Dog object>, <Cat: Cat object>, <Dog: Dog object>, <Cat: Cat object>]
>>> for a in Animal.objects.all():
...    print a.type, a.make_sound()
... 
Ozzie Woof!
None
Kitty Meow!
None
Dozzie Woof!
None
Kinnie Meow!
None
>>> 

它是如何工作的?

  1. 存储有关动物类名的信息 - 我们使用 object_class
  2. 删除“代理”元属性 - 我们需要在 Django 中反转关系(不好的一面是我们为每个子模型创建额外的数据库表并为此浪费额外的数据库命中,好的一面是我们可以添加一些子模型相关的字段)
  3. 为 Animal 自定义 save() 以将类名保存在调用 save 的对象的 object_class 中。
  4. 需要方法 get_object 来通过 Django 中的反向关系引用名称缓存在 object_class 中的模型。
  5. 每次通过重新定义 Animal 模型的 Metaclass 实例化 Animal 时,都会自动执行此 .get_object()“强制转换”。元类类似于类的模板(就像类是对象的模板一样)。

有关 Python 中元类的更多信息:http: //www.ibm.com/developerworks/linux/library/l-pymeta.html

于 2011-03-13T23:30:53.520 回答
3

我玩了很多方法来做到这一点。最后,最简单的似乎是前进的方向。覆盖__init__基类。

class Animal(models.Model):
    type = models.CharField(max_length=255)
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.__class__ = eval(self.type)

我知道 eval 可能很危险,bla bla bla,但您始终可以在类型选择上添加保护/验证,以确保它是您想要看到的。Besdies 那,我想不出任何明显的陷阱,但如果我发现任何我会提到它们/删除答案!(是的,我知道这个问题已经很老了,但希望这能帮助其他有同样问题的人)

于 2018-11-15T11:10:21.040 回答
2

您也许可以使用此处描述的方法使 Django 模型具有多态性。我相信,该代码处于开发的早期阶段,但值得研究。

于 2010-02-07T23:56:29.953 回答
0

这个答案可能在某种程度上回避了这个问题,因为它不使用代理模型。但是,正如问题所问的那样,它确实让人们编写以下内容(如果添加了新类型,则无需更新Animal类)-

animals = Animal.objects.all()
for animal in animals:
    animal.make_sound()

为了避免元类编程,可以使用组合而不是继承。例如 -

class Animal(models.Model):

    type = models.CharField(max_length=255)

    @property
    def type_instance(self):
        """Return a Dog or Cat object, etc."""
        return globals()[self.type]()

    def make_sound(self):
        return self.type_instance.make_sound()

class Dog(object):
    def make_sound(self):
        print "Woof!"

class Cat(object):
    def make_sound(self):
        print "Meow!"

如果DogCat类需要访问Animal实例,您还可以调整type_instance()上面的方法以将所需的内容传递给类构造函数(例如self)。

于 2014-02-22T18:16:29.403 回答