]> git.openstreetmap.org Git - osqa.git/blob - osqa/forum/utils/cache.py
6341392eb95cbc62bd6cba6df8a9c90215a837fc
[osqa.git] / osqa / forum / utils / cache.py
1 """Utilities for working with Django Models."""
2 import itertools
3
4 from django.contrib.contenttypes.models import ContentType
5
6 from forum.utils.lists import flatten
7
8 def fetch_model_dict(model, ids, fields=None):
9     """
10     Fetches a dict of model details for model instances with the given
11     ids, keyed by their id.
12
13     If a fields list is given, a dict of details will be retrieved for
14     each model, otherwise complete model instances will be retrieved.
15
16     Any fields list given shouldn't contain the primary key attribute for
17     the model, as this can be determined from its Options.
18     """
19     if fields is None:
20         return model._default_manager.in_bulk(ids)
21     else:
22         id_attr = model._meta.pk.attname
23         return dict((obj[id_attr], obj) for obj
24             in model._default_manager.filter(id__in=ids).values(
25                 *itertools.chain((id_attr,), fields)))
26
27 def populate_foreign_key_caches(model, objects_to_populate, fields=None):
28     """
29     Populates caches for the given related Model in instances of objects
30     which have a ForeignKey relationship to it, specified as a list of
31     (object list, related attribute name list) two-tuples.
32
33     If a list of field names is given, only the given fields will be
34     looked up and related object caches will be populated with a dict of
35     the specified fields. Otherwise, complete model instances will be
36     retrieved.
37     """
38     # Get all related object ids for the appropriate fields
39     related_object_ids = []
40     for objects, attrs in objects_to_populate:
41         related_object_ids.append(tuple(tuple(getattr(obj, '%s_id' % attr)
42                                               for attr in attrs)
43                                   for obj in objects))
44     unique_ids = tuple(set(pk for pk in flatten(related_object_ids) if pk))
45     related_objects = fetch_model_dict(model, unique_ids, fields)
46
47     # Fill related object caches
48     for (objects, attrs), related_ids in itertools.izip(objects_to_populate,
49                                                         related_object_ids):
50         for obj, related_ids_for_obj in itertools.izip(objects,
51                                                        related_ids):
52             for attr, related_object in itertools.izip(attrs, (related_objects.get(pk, None)
53                                                                for pk in related_ids_for_obj)):
54                 setattr(obj, '_%s_cache' % attr, related_object)
55
56 def populate_content_object_caches(generic_related_objects, model_fields=None):
57     """
58     Retrieves ``ContentType`` and content objects for the given list of
59     items which use a generic relation, grouping the retrieval of content
60     objects by model to reduce the number of queries executed.
61
62     This results in ``number_of_content_types + 1`` queries rather than
63     the ``number_of_generic_reL_objects * 2`` queries you'd get by
64     iterating over the list and accessing each item's object attribute.
65
66     If a dict mapping model classes to field names is given, only the
67     given fields will be looked up for each model specified and the
68     object cache will be populated with a dict of the specified fields.
69     Otherwise, complete model instances will be retrieved.
70     """
71     if model_fields is None:
72         model_fields = {}
73
74     # Group content object ids by their content type ids
75     ids_by_content_type = {}
76     for obj in generic_related_objects:
77         ids_by_content_type.setdefault(obj.content_type_id,
78                                        []).append(obj.object_id)
79
80     # Retrieve content types and content objects in bulk
81     content_types = ContentType.objects.in_bulk(ids_by_content_type.keys())
82     for content_type_id, ids in ids_by_content_type.iteritems():
83         model = content_types[content_type_id].model_class()
84         objects[content_type_id] = fetch_model_dict(
85             model, tuple(set(ids)), model_fields.get(model, None))
86
87     # Set content types and content objects in the appropriate cache
88     # attributes, so accessing the 'content_type' and 'object' attributes
89     # on each object won't result in further database hits.
90     for obj in generic_related_objects:
91         obj._object_cache = objects[obj.content_type_id][obj.object_id]
92         obj._content_type_cache = content_types[obj.content_type_id]