Wenn Sie in Django eine übergeordnete Klasse und mehrere untergeordnete Klassen haben, die davon erben, greifen Sie normalerweise über parentclass.childclass1_set oder parentclass.childclass2_set auf ein untergeordnetes Element zu. Was ist, wenn ich den Namen der gewünschten untergeordneten Klasse nicht kenne?
Gibt es eine Möglichkeit, die zugehörigen Objekte in die Richtung parent-> child zu bringen, ohne den Namen der untergeordneten Klasse zu kennen?
python
django
many-to-many
Gabriel Hurley
quelle
quelle
Antworten:
( Update : Für Django 1.2 und höher, das select_related-Abfragen über umgekehrte OneToOneField-Beziehungen (und damit über Vererbungshierarchien hinweg) folgen kann, steht eine bessere Technik zur Verfügung, für die das hinzugefügte
real_type
Feld im übergeordneten Modell nicht erforderlich ist . Sie ist als InheritanceManager im verfügbar django-model-utils- Projekt.)Die übliche Methode hierfür ist das Hinzufügen eines ForeignKey zu ContentType im übergeordneten Modell, in dem der Inhaltstyp der richtigen "Blatt" -Klasse gespeichert wird. Ohne dies müssen Sie möglicherweise eine ganze Reihe von Abfragen für untergeordnete Tabellen durchführen, um die Instanz zu finden, je nachdem, wie groß Ihr Vererbungsbaum ist. So habe ich es in einem Projekt gemacht:
from django.contrib.contenttypes.models import ContentType from django.db import models class InheritanceCastModel(models.Model): """ An abstract base class that provides a ``real_type`` FK to ContentType. For use in trees of inherited models, to be able to downcast parent instances to their child types. """ real_type = models.ForeignKey(ContentType, editable=False) def save(self, *args, **kwargs): if self._state.adding: self.real_type = self._get_real_type() super(InheritanceCastModel, self).save(*args, **kwargs) def _get_real_type(self): return ContentType.objects.get_for_model(type(self)) def cast(self): return self.real_type.get_object_for_this_type(pk=self.pk) class Meta: abstract = True
Dies wird als abstrakte Basisklasse implementiert, um sie wiederverwendbar zu machen. Sie können diese Methoden und die FK auch direkt in die übergeordnete Klasse in Ihrer speziellen Vererbungshierarchie einfügen.
Diese Lösung funktioniert nicht, wenn Sie das übergeordnete Modell nicht ändern können. In diesem Fall stecken Sie ziemlich fest und überprüfen alle Unterklassen manuell.
quelle
self.child_object()
dasreal_type
Feld (das im obigen Code alscast()
Methode enthalten ist) verwenden, und es wird nur eine Abfrage für eine Instanz benötigt. Wenn Sie jedoch ein Abfrageset voller Instanzen haben, werden daraus N Abfragen. Die einzige Möglichkeit, die Unterklassendaten für eine ganze Abfragemenge von Objekten in einer einzelnen Abfrage abzurufen, besteht darin, die Joins zu verwenden, die InheritanceManager ausführt.In Python können Sie bei einer ("neuen") Klasse X ihre (direkten) Unterklassen mit abrufen
X.__subclasses__()
, die eine Liste von Klassenobjekten zurückgeben. (Wenn Sie "weitere Nachkommen" möchten, müssen Sie auch__subclasses__
jede der direkten Unterklassen usw. usw. aufrufen. Wenn Sie Hilfe benötigen, um dies in Python effektiv zu tun, fragen Sie einfach!).Sobald Sie eine interessierende untergeordnete Klasse identifiziert haben (vielleicht alle, wenn Sie Instanzen aller untergeordneten Unterklassen usw. möchten),
getattr(parentclass,'%s_set' % childclass.__name__)
sollte dies helfen (wenn der Name der untergeordneten Klasse lautet'foo'
, ist dies wie der Zugriffparentclass.foo_set
- nicht mehr und nicht weniger ). Wenn Sie weitere Erläuterungen oder Beispiele benötigen, fragen Sie bitte!quelle
Carls Lösung ist gut. Hier ist eine Möglichkeit, dies manuell zu tun, wenn mehrere verwandte untergeordnete Klassen vorhanden sind:
def get_children(self): rel_objs = self._meta.get_all_related_objects() return [getattr(self, x.get_accessor_name()) for x in rel_objs if x.model != type(self)]
Es verwendet eine Funktion aus _meta, die nicht garantiert stabil ist, wenn sich Django entwickelt, aber es macht den Trick und kann bei Bedarf im laufenden Betrieb verwendet werden.
quelle
Es stellte sich heraus, dass ich wirklich Folgendes brauchte:
Modellvererbung mit Inhaltstyp und vererbungsbewusstem Manager
Das hat bei mir perfekt funktioniert. Vielen Dank an alle anderen. Ich habe viel gelernt, indem ich nur deine Antworten gelesen habe!
quelle
Sie können dafür django-polymorph verwenden .
Damit können abgeleitete Klassen automatisch auf ihren tatsächlichen Typ zurückgesetzt werden. Es bietet auch Django-Administratorunterstützung, effizientere SQL-Abfragebehandlung sowie Unterstützung für Proxy-Modelle, Inlines und Formset.
Das Grundprinzip scheint viele Male neu erfunden zu werden (einschließlich Bachstelzen
.specific
oder der in diesem Beitrag beschriebenen Beispiele). Es erfordert jedoch mehr Aufwand, um sicherzustellen, dass es nicht zu einem N-Abfrageproblem kommt, oder sich gut in den Administrator, Formsets / Inlines oder Apps von Drittanbietern zu integrieren.quelle
Hier ist meine Lösung, die wieder verwendet wird und
_meta
daher nicht garantiert stabil ist.class Animal(models.model): name = models.CharField() number_legs = models.IntegerField() ... def get_child_animal(self): child_animal = None for r in self._meta.get_all_related_objects(): if r.field.name == 'animal_ptr': child_animal = getattr(self, r.get_accessor_name()) if not child_animal: raise Exception("No subclass, you shouldn't create Animals directly") return child_animal class Dog(Animal): ... for a in Animal.objects.all(): a.get_child_animal() # returns the dog (or whatever) instance
quelle
Sie können dies erreichen, indem Sie nach allen Feldern im übergeordneten Element suchen, die eine Instanz von django.db.models.fields.related.RelatedManager sind. Aus Ihrem Beispiel geht hervor, dass die untergeordneten Klassen, über die Sie sprechen, keine Unterklassen sind. Richtig?
quelle
Einen alternativen Ansatz mit Proxys finden Sie in diesem Blogbeitrag . Wie die anderen Lösungen hat es seine Vorteile und Verbindlichkeiten, die am Ende des Beitrags sehr gut formuliert sind.
quelle