集成查询集的Django模型子类化
就像在这个问题中提到的那样,我想要的是能够得到一个包含不同对象的查询集:
>>> Product.objects.all()
[<SimpleProduct: ...>, <OtherProduct: ...>, <BlueProduct: ...>, ...]
我发现我不能仅仅把Product.Meta.abstract
设置为真,也不能简单地把不同对象的查询集合并在一起。没关系,但这些都是一个共同类的子类,所以只要我把它们的父类保持为非抽象类,我应该就能满意,只要我能让它的管理器返回正确类的对象。Django中的查询代码会正常工作,只是调用Product()
。听起来很简单,但当我重写Product.__new__
时就出问题了,我猜是因为Model中的__metaclass__
。这里有一段非Django的代码,基本上是我想要的效果:
class Top(object):
_counter = 0
def __init__(self, arg):
Top._counter += 1
print "Top#__init__(%s) called %d times" % (arg, Top._counter)
class A(Top):
def __new__(cls, *args, **kwargs):
if cls is A and len(args) > 0:
if args[0] is B.fav:
return B(*args, **kwargs)
elif args[0] is C.fav:
return C(*args, **kwargs)
else:
print "PRETENDING TO BE ABSTRACT"
return None # or raise?
else:
return super(A).__new__(cls, *args, **kwargs)
class B(A):
fav = 1
class C(A):
fav = 2
A(0) # => None
A(1) # => <B object>
A(2) # => <C object>
但是如果我继承自django.db.models.Model
而不是object
,就会失败:
File "/home/martin/beehive/apps/hello_world/models.py", line 50, in <module>
A(0)
TypeError: unbound method __new__() must be called with A instance as first argument (got ModelBase instance instead)
这个错误信息非常糟糕;我在调试器中也无法进入我的__new__
代码的框架。我尝试过super(A, cls)
、Top
、super(A, A)
,以及将这些组合在一起并把cls
作为第一个参数传给__new__
,但都没有成功。为什么会这么难呢?我是不是必须搞懂Django的元类才能解决这个问题,还是有更好的方法可以实现我的目标?
5 个回答
好的,这个可以用:https://gist.github.com/348872
这里面有点复杂的地方。
class A(Top):
pass
def newA(cls, *args, **kwargs):
# [all that code you wrote for A.__new__]
A.__new__ = staticmethod(newA)
关于Python是怎么处理__new__
的,我可能不是特别明白,但大概意思是这样的:django的ModelBase
元类会创建一个新的类对象,而不是使用传入的那个__new__
;我们叫这个新对象A_prime
。然后它会把你在A
类定义中写的所有属性都放到A_prime
上,但__new__
没有正确地重新绑定。
所以当你执行A(1)
的时候,其实A
就是A_prime
,Python会调用<A.__new__>(A_prime, 1)
,这就不对上了,结果就出错了。
解决办法是要在定义A_prime
之后再定义你的__new__
。
这可能是django.db.models.base.ModelBase.add_to_class
里的一个bug,也可能是Python本身的bug,我不太清楚。
我之前说“这个可以用”是指在当前Django的SVN版本中,这个在最简单的对象构造测试中是有效的。我不知道它在作为模型时是否真的有效,或者在QuerySet中是否有用。如果你真的在生产代码中使用这个,我会在pdxpython的一个公开演讲中提到它,让大家嘲笑你,直到你请我们吃无麸质的披萨。
你可能需要其中一个:
http://code.google.com/p/django-polymorphic-models/
https://github.com/bconstantin/django_polymorphic
不过要注意,这里有一些缺点,比如会增加额外的查询。
简单来说,你想做的是在查询一个共同的父类时,返回不同的子类。也就是说,你想要的是具体的子类。可以参考这个代码片段来找到解决方案:http://www.djangosnippets.org/snippets/1034/
另外,记得查看一下Django的内容类型框架的文档:http://docs.djangoproject.com/en/dev/ref/contrib/contenttypes/ 一开始可能会有点让人困惑,但内容类型可以帮助你解决在使用Django的ORM时,遇到的与非抽象基类相关的额外问题。