W Django, gdy masz klasę nadrzędną i wiele klas potomnych, które ją dziedziczą, normalnie uzyskujesz dostęp do dziecka poprzez parentclass.childclass1_set lub parentclass.childclass2_set, ale co jeśli nie znam nazwy konkretnej klasy potomnej, której chcę?
Czy istnieje sposób na uzyskanie powiązanych obiektów w kierunku rodzic-> dziecko bez znajomości nazwy klasy podrzędnej?
python
django
many-to-many
Gabriel Hurley
źródło
źródło
Odpowiedzi:
( Aktualizacja : dla Django 1.2 i nowszych, które mogą podążać za zapytaniami select_related w odwrotnych relacjach OneToOneField (a tym samym w dół hierarchii dziedziczenia), dostępna jest lepsza technika, która nie wymaga dodanego
real_type
pola w modelu nadrzędnym. Jest dostępna jako InheritanceManager w projekt django-model-utils .)Zwykłym sposobem jest dodanie ForeignKey do ContentType w modelu Parent, który przechowuje typ zawartości odpowiedniej klasy „leaf”. Bez tego może być konieczne wykonanie wielu zapytań w tabelach potomnych, aby znaleźć instancję, w zależności od tego, jak duże jest twoje drzewo dziedziczenia. Oto, jak zrobiłem to w jednym projekcie:
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
Jest to zaimplementowane jako abstrakcyjna klasa bazowa, aby umożliwić wielokrotne użycie; możesz również umieścić te metody i FK bezpośrednio w klasie nadrzędnej w określonej hierarchii dziedziczenia.
To rozwiązanie nie zadziała, jeśli nie możesz zmodyfikować modelu nadrzędnego. W takim przypadku utkniesz, sprawdzając ręcznie wszystkie podklasy.
źródło
self.child_object()
używającreal_type
pola (czyli w powyższym kodzie jakocast()
metody), a to zajmie tylko jedno zapytanie dla jednej instancji. Ale jeśli masz zestaw zapytań pełen instancji, staje się to N zapytań. Jedynym sposobem uzyskania danych podklasy dla całego zestawu zapytań obiektów w pojedynczym zapytaniu jest użycie sprzężeń, które robi InheritanceManager.W Pythonie, mając („nowy styl”) klasę X, możesz pobrać jej (bezpośrednie) podklasy za pomocą
X.__subclasses__()
, która zwraca listę obiektów klas. (Jeśli chcesz „dalszych potomków”, musisz też zadzwonić__subclasses__
każdą z bezpośrednich podklas itp. Itd. - jeśli potrzebujesz pomocy, jak to zrobić skutecznie w Pythonie, po prostu zapytaj!).Gdy w jakiś sposób zidentyfikujesz interesującą Cię klasę podrzędną (może wszystkie, jeśli chcesz mieć instancje wszystkich podklas podrzędnych itp.),
getattr(parentclass,'%s_set' % childclass.__name__)
Powinno pomóc (jeśli nazwa klasy podrzędnej to'foo'
, jest to tak samo jak dostępparentclass.foo_set
- nie więcej, nie mniej ). Ponownie, jeśli potrzebujesz wyjaśnień lub przykładów, zapytaj!źródło
Rozwiązanie Carla jest dobre, oto jeden sposób, aby zrobić to ręcznie, jeśli istnieje wiele powiązanych klas podrzędnych:
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)]
Używa funkcji spoza _meta, która nie ma gwarancji, że będzie stabilna w miarę rozwoju django, ale spełnia swoje zadanie i może być używana w locie, jeśli zajdzie taka potrzeba.
źródło
Okazuje się, że naprawdę potrzebowałem tego:
Dziedziczenie modelu z typem zawartości i menedżerem uwzględniającym dziedziczenie
To zadziałało idealnie dla mnie. Jednak dzięki wszystkim innym. Wiele się nauczyłem, czytając Twoje odpowiedzi!
źródło
Możesz do tego użyć django-polymorphic .
Umożliwia automatyczne rzutowanie klas pochodnych z powrotem do ich rzeczywistego typu. Zapewnia również obsługę administracyjną Django, bardziej wydajną obsługę zapytań SQL oraz model proxy, obsługę inline i formset.
Wydaje się, że podstawowa zasada była wielokrotnie odkrywana (w tym pliszka
.specific
lub przykłady przedstawione w tym poście). Jednak potrzeba więcej wysiłku, aby upewnić się, że nie powoduje to problemu z N-query lub ładnie integruje się z administratorem, zestawami formularzy / inline lub aplikacjami innych firm.źródło
Oto moje rozwiązanie, ponownie używa,
_meta
więc nie ma gwarancji, że będzie stabilne.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
źródło
Możesz to osiągnąć, szukając wszystkich pól w obiekcie nadrzędnym, które są instancją django.db.models.fields.related.RelatedManager. Z twojego przykładu wynika, że klasy podrzędne, o których mówisz, nie są podklasami. Dobrze?
źródło
Alternatywne podejście z wykorzystaniem serwerów proxy można znaleźć w tym poście na blogu . Podobnie jak inne rozwiązania ma swoje zalety i zobowiązania, które bardzo dobrze ujęte są na końcu wpisu.
źródło