Obtenir tous les objets de modèle Django associés

88

Comment puis-je obtenir une liste de tous les objets de modèle qui ont une clé étrangère pointant vers un objet? (Quelque chose comme la page de confirmation de suppression dans l'admin Django avant DELETE CASCADE).

J'essaie de trouver un moyen générique de fusionner des objets en double dans la base de données. Fondamentalement, je veux que tous les objets qui ont des points ForeignKeys vers l'objet "B" soient mis à jour pour pointer vers l'objet "A" afin que je puisse ensuite supprimer "B" sans rien perdre d'important.

Merci de votre aide!

erikcw
la source
1
Cet extrait de Django vaut vraiment le détour!
Nick Merrill
J'essaye de mettre en œuvre exactement la même chose moi-même. Seriez-vous prêt à partager votre solution? en particulier comment setl'objet lié à pointer vers le A?
eugene

Réponses:

84

Django <= 1,7

Cela vous donne les noms de propriété pour tous les objets associés:

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

Vous pouvez ensuite utiliser quelque chose comme ceci pour obtenir tous les objets associés:

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

J'ai passé un certain temps à essayer de comprendre cela afin de pouvoir implémenter une sorte de "motif d'observateur" sur l'un de mes modèles. J'espère que c'est utile.

Django 1.8+

Utilisez _meta.get_fields(): https://docs.djangoproject.com/en/1.10/ref/models/meta/#django.db.models.options.Options.get_fields (voir aussi l'inverse dans la _get_fields()source)

vole
la source
7
La all()pièce échouera sur un OneToOneField. Vous devez le détecter d'une manière ou d'une autre.
augustomen
2
Il n'affiche pas les connexions ManyToMany et est obsolète. Dans Django 1.8+, il est recommandé de le remplacer par _meta.get_fields(): docs.djangoproject.com/en/1.10/ref/models/meta/… (voir aussi reversedans la _get_fields()source)
int_ua
1
Merci pour la modification @int_ua! Je suis surpris que cette solution de contournement soit restée compatible avec Django tant qu'elle l'a fait.
vole
4
Suite à la _meta.get_fields()pointe, cela faisait partie de la solution pour moi: links = [field.get_accessor_name() for field in obj._meta.get_fields() if issubclass(type(field), ForeignObjectRel)](donné from django.db.models.fields.related import ForeignObjectRel)
driftcatcher
20

@digitalPBK était proche ... voici probablement ce que vous recherchez en utilisant les éléments intégrés de Django qui sont utilisés dans l'administrateur de Django pour afficher les objets associés lors de la suppression

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)

cela vous permet de créer ce que l'administrateur Django affiche - les objets associés à supprimer.

IMFletcher
la source
FWICT cela ne fonctionne pas tout à fait correctement. Il semble suivre des relations qui n'impliquent pas les critères de suppression, même si je ne sais pas pourquoi.
Catskul
1
Est-ce moi ou est-ce que Collector(importé sur la ligne 1) n'est pas utilisé?
djvg
7

Essayez ceci.

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
la source
N'oubliez pas ManyToMany aussi
theannouncer
6

Voici ce que django utilise pour obtenir tous les objets associés

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

print collector.data
digitalPBK
la source
1
ne semble pas en cascade. Je n'ai pas fait suffisamment de tests autour de celui-ci pour connaître toutes les différences, mais voyez ma réponse pour celle en cascade.
IMFletcher
6

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

Vous pouvez ensuite utiliser quelque chose comme ceci pour obtenir tous les objets associés:

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

À partir de la documentation officielle de Django 1.10:

MyModel._meta.get_all_related_objects () devient:

[
    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
]

Donc, en prenant l'exemple approuvé, nous utiliserions:

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
la source
Juste pour corriger un peu votre réponse 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()

Fonctionne pour les ensembles associés, mais pas pour ForeignKeys. Puisque les RelatedManagers sont créés dynamiquement, il est plus facile de regarder le nom de la classe que de faire une isinstance

objOrMgr = getattr(a, link)
 if objOrMgr.__class__.__name__ ==  'RelatedManager':
      objects = objOrMgr.all()
 else:
      objects = [ objOrMgr ]
 for object in objects:
      # Do Stuff
Kai
la source
4

Django 1.9
get_all_related_objects () est obsolète

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

Remarque: RemovedInDjango110Warning: 'get_all_related_objects est une API non officielle qui est obsolète. Vous pourrez peut-être le remplacer par 'get_fields ()'

Slipstream
la source
get_fields ne renvoie pas les objets associés.
iankit
Il renvoie des connexions inverses, même sur Django 1.8, voir docs.djangoproject.com/en/1.8/_modules/django/db/models/options/…
int_ua
2

Voici une autre façon d'obtenir une liste de champs (noms uniquement) dans les modèles associés.

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
la source
2

Malheureusement, user._meta.get_fields () ne retourne que les relations accessibles depuis l'utilisateur, cependant, vous pouvez avoir un objet lié, qui utilise related_name = '+'. Dans ce cas, la relation ne serait pas retournée par user._meta.get_fields (). Par conséquent, si vous avez besoin d'un moyen générique et robuste de fusionner des objets, je vous suggère d'utiliser le collecteur mentionné ci-dessus.

Jakub QB Dorňák
la source