How can I get a list of all the model objects that have a ForeignKey pointing to an object? (Something like the delete confirmation page in the Django admin before DELETE CASCADE).
I'm trying to come up with a generic way of merging duplicate objects in the database. Basically I want all of the objects that have ForeignKeys points to object "B" to be updated to point to object "A" so I can then delete "B" without losing anything important.
Thanks for your help!
29 Answers
Django <= 1.7
This gives you the property names for all related objects:
links = [rel.get_accessor_name() for rel in a._meta.get_all_related_objects()] You can then use something like this to get all related objects:
for link in links: objects = getattr(a, link).all() for object in objects: # do something with related object instance I spent a while trying to figure this out so I could implement a kind of "Observer Pattern" on one of my models. Hope it's helpful.
Django 1.8+
Use _meta.get_fields(): (see reverse in the _get_fields() source also)
@digitalPBK was close... here is probably what you are looking for using Django's built-in stuff that is used in Django admin for displaying related objects during deletion
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) this allows you to create what the Django admin displays - the related objects to be deleted.
2Give this a try.
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] 1The following is what django uses to get all related objects
from django.db.models.deletion import Collector collector = Collector(using="default") collector.collect([a]) print collector.data 1links = [rel.get_accessor_name() for rel in a._meta.get_all_related_objects()]
You can then use something like this to get all related objects:
for link in links: objects = getattr(a, link.name).all() for object in objects: # do something with related object instance From Django 1.10 offical docs:
MyModel._meta.get_all_related_objects() becomes:
[ 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 ] So by taking the approved example we would use:
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 1for link in links: objects = getattr(a, link).all() Works for related sets, but not for ForeignKeys. Since RelatedManagers are created dynamically, looking at the class name is easier than doing an isinstance()
objOrMgr = getattr(a, link) if objOrMgr.__class__.__name__ == 'RelatedManager': objects = objOrMgr.all() else: objects = [ objOrMgr ] for object in objects: # Do Stuff Django 1.9
get_all_related_objects() has been deprecated
#Example: user = User.objects.get(id=1) print(user._meta.get_fields()) Note: RemovedInDjango110Warning: 'get_all_related_objects is an unofficial API that has been deprecated. You may be able to replace it with 'get_fields()'
2Unfortunately, user._meta.get_fields() returns only relations accessible from user, however, you may have some related object, which uses related_name='+'. In such case, the relation would not be returned by user._meta.get_fields(). Therefore, if You need generic and robust way to merge objects, I'd suggest to use the Collector mentioned above.
Here is another way to get a list of fields (names only) in related models.
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