在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()
我想得到一系列的“汪汪”和“喵喵”。很明显,我可以在原来的模型里定义一个叫做make_sound的函数,根据动物类型来分支,但每次我添加一个新的动物类型(想象一下它们在不同的应用里),我就得去修改这个make_sound函数。我更希望能定义一些代理模型,让它们自己定义行为。从我能看到的,似乎没有办法返回混合的猫或狗实例,但我想也许我可以在主类上定义一个“get_proxy_model”的方法,返回一个猫或狗的模型。
当然,你可以这样做,传入一些像主键这样的东西,然后就可以用Cat.objects.get(pk = 传入的主键)来获取。但这就意味着要多做一次查询,而这些数据你其实已经有了,这样看起来有点多余。有没有什么办法可以有效地把一个动物转变成猫或狗的实例?我想实现的目标有什么正确的方法吗?
5 个回答
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
>>>
它是如何工作的呢?
- 存储关于类的信息,比如动物的名字 - 我们用 object_class 来实现这个。
- 移除“代理”元属性 - 我们需要在 Django 中反向关系(这样做的坏处是每个子模型都会创建额外的数据库表,并且会多一次数据库访问,好的方面是我们可以添加一些依赖于子模型的字段)。
- 定制 Animal 的 save() 方法,以便在调用 save 的对象中保存类名到 object_class。
- 需要一个 get_object 方法,用于通过 Django 中的反向关系引用到 object_class 中缓存的模型名称。
- 每次实例化 Animal 时,自动进行这个 .get_object() 的“转换”,通过重新定义 Animal 模型的元类来实现。元类就像是类的模板(就像类是对象的模板一样)。
关于 Python 中元类的更多信息: http://www.ibm.com/developerworks/linux/library/l-pymeta.html
12
thedk 提出的元类方法确实是一个非常强大的解决方案,不过我需要把它和这个问题的答案结合起来,才能让查询返回一个 代理 模型实例。根据之前的例子,简化后的代码如下:
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__()])
这个代理方法的好处是,创建新子类时不需要进行数据库迁移。缺点是,子类不能添加特定的字段。
我很乐意听听大家对这个方法的反馈。
2
你可以尝试使用这里提到的方法,让Django模型具有多态性。这个代码目前还在早期开发阶段,但值得一看。