Obtener todos los objetos del modelo de Django relacionados

88

¿Cómo puedo obtener una lista de todos los objetos del modelo que tienen una ForeignKey apuntando a un objeto? (Algo así como la página de confirmación de eliminación en el administrador de Django antes de DELETE CASCADE).

Estoy tratando de encontrar una forma genérica de fusionar objetos duplicados en la base de datos. Básicamente, quiero que todos los objetos que tienen ForeignKeys apuntan al objeto "B" para que se actualicen para que apunten al objeto "A" para luego poder borrar "B" sin perder nada importante.

¡Gracias por tu ayuda!

erikcw
fuente
1
¡ Definitivamente vale la pena echarle un vistazo a este fragmento de Django !
Nick Merrill
Estoy tratando de implementar exactamente lo mismo yo mismo. ¿Estarías dispuesto a compartir tu solución? especialmente, ¿cómo apunta setel objeto relacionado a la A?
eugene

Respuestas:

84

Django <= 1.7

Esto le da los nombres de propiedad para todos los objetos relacionados:

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

Luego puede usar algo como esto para obtener todos los objetos relacionados:

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

Pasé un tiempo tratando de resolver esto para poder implementar una especie de "Patrón de observador" en uno de mis modelos. Espero que sea de ayuda.

Django 1.8+

Utilice _meta.get_fields(): https://docs.djangoproject.com/en/1.10/ref/models/meta/#django.db.models.options.Options.get_fields (vea el reverso en el_get_fields() fuente)

robos
fuente
7
La all()pieza fallará en a OneToOneField. Tienes que detectarlo de alguna manera.
augustomen
2
No muestra conexiones ManyToMany y está en desuso. En Django 1.8+ se recomienda reemplazarlo con_meta.get_fields() : docs.djangoproject.com/en/1.10/ref/models/meta/… (ver también reverseen la _get_fields()fuente)
int_ua
1
¡Gracias por editar @int_ua! Me sorprende que esta solución siga siendo compatible con Django tanto tiempo como lo hizo.
robos
4
Siguiendo el _meta.get_fields()consejo, esto fue parte de la solución para mí: links = [field.get_accessor_name() for field in obj._meta.get_fields() if issubclass(type(field), ForeignObjectRel)](dadofrom django.db.models.fields.related import ForeignObjectRel )
driftcatcher
20

@digitalPBK estuvo cerca ... esto es probablemente lo que está buscando usando las cosas integradas de Django que se usan en el administrador de Django para mostrar objetos relacionados durante la eliminación

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)

esto le permite crear lo que muestra el administrador de Django: los objetos relacionados que se eliminarán.

IMFletcher
fuente
FWICT esto no funciona del todo correctamente. Parece seguir relaciones que no implican los criterios de eliminación, aunque no estoy seguro de por qué.
Catskul
1
¿Soy yo o Collectorno se utiliza (importado en la línea 1)?
djvg
7

Pruébalo.

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
fuente
No te olvides de ManyToMany también
theannouncer
6

Lo siguiente es lo que usa django para obtener todos los objetos relacionados

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

print collector.data
digitalPBK
fuente
1
no parece en cascada. No he hecho suficientes pruebas en este particular para conocer las diferencias completas, pero vea mi respuesta para la cascada.
IMFletcher
6

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

Luego puede usar algo como esto para obtener todos los objetos relacionados:

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

De Django 1.10 documentos oficiales:

MyModel._meta.get_all_related_objects () se convierte en:

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

Entonces, tomando el ejemplo aprobado, usaríamos:

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
fuente
Solo para corregir un poco su respuesta 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()

Funciona para conjuntos relacionados, pero no para ForeignKeys. Dado que los RelatedManagers se crean dinámicamente, mirar el nombre de la clase es más fácil que hacer un isinstance ()

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

Django 1.9
get_all_related_objects () ha quedado obsoleto

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

Nota: RemovedInDjango110Warning: 'get_all_related_objects es una API no oficial que ha quedado obsoleta. Es posible que pueda reemplazarlo con 'get_fields ()'

Slipstream
fuente
get_fields no devuelve objetos relacionados.
iankit
Devuelve
int_ua
2

Aquí hay otra forma de obtener una lista de campos (solo nombres) en modelos relacionados.

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
fuente
2

Desafortunadamente, user._meta.get_fields () devuelve solo relaciones accesibles desde el usuario, sin embargo, puede tener algún objeto relacionado, que usa related_name = '+'. En tal caso, user._meta.get_fields () no devolverá la relación. Por lo tanto, si necesita una forma genérica y sólida de fusionar objetos, le sugiero que use el recopilador mencionado anteriormente.

Jakub QB Dorňák
fuente