2010-02-07 47 views
24

说我有型号:在Django中从基础模型实例返回代理模型实例的正确方法?

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 = passed_in_primary_key)。但这意味着要对已有的数据进行额外的查询,这似乎是多余的。有没有什么办法可以有效地将动物变成猫或狗的实例?什么是正确的方式来做我想达到的目标?

+0

您可以应用make_sound动物模型,并添加声音= models.charField()到它。 – monkut 2010-02-08 01:22:54

+2

我的例子很简单 - 我想要做的事情需要一堆取决于类型的工作,并且不能与模型一起存储。 – sotangochips 2010-02-08 04:26:23

回答

1

你也许可以使用here描述的方法使Django模型变为多态。我相信这些代码处于开发的早期阶段,但值得研究。

4

人类已知的唯一方法是使用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 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( 反向关系,这个坏 方面,我们创造额外DB 表每一个儿童模型和 浪费额外的DB命中为, 好的一面,我们可以添加一些孩子 模型相关字段)
  3. 自定义保存()动物拯救类 调用保存的对象 的object_class中的名称。
  4. 方法get_object需要通过在Django 中通过反向关系引用 与名称缓存在 object_class中的Model。
  5. 做这个.get_object()自动“投射” 每次动物被实例化 重新定义动物的Metaclass 模型。 Metaclass就像一个 类的模板(就像 类是一个对象的模板)。关于元类Python中

的更多信息:http://www.ibm.com/developerworks/linux/library/l-pymeta.html

0

这个答案可能是侧步有些问题,因为它不使用代理模式。然而,因为这个问题问,它让一个编写以下(而无需更新Animal类,如果添加了新的类型) -

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

为了避免元类编程,可以使用composition over inheritance。对于example--

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

7

通过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迁移在创建新的子类必须的。缺点是没有特定的字段可以添加到子类中。

我很高兴能有这种方法的反馈。

+0

伟大的工作,不知道这是否反抗真实生活测试。 – eugene 2016-04-29 10:53:12

+1

我认为将SUBCLASSES_OF_ANIMAL注册移动到元类“__init__”会很方便 – eugene 2016-04-29 11:16:19

相关问题