Pobierz wszystkie powiązane obiekty modelu Django

88

Jak uzyskać listę wszystkich obiektów modelu, które mają klucz obcy wskazujący na obiekt? (Coś jak strona potwierdzenia usunięcia w panelu administracyjnym Django przed DELETE CASCADE).

Próbuję wymyślić ogólny sposób łączenia zduplikowanych obiektów w bazie danych. Zasadniczo chcę, aby wszystkie obiekty, które mają ForeignKeys wskazujące na obiekt „B”, zostały zaktualizowane tak, aby wskazywały na obiekt „A”, dzięki czemu będę mógł usunąć „B” bez utraty niczego ważnego.

Dzięki za pomoc!

erikcw
źródło
1
Ten fragment Django jest zdecydowanie wart obejrzenia!
Nick Merrill,
Sam próbuję zaimplementować dokładnie to samo. Czy byłbyś skłonny podzielić się swoim rozwiązaniem? w szczególności w jaki sposób setpowiązany obiekt wskazywał na literę A?
Eugene

Odpowiedzi:

84

Django <= 1,7

To daje nazwy właściwości dla wszystkich powiązanych obiektów:

links = [rel.get_accessor_name() for rel in a._meta.get_all_related_objects()]

Następnie możesz użyć czegoś takiego, aby uzyskać wszystkie powiązane obiekty:

for link in links:
    objects = getattr(a, link).all()
    for object in objects:
        # do something with related object instance

Spędziłem trochę czasu próbując to rozgryźć, aby móc zaimplementować rodzaj „Wzorca Obserwatora” w jednym z moich modeli. Mam nadzieję, że to pomocne.

Django 1.8+

Użyj _meta.get_fields(): https://docs.djangoproject.com/en/1.10/ref/models/meta/#django.db.models.options.Options.get_fields (patrz również na odwrocie w _get_fields()źródle)

rabusie
źródło
7
all()Część zawiedzie na zasadzie OneToOneField. Musisz to jakoś wykryć.
augustomen
2
Nie pokazuje połączeń ManyToMany i został wycofany. W Django 1.8+ zaleca się zastąpienie go _meta.get_fields(): docs.djangoproject.com/en/1.10/ref/models/meta/… (zobacz także reversew _get_fields()źródle)
int_ua
1
Dzięki za edycję @int_ua! Jestem zaskoczony, że to obejście pozostało kompatybilne z Django tak długo, jak było.
rabuje
4
Idąc za _meta.get_fields()wskazówką, było to dla mnie częścią rozwiązania: links = [field.get_accessor_name() for field in obj._meta.get_fields() if issubclass(type(field), ForeignObjectRel)](podane from django.db.models.fields.related import ForeignObjectRel)
driftcatcher
20

@digitalPBK był blisko ... oto prawdopodobnie to, czego szukasz, używając wbudowanych rzeczy Django, które są używane w panelu administracyjnym Django do wyświetlania powiązanych obiektów podczas usuwania

from django.contrib.admin.utils import NestedObjects
collector = NestedObjects(using="default") #database name
collector.collect([objective]) #list of objects. single one won't do
print(collector.data)

pozwala to na tworzenie tego, co wyświetla administrator Django - powiązanych obiektów do usunięcia.

IMFletcher
źródło
FWICT to nie działa całkiem poprawnie. Wydaje się, że podąża za relacjami, które nie implikują kryteriów usunięcia, chociaż nie jestem pewien dlaczego.
Catskul
1
Czy to ja, czy Collector(importowany w linii 1) nie jest używany?
djvg,
7

Spróbuj.

class A(models.Model):
    def get_foreign_fields(self):
      return [getattr(self, f.name) for f in self._meta.fields if type(f) == models.fields.related.ForeignKey]
Buckley
źródło
Nie zapomnij też o ManyToMany
theannouncer
6

Poniżej opisano, czego używa django do pobrania wszystkich powiązanych obiektów

from django.db.models.deletion import Collector
collector = Collector(using="default")
collector.collect([a])

print collector.data
digitalPBK
źródło
1
nie wydaje się kaskadować. nie wykonałem wystarczająco dużo testów wokół tego konkretnego, aby poznać pełne różnice, ale zobacz moją odpowiedź na kaskadową.
IMFletcher
6

links = [rel.get_accessor_name() for rel in a._meta.get_all_related_objects()]

Następnie możesz użyć czegoś takiego, aby uzyskać wszystkie powiązane obiekty:

for link in links:
    objects = getattr(a, link.name).all()
    for object in objects:
        # do something with related object instance

Z oficjalnej dokumentacji Django 1.10:

MyModel._meta.get_all_related_objects () staje się:

[
    f for f in MyModel._meta.get_fields()
    if (f.one_to_many or f.one_to_one)
    and f.auto_created and not f.concrete
]

Więc biorąc zatwierdzony przykład, użylibyśmy:

links = [
            f for f in MyModel._meta.get_fields()
            if (f.one_to_many or f.one_to_one)
            and f.auto_created and not f.concrete
        ]

for link in links:
    objects = getattr(a, link.name).all()
    for object in objects:
        # do something with related object instance
Bojan Jovanovic
źródło
Tylko trochę popraw swoją odpowiedź python for link in links: objects = getattr(a, link.name).all() for object in objects:
Nam Ngo
5
for link in links:
    objects = getattr(a, link).all()

Działa dla powiązanych zestawów, ale nie dla kluczy obcych. Ponieważ menedżerowie pokrewni są tworzeni dynamicznie, patrzenie na nazwę klasy jest łatwiejsze niż wykonanie isinstance ()

objOrMgr = getattr(a, link)
 if objOrMgr.__class__.__name__ ==  'RelatedManager':
      objects = objOrMgr.all()
 else:
      objects = [ objOrMgr ]
 for object in objects:
      # Do Stuff
Kai
źródło
4

Django 1.9
get_all_related_objects () jest przestarzałe

#Example: 
user = User.objects.get(id=1)
print(user._meta.get_fields())

Uwaga: RemovedInDjango110Warning: 'get_all_related_objects to nieoficjalny interfejs API, który został wycofany. Możesz zastąpić go „get_fields ()”

Strumień
źródło
get_fields nie zwraca powiązanych obiektów.
iankit
Zwraca odwrotne połączenia, nawet w Django 1.8, patrz docs.djangoproject.com/en/1.8/_modules/django/db/models/options/ ...
int_ua
2

Oto inny sposób uzyskania listy pól (tylko nazw) w powiązanych modelach.

def get_related_model_fields(model):
    fields=[]
    related_models = model._meta.get_all_related_objects()
    for r in related_models:
        fields.extend(r.opts.get_all_field_names())
    return fields
JessieinAg
źródło
2

Niestety, user._meta.get_fields () zwraca tylko relacje dostępne dla użytkownika, jednak możesz mieć jakiś powiązany obiekt, który używa related_name = '+'. W takim przypadku relacja nie byłaby zwracana przez user._meta.get_fields (). Dlatego jeśli potrzebujesz uniwersalnego i niezawodnego sposobu łączenia obiektów, sugerowałbym użycie wspomnianego powyżej Kolektora.

Jakub QB Dorňák
źródło