from django.db.models.query import QuerySet
from django.db.models import Manager
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.generic import GenericForeignKey
class GFKManager(Manager):
"""
A manager that returns a GFKQuerySet instead of a regular QuerySet.
"""
def get_query_set(self):
return GFKQuerySet(self.model)
class GFKQuerySet(QuerySet):
"""
A QuerySet with a fetch_generic_relations() method to bulk fetch
all generic related items. Similar to select_related(), but for
generic foreign keys.
Based on http://www.djangosnippets.org/snippets/984/
"""
def fetch_generic_relations(self):
qs = self._clone()
gfk_fields = [g for g in self.model._meta.virtual_fields
if isinstance(g, GenericForeignKey)]
ct_map = {}
item_map = {}
for item in qs:
for gfk in gfk_fields:
ct_id_field = self.model._meta.get_field(gfk.ct_field).column
ct_map.setdefault(
(ct_id_field, getattr(item, ct_id_field)), {}
)[getattr(item, gfk.fk_field)] = (gfk.name, item.id)
item_map[item.id] = item
for (ct_id_field, ct_id), items_ in ct_map.items():
ct = ContentType.objects.get_for_id(ct_id)
for o in ct.model_class().objects.select_related().filter(
id__in=items_.keys()).all():
(gfk_name, item_id) = items_[o.id]
setattr(item_map[item_id], gfk_name, o)
return qs
Comments
This definitely works. Thank you Carl!
#
I posted an improved version of your snippet.
Problem I had with your version : if two or more items shares the same generic foreign object, then only the first one is cached. Next ones generates new unwanted SQL queries. I solved this problem by putting all the needed foreign objects in a temporary data_map dictionary. Then, the objects are distributed to every items, so that if two items shares the same foreign object, it will only be fetched once.
Thanks for the initial work !
#