集成查询集的Django模型子类化

7 投票
5 回答
2095 浏览
提问于 2025-04-15 21:01

就像在这个问题中提到的那样,我想要的是能够得到一个包含不同对象的查询集:

>>> 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)Topsuper(A, A),以及将这些组合在一起并把cls作为第一个参数传给__new__,但都没有成功。为什么会这么难呢?我是不是必须搞懂Django的元类才能解决这个问题,还是有更好的方法可以实现我的目标?

5 个回答

1

好的,这个可以用: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的一个公开演讲中提到它,让大家嘲笑你,直到你请我们吃无麸质的披萨。

2

你可能需要其中一个:

http://code.google.com/p/django-polymorphic-models/
https://github.com/bconstantin/django_polymorphic

不过要注意,这里有一些缺点,比如会增加额外的查询。

4

简单来说,你想做的是在查询一个共同的父类时,返回不同的子类。也就是说,你想要的是具体的子类。可以参考这个代码片段来找到解决方案:http://www.djangosnippets.org/snippets/1034/

另外,记得查看一下Django的内容类型框架的文档:http://docs.djangoproject.com/en/dev/ref/contrib/contenttypes/ 一开始可能会有点让人困惑,但内容类型可以帮助你解决在使用Django的ORM时,遇到的与非抽象基类相关的额外问题。

撰写回答