Django -Modelle mit unterklassigen Dschango mit integrierten Querysets
-
23-09-2019 - |
Frage
Wie in diese Frage, außer ich möchte in der Lage sein, QuerySets zu haben, die einen gemischten Teil von Objekten zurückgeben:
>>> Product.objects.all()
[<SimpleProduct: ...>, <OtherProduct: ...>, <BlueProduct: ...>, ...]
Ich habe herausgefunden, dass ich nicht einfach einstellen kann Product.Meta.abstract
zu wahr oder auf andere Weise nur oder zusammen Querysets unterschiedlicher Objekte. Gut, aber das sind alles Unterklassen einer gemeinsamen Klasse. Wenn ich also ihre Superklasse als nicht abstreckt lasse, sollte ich glücklich sein, solange ich seinen Manager dazu bringen kann, Objekte der richtigen Klasse zurückzugeben. Der Abfragecode in Django macht seine Sache und trifft nur an Product (). Klingt einfach genug, außer dass es aufbläst, wenn ich überschreibe Product.__new__
, Ich schätze wegen der __metaclass__
In Modell ... hier ist der Nicht-Django-Code, der sich so gut verhält, wie ich es will:
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>
Aber das scheitert, wenn ich von django.db.models.Model
Anstatt von 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)
Das ist eine besonders beschissene Backtrace; Ich kann nicht in den Rahmen meiner __new__
auch im Debugger Code. Ich habe unterschiedlich versucht super(A, cls)
, Top
, super(A, A)
, und all das oben in Kombination mit dem Pass cls
in als erstes Argument zu __new__
, alles ohne Erfolg. Warum tritt mich das so hart? Muss ich Djangos Metaklasse herausfinden, um dies zu beheben, oder gibt es eine bessere Möglichkeit, meine Ziele zu erreichen?
Lösung
Grundsätzlich versuchen Sie, die verschiedenen Kinderklassen zurückzugeben, während Sie eine gemeinsame Basisklasse abfragen. Das heißt: Sie wollen die Blattklassen. Überprüfen Sie diesen Ausschnitt für eine Lösung: http://www.djangosnippets.org/snippets/1034/
Schauen Sie sich auch die Dokumente im ContentTypes Framework von Django an: http://docs.djangoproject.com/en/dev/ref/contrib/contenttypes/ Es kann zunächst ein bisschen verwirrend sein, aber ContentTypes löst zusätzliche Probleme, mit denen Sie wahrscheinlich bei nicht abstrakten Basisklassen mit Djangos ORM konfrontiert sind.
Andere Tipps
Sie wollen eines davon:
http://code.google.com/p/django-polymorphic-models/
https://github.com/bconstantin/django_polymorphic
Es gibt Nachteile, nämlich zusätzliche Fragen.
Okay, das funktioniert: https://gist.github.com/348872
Das knifflige Stück war das.
class A(Top):
pass
def newA(cls, *args, **kwargs):
# [all that code you wrote for A.__new__]
A.__new__ = staticmethod(newA)
Jetzt gibt es etwas darüber, wie Python bindet __new__
Dass ich vielleicht nicht ganz verstehe, aber das Kern davon ist Folgendes: Django's ModelBase
Metaclass erstellt ein neues Klassenobjekt, anstatt das zu verwenden, der in seine übergeben wird __new__
; Nennen Sie das A_prime
. Dann bleibt alle Attribute, für die Sie in der Klassendefinition hatten A
auf zu A_prime
, aber __new__
wird nicht richtig wiederhergestellt.
Dann, wenn Sie bewerten A(1)
, A
ist eigentlich A_prime
Hier ruft Python an <A.__new__>(A_prime, 1)
, was nicht übereinstimmt und es explodiert.
Die Lösung besteht also darin, Ihre zu definieren __new__
nach A_prime
Wurde definiert.
Vielleicht ist das ein Fehler in django.db.models.base.ModelBase.add_to_class
, Vielleicht ist es ein Fehler in Python, ich weiß es nicht.
Nun, als ich früher "das funktioniert" sagte, meinte ich Dies funktioniert isoliert mit dem Fall minimaler Objektkonstruktionstest in der aktuellen SVN -Version von Django. Ich weiß nicht, ob es tatsächlich als Modell funktioniert oder in einem Queryset nützlich ist. Wenn Sie dies tatsächlich im Produktionscode verwenden, werde ich für PDXPython ein öffentliches Blitzgespräch daraus machen und sie verspotten lassen, bis Sie uns alle glutenfreie Pizza kaufen.
Halten Sie einfach @StaticMethod vor die __new__
Methode.
@staticmethod
def __new__(cls, *args, **kwargs):
print args, kwargs
return super(License, cls).__new__(cls, *args, **kwargs)
Ein weiterer Ansatz, den ich kürzlich gefunden habe: http://jeffelmore.org/2010/11/11/automatic-downcasting-ofherited-models-in-django/