Obtenha todos os objetos de modelo Django relacionados

88

Como posso obter uma lista de todos os objetos de modelo que possuem uma ForeignKey apontando para um objeto? (Algo como a página de confirmação de exclusão no administrador do Django antes de DELETE CASCADE).

Estou tentando encontrar uma maneira genérica de mesclar objetos duplicados no banco de dados. Basicamente, quero que todos os objetos que têm pontos ForeignKeys para o objeto "B" sejam atualizados para apontar para o objeto "A" para que eu possa excluir "B" sem perder nada importante.

Obrigado pela ajuda!

Erikcw
fonte
1
Este snippet do Django definitivamente vale a pena conferir!
Nick Merrill
Estou tentando implementar exatamente a mesma coisa sozinho. Você estaria disposto a compartilhar sua solução? especialmente como seto objeto relacionado apontou para o A?
Eugene

Respostas:

84

Django <= 1,7

Isso fornece os nomes das propriedades para todos os objetos relacionados:

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

Você pode então usar algo assim para obter todos os objetos relacionados:

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

Passei um tempo tentando descobrir isso para que pudesse implementar uma espécie de "Padrão do Observador" em um dos meus modelos. Espero que seja útil.

Django 1.8+

Use _meta.get_fields(): https://docs.djangoproject.com/en/1.10/ref/models/meta/#django.db.models.options.Options.get_fields (veja o reverso na _get_fields()fonte também)

roubos
fonte
7
A all()peça falhará em a OneToOneField. Você tem que detectá-lo de alguma forma.
agosto
2
Ele não mostra conexões ManyToMany e foi descontinuado. No Django 1.8+, é recomendado ser substituído por _meta.get_fields(): docs.djangoproject.com/en/1.10/ref/models/meta/… (veja reversena _get_fields()fonte também)
int_ua
1
Obrigado pela edição @int_ua! Estou surpreso que esta solução alternativa permaneceu compatível com Django por tanto tempo.
rouba
4
Seguindo na _meta.get_fields()dica, isso foi parte da solução para mim: links = [field.get_accessor_name() for field in obj._meta.get_fields() if issubclass(type(field), ForeignObjectRel)](dado from django.db.models.fields.related import ForeignObjectRel)
driftcatcher
20

@digitalPBK estava perto ... aqui está provavelmente o que você está procurando usando o material embutido do Django que é usado no Django admin para exibir objetos relacionados durante a exclusão

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)

isto permite que você crie o que o administrador do Django exibe - os objetos relacionados a serem excluídos.

IMFletcher
fonte
FWICT isso não funciona corretamente. Parece seguir relacionamentos que não implicam nos critérios de exclusão, embora eu não tenha certeza do porquê.
Catskul
1
Sou eu ou o Collector(importado na linha 1) não é usado?
djvg
7

Experimente.

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
fonte
Não se esqueça de ManyToMany também
theannouncer
6

O seguinte é o que django usa para obter todos os objetos relacionados

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

print collector.data
digitalPBK
fonte
1
não parece em cascata. não fiz testes suficientes em torno deste em particular para saber todas as diferenças, mas veja minha resposta para o em cascata.
IMFletcher de
6

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

Você pode então usar algo assim para obter todos os objetos relacionados:

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

Dos documentos oficiais do Django 1.10:

MyModel._meta.get_all_related_objects () torna-se:

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

Então, pegando o exemplo aprovado, 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
fonte
Só para corrigir um pouco a sua resposta 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, mas não para ForeignKeys. Visto que RelatedManagers são criados dinamicamente, olhar para o nome da classe é mais fácil do que fazer um isinstance ()

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

Django 1.9
get_all_related_objects () tornou-se obsoleto

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

Nota: RemovedInDjango110Warning: 'get_all_related_objects é uma API não oficial que se tornou obsoleta. Você pode substituí-lo por 'get_fields ()'

Slipstream
fonte
get_fields não retorna objetos relacionados.
iankit
Ele retorna conexões reversas, mesmo no Django 1.8, consulte docs.djangoproject.com/en/1.8/_modules/django/db/models/options/…
int_ua
2

Aqui está outra maneira de obter uma lista de campos (apenas nomes) em 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
fonte
2

Infelizmente, user._meta.get_fields () retorna apenas relações acessíveis do usuário, entretanto, você pode ter algum objeto relacionado, que usa related_name = '+'. Nesse caso, a relação não seria retornada por user._meta.get_fields (). Portanto, se você precisa de uma maneira genérica e robusta de mesclar objetos, sugiro usar o Coletor mencionado acima.

Jakub QB Dorňák
fonte