Skip to content

Commit

Permalink
Adding a GFKOptimizedQuerySet that will allow efficient lookups across
Browse files Browse the repository at this point in the history
the GFK and used transparently by the RelatedObjectsDescriptor if the
to_object is a GFK (thanks Alex for the ideas on implementation)
  • Loading branch information
coleifer committed Feb 17, 2011
1 parent 46720f5 commit ab92043
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 2 deletions.
13 changes: 12 additions & 1 deletion genericm2m/genericm2m_tests/tests.py
@@ -1,6 +1,6 @@
from django.test import TestCase

from genericm2m.models import RelatedObject, RelatedObjectsDescriptor
from genericm2m.models import RelatedObject, RelatedObjectsDescriptor, GFKOptimizedQuerySet
from genericm2m.genericm2m_tests.models import Food, Beverage, Person, RelatedBeverage, Boring


Expand Down Expand Up @@ -216,3 +216,14 @@ def test_custom_model_level(self):
(self.pizza, self.beer),
(self.pizza, self.soda),
), 'food', 'beverage')

def test_generic_traversal(self):
self.pizza.related.connect(self.beer)
self.pizza.related.connect(self.soda)
self.pizza.related.connect(self.mario)

related = self.pizza.related.all()
self.assertEqual(type(related), GFKOptimizedQuerySet)

objects = related.generic_objects()
self.assertEqual(objects, [self.mario, self.soda, self.beer])
40 changes: 39 additions & 1 deletion genericm2m/models.py
@@ -1,6 +1,40 @@
from django.contrib.contenttypes.generic import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.db import models
from django.db.models.query import QuerySet


class GFKOptimizedQuerySet(QuerySet):
def get_gfk(self):
for field in self.model._meta.virtual_fields:
if isinstance(field, GenericForeignKey):
return field

def generic_objects(self):
clone = self._clone()

ctypes_and_fks = {}

gfk_field = self.get_gfk()
ctype_field = '%s_id' % gfk_field.ct_field
fk_field = gfk_field.fk_field

for obj in clone:
ctype = ContentType.objects.get_for_id(getattr(obj, ctype_field))
obj_id = getattr(obj, fk_field)

ctypes_and_fks.setdefault(ctype, [])
ctypes_and_fks[ctype].append(obj_id)

gfk_objects = {}
for ctype, obj_ids in ctypes_and_fks.items():
gfk_objects[ctype.pk] = ctype.model_class()._default_manager.in_bulk(obj_ids)

obj_list = []
for obj in clone:
obj_list.append(gfk_objects[getattr(obj, ctype_field)][getattr(obj, fk_field)])

return obj_list


class RelatedObjectsDescriptor(object):
Expand Down Expand Up @@ -63,10 +97,14 @@ def delete_manager(self, instance):
def create_manager(self, instance, superclass):
rel_obj = self
core_filters = self.get_query_from(instance)
uses_gfk = self.is_gfk(self.to_field)

class RelatedManager(superclass):
def get_query_set(self):
return superclass.get_query_set(self).filter(**(core_filters))
if uses_gfk:
return GFKOptimizedQuerySet(self.model).filter(**(core_filters))
else:
return superclass.get_query_set(self).filter(**(core_filters))

def add(self, *objs):
for obj in objs:
Expand Down

0 comments on commit ab92043

Please sign in to comment.