Login

Prefill ForeignKey caches

Author:
insin
Posted:
October 8, 2008
Language:
Python
Version:
1.0
Tags:
foreignkey cache prefill
Score:
2 (after 2 ratings)

Provides an efficient means of looking up multiple related model instances for a range of objects by pre-filling the cache attribute used by SingleRelatedObjectDescriptor with either complete model instances or a dict containing only specified fields, looking up all required data with a single query.

Example usage:

C:\django_projects\soclone>django-admin.py shell
>>> from soclone.models import Question
>>> from soclone.views import populate_fk_caches
>>> from django.db import connection
>>> from django.contrib.auth.models import User
>>> q = Question.objects.get(id=1)
>>> a = list(q.answers.all())
>>> connection.queries = []
>>> populate_fk_caches(User, (
...         ((q,), ('author', 'last_edited_by', 'closed_by')),
...         (a,    ('author', 'last_edited_by')),
...      ),
...      fields=('username', 'gravatar', 'reputation', 'gold', 'silver',
...              'bronze'))
>>> connection.queries
[{'time': '0.000', 'sql': u'SELECT "auth_user"."id", "auth_user"."username", "au
th_user"."gravatar", "auth_user"."reputation", "auth_user"."gold", "auth_user"."
silver", "auth_user"."bronze" FROM "auth_user" WHERE "auth_user"."id" IN (1, 2)'
}]
 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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
import itertools

def flatten(x):
    """flatten(sequence) -> list

    Returns a single, flat list which contains all elements retrieved
    from the sequence and all recursively contained sub-sequences
    (iterables).

    Examples:
    >>> [1, 2, [3,4], (5,6)]
    [1, 2, [3, 4], (5, 6)]
    >>> flatten([[[1,2,3], (42,None)], [4,5], [6], 7, MyVector(8,9,10)])
    [1, 2, 3, 42, None, 4, 5, 6, 7, 8, 9, 10]

    http://kogs-www.informatik.uni-hamburg.de/~meine/python_tricks"""

    result = []
    for el in x:
        if hasattr(el, '__iter__') and not isinstance(el, basestring):
            result.extend(flatten(el))
        else:
            result.append(el)
    return result

def populate_fk_caches(model, objects_to_populate, fields=None):
    """
    Populates caches for the given related Model in instances of objects
    which have a ForeignKey relationship to it, specified as a list of
    (object list, related attribute name list) two-tuples.

    If a list of field names is given, only the given fields will be
    looked up and related object caches will be populated with a dict of
    the specified fields. This list shouldn't include the primary key
    attribute for the related mode, as this can be determined from its
    Model's Options.

    If field names are not given, complete related objects will be
    retrieved and cached.
    """
    # Get all related object ids for the appropriate fields
    related_object_ids = []
    for objects, attrs in objects_to_populate:
        related_object_ids.append(tuple(tuple(getattr(obj, '%s_id' % attr)
                                              for attr in attrs)
                                  for obj in objects))

    # Get unique related object ids
    unique_ids = tuple(set(pk for pk in flatten(related_object_ids) if pk))

    # Retrieve related object details
    if fields is None:
        related_objects = model._default_manager.in_bulk(unique_ids)
    else:
        id_attribute = model._meta.pk.attname
        related_objects = dict((obj[id_attribute], obj)
            for obj in model._default_manager.filter(id__in=unique_ids).values(
                *itertools.chain((id_attribute,), fields)))

    # Fill related object caches
    for (objects, attrs), related_ids in itertools.izip(objects_to_populate,
                                                        related_object_ids):
        for obj, related_ids_for_obj in itertools.izip(objects,
                                                       related_ids):
            for attr, related_object in itertools.izip(attrs, (related_objects.get(pk, None)
                                                               for pk in related_ids_for_obj)):
                setattr(obj, '_%s_cache' % attr, related_object)

More like this

  1. PatchModelForm - A ModelForm subclass with the semantics of the PATCH HTTP method by gnrfan 2 years, 10 months ago
  2. SerializedObjectField by dominno 5 years ago
  3. A ModelChoiceField with support for title in options based on a field in the model by celopes 4 years, 7 months ago
  4. Filter on Multiple M2M Objects Simultaneously by axiak 7 years, 11 months ago
  5. UsernameField (for clean error messages) by davepeck 5 years, 7 months ago

Comments

anentropic (on August 24, 2011):

In recent versions of Django the "_%s_cache" string shouldn't be hard-coded as there's a method to get it

db/models/related.py

class RelatedObject(object):
    get_cache_name()

#

anentropic (on August 24, 2011):

also, for the '%s_id'

class RelatedObject(object):
    get_attname()

#

Please login first before commenting.