Django subclass models with inline queries

As in this question , except that I want to have queries returning a mixed body of objects:

>>> Product.objects.all() [<SimpleProduct: ...>, <OtherProduct: ...>, <BlueProduct: ...>, ...] 

I realized that I can’t just set Product.Meta.abstract to true or otherwise just OR along with requests for different objects. Ok, but these are all subclasses of the general class, so if I leave my superclass as non-abstract, I should be happy as long as I can get his manager to return objects of the corresponding class. The request code in django does its thing and simply calls Product () calls. It sounds simple enough, except that it explodes when I redefine Product.__new__ , I guess because of __metaclass__ in Model ... Here is a non-jango code that behaves very well as I want it:

 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> 

But this fails if I inherit from django.db.models.Model instead of 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) 

Which one is a crappy return line; I also cannot enter the frame of my __new__ code in the debugger. I tried super(A, cls) , Top , super(A, A) in different ways, and all of the above, combined with passing cls as the first argument __new__ all to no avail. Why does it hit me so hard? Do I have to define django metaclasses to be able to fix this, or is there a better way to achieve my goals?

+7
python metaclass django django-models django-queryset
source share
5 answers

Basically what you are trying to do is return different child classes when querying a common base class. That is: you want sheet classes. Check this snippet for a solution: http://www.djangosnippets.org/snippets/1034/

Also be sure to check out the docs in the Django Contenttypes framework: http://docs.djangoproject.com/en/dev/ref/contrib/contenttypes/ At first, this can be a bit confusing, but Contenttypes will solve additional problems that you are likely to will have to face when using non-abstract base classes with Django ORM.

+4
source share

You want one of them:

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

There are downsides, namely additional requests.

+2
source share

Ok, this works: https://gist.github.com/348872

It was difficult.

 class A(Top): pass def newA(cls, *args, **kwargs): # [all that code you wrote for A.__new__] A.__new__ = staticmethod(newA) 

Now there is something about how Python binds __new__ , which I may not quite understand, but the point is: django ModelBase metaclass creates a new class object, and does not use the one that was passed in to its __new__ ; name it A_prime . It then saves all the attributes that you had in the class definition for A to A_prime , but __new__ does not get the correct binding.

Then when you evaluate A(1) , A is actually A_prime here, python calls A_prime <A.__new__>(A_prime, 1) , which does not match, and it explodes.

So, the solution is to determine your __new__ after A_prime been defined.

Perhaps this is a bug in django.db.models.base.ModelBase.add_to_class , maybe this is a bug in Python, I don't know.

Now that I said “it works” before, I meant it works in isolation with a minimal object build script in the current version of SVN Django . I don't know if it really works as a model or is useful in a QuerySet. If you really use this in production code, I will make a lightning fast public conversation from it for pdxpython and ask you to mock you until you buy us all the gluten-free pizza.

+1
source share

Just apply the @static method before the __new__ method.

 @staticmethod def __new__(cls, *args, **kwargs): print args, kwargs return super(License, cls).__new__(cls, *args, **kwargs) 
+1
source share
+1
source share

All Articles