1 """Utilities for working with Django Models."""
4 from django.contrib.contenttypes.models import ContentType
6 from lanai.utils.lists import flatten
8 def fetch_model_dict(model, ids, fields=None):
10 Fetches a dict of model details for model instances with the given
11 ids, keyed by their id.
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.
16 Any fields list given shouldn't contain the primary key attribute for
17 the model, as this can be determined from its Options.
20 return model._default_manager.in_bulk(ids)
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)))
27 def populate_foreign_key_caches(model, objects_to_populate, fields=None):
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.
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
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)
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)
47 # Fill related object caches
48 for (objects, attrs), related_ids in itertools.izip(objects_to_populate,
50 for obj, related_ids_for_obj in itertools.izip(objects,
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)
56 def populate_content_object_caches(generic_related_objects, model_fields=None):
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.
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.
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.
71 if model_fields is None:
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)
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))
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]