从Django中的基本模型实例返回代理模型实例的正确方法?

时间:2010-02-07 23:51:55

标签: python django django-models proxy-classes

说我有模特:

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()

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

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

5 个答案:

答案 0 :(得分:9)

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

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__()])

此代理方法的优点是在创建新子类时不需要db迁移。缺点是没有特定的字段可以添加到子类。

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

答案 1 :(得分:4)

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

这是简短的回答:

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. 存储有关课程的信息 动物的名字 - 我们使用 那个
  2. 的object_class
  3. 删除“代理”元属性 - 我们需要 Django中的反向关系(坏的 我们创造了额外的数据库 每个儿童模型的表格 浪费额外的DB命中, 好的一面,我们可以添加一些孩子 模型依赖领域)
  4. 为Animal自定义save()以保存该类 对象的object_class中的名称 调用保存。
  5. 引用需要方法get_object 通过Django中的反向关系 到名为cached的模型 object_class。
  6. 自动执行.get_object()“cast” 每次动物实例化 重新定义动物元素 模型。元类就像一个 一个类的模板(就像一个 class是对象的模板。
  7. 有关Python中元类的更多信息:http://www.ibm.com/developerworks/linux/library/l-pymeta.html

答案 2 :(得分:1)

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

答案 3 :(得分:1)

我尝试了很多方法来做到这一点。最后,最简单的方法似乎是前进的道路。 覆盖基类的__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 bla,但是您始终可以在类型选择上添加保护/验证以确保它是您想要看到的。 别担心,我想不出任何明显的陷阱,但是如果我发现任何陷阱,我会提及它们/删除答案! (是的,我知道这个问题太老了,但是希望这可以帮助其他遇到相同问题的人)

答案 4 :(得分:0)

这个答案可能有些侧面,因为它没有使用代理模型。但是,正如问题所要求的那样,它确实让人编写以下内容(如果添加了新类型,则无需更新Animal类) -

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

为了避免元类编程,可以使用composition over inheritance。例如 -

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)。