byteflow

view apps/lib/db.py @ 1363:f75ca7dfb4f0

fix py2.4 compat
author Alexander Solovyov <piranha@piranha.org.ua>
date Wed Feb 03 11:48:39 2010 +0200 (5 weeks ago)
parents 291c40de7660
children
line source
1 # -*- mode: python; coding: utf-8; -*-
3 """DB and Django ORM utilities and extensions."""
5 from django.contrib.contenttypes.models import ContentType, ContentTypeManager
8 def get_without_app(model):
9 for key in ContentTypeManager._cache:
10 app_name, model_name = key
11 if model_name == model:
12 return key
13 return None
16 def ctm_get(self, **kwargs):
17 if 'model' in kwargs:
18 if 'app_label' in kwargs:
19 key = (kwargs['app_label'], kwargs['model'])
20 else:
21 key = get_without_app(kwargs['model'])
22 if key:
23 try:
24 return ContentTypeManager._cache[key]
25 except KeyError:
26 pass
27 ctype = self.get_query_set().get(**kwargs)
28 ContentTypeManager._cache[(ctype.app_label, ctype.model)] = ctype
29 return ctype
30 return self.get_query_set().get(**kwargs)
33 def get_ranges(keys):
34 """Get less possible number of inclusive ranges of integers keys."""
35 if not keys:
36 return []
37 keys.sort()
38 ranges = []
39 last_key, range_begin = keys[0], keys[0]
40 for key in keys[1:]:
41 if key - last_key != 1: # new range
42 ranges.append((range_begin, last_key))
43 range_begin = key
44 last_key = key
45 else:
46 last_key = key
47 ranges.append((range_begin, last_key))
48 return ranges
51 def get_ranges_sql(field, ranges):
52 """Return WHERE statement for the ranges"""
53 def sql(range):
54 diff = range[1] - range[0]
55 if diff > 1:
56 return "(%s>=%d AND %s<=%d)" % (field, range[0], field, range[1])
57 elif diff == 1:
58 return "%s=%d OR %s=%d" % (field, range[0], field, range[1])
59 else:
60 return "%s=%d" % (field, range[0])
61 return "(" + " OR ".join([sql(r) for r in ranges]) + ")"
64 def load_generic_related(object_list, related_qs, cache_field, field='object_id', ct_field='content_type'):
65 if object_list:
66 ct = ContentType.objects.get_for_model(object_list[0])
67 return load_related(object_list, related_qs.filter(**{ct_field: ct.id}), cache_field, field)
68 return object_list
71 def load_related(object_list, related_qs, cache_field=None, field=None):
72 """Load backward related objects for a list of objects.
74 Function will do only 1 SQL query instead of len(object_list) queries.
76 Parameters:
78 - object_list - list of objects to attach related objects.
79 - related_qs - queryset (or a manager) from where to fetch related
80 objects.
81 - cache_field - name for attaching related objects, optional (determined
82 by the name of object_list model).
83 - field - relation field, by which related_qs is related to object_list,
84 optional (determined by the name of related_qs model).
85 """
86 if not object_list:
87 return object_list
88 if not field:
89 field = object_list[0]._meta.object_name.lower()
90 field = related_qs.model._meta.get_field(field)
91 if not cache_field:
92 cache_field = related_qs.model.__name__.lower() + "_cache"
93 pks = list(set([field.to_python(obj.pk) for obj in object_list]))
94 # get related queryset
95 if isinstance(pks[0], int):
96 ranges = get_ranges(pks)
97 related_qs = related_qs.extra(where = (get_ranges_sql(field.column, ranges),))
98 else:
99 related_qs = related_qs.filter(**{"%s__in" % field.name: pks})
100 # generate list of related objects
101 related_list = {}
102 for rel_obj in related_qs:
103 related_list.setdefault(getattr(rel_obj, field.attname), []).append(rel_obj)
104 # append related objects to cache field
105 for obj in object_list:
106 setattr(obj, cache_field, related_list.get(obj.pk, []))
107 return object_list
110 def load_content_objects(object_list, cache_field='content_object',
111 field='object_id', ct_field='content_type',
112 processor=lambda qs: qs):
113 """Load content objects for a generic relation.
115 Number of sql queries is equals not to number of objects but number of
116 unique content types. For example we have article and project models that
117 can be tagged and we want to load all objects tagged with a tag, then we
118 always will spent 2 SQL queries instead of number of tagged objects SQL
119 queries.
121 Parameters:
123 - object_list - list of objects with GenericForeignKey to process
124 - cache_field - name of attribute, which should contain loaded
125 object. Typically it's a name of a GenericForeignKey field.
126 - field - field, which contains object_id
127 - ct_field - field, which contains foreign key to content type
128 - processor - function, which should receive QuerySet of objects to be
129 loaded and return processed QuerySet. Used to set select_related etc.
130 """
131 if not object_list:
132 return object_list
133 field = object_list[0]._meta.get_field(field)
134 ct_field = object_list[0]._meta.get_field(ct_field)
135 ctypes = {}
136 for obj in object_list: # group objects by ctype
137 ct_id = getattr(obj, ct_field.column)
138 try:
139 ctypes[ct_id].append(obj)
140 except KeyError:
141 ctypes[ct_id] = [obj]
142 for ct_id, objects in ctypes.items(): # fetch each ctype by 1 SQL query
143 ctype = ContentType.objects.get_for_id(ct_id)
144 model = ctype.model_class()
145 pk_field = model._meta.pk
146 pk_object_map = {}
147 for obj in objects:
148 pk_value = pk_field.to_python(getattr(obj, field.column))
149 try:
150 pk_object_map[pk_value].append(obj)
151 except KeyError:
152 pk_object_map[pk_value] = [obj]
153 keys = pk_object_map.keys()
154 qs = model.objects.filter(pk__in=keys)
155 qs = processor(qs)
156 for model_obj in qs:
157 for obj in pk_object_map[model_obj.pk]:
158 setattr(obj, cache_field, model_obj)
159 return object_list
Repositories maintained by Alexander Solovyov