Skip to content

Instantly share code, notes, and snippets.

@EBNull
Created August 17, 2011 19:21
Show Gist options
  • Save EBNull/1152373 to your computer and use it in GitHub Desktop.
Save EBNull/1152373 to your computer and use it in GitHub Desktop.
Returns all foreign key relations to a django model and their accessors
#Some utilities to work with django models and foreign keys.
#Especially useful for finding (and writing out) code to illuminate information about relations.
#
#To try it out, run show_relation_accessors(model) in a python prompt, or to be more general, get_related_instance_ids_code(model)
#
#-2011 CBWhiz
#
#
#Usage sample:
# from django.db import transaction
#
# c = get_related_objects(HatedModel.objects.filter(id=24576))
# c.data.keys()
# [<class 'app1.models.HatedModel'>, <class 'app2.models.MissionCriticalModel'>]
#
# transaction.enter_transaction_management()
# exec get_replace_object_relations_code(HatedModel, 24576, 12740)
# c = get_related_objects(HatedModel.objects.filter(id=24576))
# c.data.keys()
# [<class 'app1.models.HatedModel'>]
# transaction.commit()
from django.db.models.deletion import Collector
def get_related_objects(qs, using='default'):
"""Returns a Collector instance whose 'data' attribute is a mapping of Models to a set of dependent instances of that model"""
c = Collector(using)
c.collect(qs)
return c
def get_relations(model, global_accessors=False, instance_filter=True):
"""Returns all (non gfk) foreign key relations to a django model and their accessors"""
for ro in model._meta.get_all_related_objects(include_hidden=True):
an = "%s.%s"%('%s', ro.get_accessor_name())
if an.endswith('+') or global_accessors:
#m2m
if instance_filter:
an = "%s.objects.filter(%s=%s)"%(ro.model.__name__, ro.field.name, '%s')
else:
an = "%s.objects.filter(%s__isnull=False)"%(ro.model.__name__, ro.field.name)
else:
an += '.all()'
yield (
model.__name__,
ro.field.rel.get_related_field().name,
ro.model._meta.app_label,
ro.model.__name__,
ro.field.name,
an
)
def get_relations_gfk(model, global_accessors=False, instance_filter=True):
for rf in model._meta.many_to_many:
if not rf.rel.through:
#rf is a GenericRelation (RelatedField)
ct = ContentType.objects.get_for_model(model).pk
if global_accessors:
an = "%s.objects.filter(%s=%s)"%(rf.rel.to.__name__, rf.content_type_field_name, ct)
if instance_filter:
an += ".filter(%s=%%s)"%(rf.object_id_field_name)
else:
an = "%%s.%s.all()"%(rf.name, )
yield (
model.__name__,
rf.m2m_target_field_name(),
rf.rel.to._meta.app_label,
rf.rel.to.__name__,
rf.object_id_field_name,
rf.content_type_field_name,
rf.name,
an,
)
def get_related_instance_ids_code(model):
r = list(get_relations(model, True, False)) + list(get_relations_gfk(model, True, False))
imports = ["from django.db.models.loading import get_model"]
qsgroups = []
for i in r:
imports.append("%s = get_model('%s', '%s')"%(i[3], i[2], i[3]))
qsgroups.append(i[-1] + '.values_list("%s", flat=True)'%(i[4]))
ld = locals()
pycode = '\n'.join(imports) + "\nqslist=[\n\t%s\n]"%(",\n\t".join(qsgroups))
return pycode
def get_unrelated_instance_qs(model):
exec get_related_instance_ids_code(model);
from django.db.models import Q
filt = []
for qs in qslist:
filt.append(~Q(pk__in=qs))
return model.objects.filter(reduce(lambda x, y: x & y, filt))
def get_replace_object_relations_code(model, oldid, newid):
"""Returns python code that will replace an object's relations to point to a different object (of the same type)"""
r = list(get_relations(model, True, False)) + list(get_relations_gfk(model, True, False))
imports = ["from django.db.models.loading import get_model"]
qsgroups = []
for i in r:
imports.append("%s = get_model('%s', '%s')"%(i[3], i[2], i[3]))
qsgroups.append(i[-1] + '.filter(%s=%s).update(%s=%s)'%(i[4], oldid, i[4], newid))
ld = locals()
pycode = '\n'.join(imports) + "\n\n%s"%("\n".join(qsgroups))
return pycode
def massreplace(model, idmap):
"""Replace all references to the instances of a model with other instances, given an {old_pk:new_pk} map"""
for v in idmap.values():
assert v not in idmap, "ID overlap"
for k, v in idmap.iteritems():
exec get_replace_object_relations_code(model, k, v)
c = get_related_objects(model.objects.filter(id=k))
assert len(c.data.keys()) == 1, "Replacment from %s to %s still left %s relations!"%(k, v, len(c.data.keys()))
def show_relation_accessors(model):
r = list(get_relations(model)) + list(get_relations_gfk(model))
for i in r:
print i[-1]%('instance')
@michaelschem
Copy link

thanks for the code!

I'm getting an error

with the get_relations function

Traceback (most recent call last):
  File "/opt/.pycharm_helpers/pydev/_pydevd_bundle/pydevd_exec2.py", line 3, in Exec
    exec(exp, global_vars, local_vars)
  File "<string>", line 1, in <module>
  File "<string>", line 3, in get_relations
AttributeError: 'Options' object has no attribute 'get_all_related_objects'

@boatcoder
Copy link

Seeing the same thing. Guess this was for a much older version of django

@ivarandre
Copy link

https://docs.djangoproject.com/en/2.1/ref/models/meta/

As part of the formalization of the Model._meta API (from the django.db.models.options.Options class), a number of methods and properties have been deprecated and will be removed in Django 1.10.

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
]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment