improved generic foreign key manager

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
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

More like this

  1. Improved generic foreign key manager 2 by Nomalz 2 years, 6 months ago
  2. Manager method for limiting GenericForeignKey queries by zerok 3 years, 9 months ago
  3. Replace model select widget in admin with a readonly link to the related object by ekellner 3 years, 8 months ago
  4. Decoupling models with cross-database relations by zvikico 1 year, 3 months ago
  5. autocompleter with database query by bbolli 4 years, 10 months ago

Comments

taddr (on January 22, 2009):

This definitely works. Thank you Carl!

#

Nomalz (on October 25, 2009):

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 !

#

(Forgotten your password?)