1 from UserDict import DictMixin
3 from django.db import models
4 from django.contrib.contenttypes.models import ContentType
5 from django.core.paginator import Paginator, EmptyPage
6 from django.template import Context
7 from django.template.loader_tags import ExtendsNode, ConstantIncludeNode
10 def fattr(*args, **kwargs):
11 def wrapper(function):
13 setattr(function, key, kwargs[key])
18 ### ContentTypeLimiters
21 class ContentTypeLimiter(object):
23 return models.Q(pk__in=[])
25 def add_to_query(self, query, *args, **kwargs):
26 query.add_q(self.q_object(), *args, **kwargs)
29 class ContentTypeRegistryLimiter(ContentTypeLimiter):
33 def register_class(self, cls):
34 self.classes.append(cls)
36 def unregister_class(self, cls):
37 self.classes.remove(cls)
41 for cls in self.classes:
43 if issubclass(cls, models.Model):
44 if not cls._meta.abstract:
45 contenttype = ContentType.objects.get_for_model(cls)
46 contenttype_pks.append(contenttype.pk)
49 return models.Q(pk__in=contenttype_pks)
52 class ContentTypeSubclassLimiter(ContentTypeLimiter):
53 def __init__(self, cls, inclusive=False):
55 self.inclusive = inclusive
59 def handle_subclasses(cls):
60 for subclass in cls.__subclasses__():
62 if issubclass(subclass, models.Model):
63 if not subclass._meta.abstract:
64 if not self.inclusive and subclass is self.cls:
66 contenttype = ContentType.objects.get_for_model(subclass)
67 contenttype_pks.append(contenttype.pk)
68 handle_subclasses(subclass)
71 handle_subclasses(self.cls)
72 return models.Q(pk__in=contenttype_pks)
78 def paginate(objects, per_page=None, page_number=1):
80 Given a list of objects, return a (paginator, page, objects) tuple.
83 per_page = int(per_page)
84 except (TypeError, ValueError):
85 # Then either it wasn't set or it was set to an invalid value
86 paginator = page = None
88 # There also shouldn't be pagination if the list is too short. Try count()
89 # first - good chance it's a queryset, where count is more efficient.
91 if objects.count() <= per_page:
92 paginator = page = None
93 except AttributeError:
94 if len(objects) <= per_page:
95 paginator = page = None
98 return paginator, page, objects
102 paginator = Paginator(objects, per_page)
104 page_number = int(page_number)
109 page = paginator.page(page_number)
113 objects = page.object_list
115 return paginator, page, objects
118 ### Facilitating template analysis.
121 LOADED_TEMPLATE_ATTR = '_philo_loaded_template'
122 BLANK_CONTEXT = Context()
125 def get_extended(self):
126 return self.get_parent(BLANK_CONTEXT)
129 def get_included(self):
133 # We ignore the IncludeNode because it will never work in a blank context.
134 setattr(ExtendsNode, LOADED_TEMPLATE_ATTR, property(get_extended))
135 setattr(ConstantIncludeNode, LOADED_TEMPLATE_ATTR, property(get_included))
141 class AttributeMapper(object, DictMixin):
142 def __init__(self, entity):
146 def __getitem__(self, key):
147 if not self._cache_populated:
148 self._populate_cache()
149 return self._cache[key]
151 def __setitem__(self, key, value):
152 # Prevent circular import.
153 from philo.models.base import JSONValue, ForeignKeyValue, ManyToManyValue, Attribute
154 old_attr = self.get_attribute(key)
155 if old_attr and old_attr.entity_content_type == ContentType.objects.get_for_model(self.entity) and old_attr.entity_object_id == self.entity.pk:
158 attribute = Attribute(key=key)
159 attribute.entity = self.entity
160 attribute.full_clean()
162 if isinstance(value, models.query.QuerySet):
163 value_class = ManyToManyValue
164 elif isinstance(value, models.Model):
165 value_class = ForeignKeyValue
167 value_class = JSONValue
169 attribute.set_value(value=value, value_class=value_class)
170 self._cache[key] = attribute.value.value
171 self._attributes_cache[key] = attribute
173 def get_attributes(self):
174 return self.entity.attribute_set.all()
176 def get_attribute(self, key):
177 if not self._cache_populated:
178 self._populate_cache()
179 return self._attributes_cache.get(key, None)
182 if not self._cache_populated:
183 self._populate_cache()
184 return self._cache.keys()
187 if not self._cache_populated:
188 self._populate_cache()
189 return self._cache.items()
192 if not self._cache_populated:
193 self._populate_cache()
194 return self._cache.values()
196 def _populate_cache(self):
197 if self._cache_populated:
200 attributes = self.get_attributes()
204 value_lookups.setdefault(a.value_content_type, []).append(a.value_object_id)
205 self._attributes_cache[a.key] = a
209 for ct, pks in value_lookups.items():
210 values_bulk[ct] = ct.model_class().objects.in_bulk(pks)
212 self._cache.update(dict([(a.key, getattr(values_bulk[a.value_content_type].get(a.value_object_id), 'value', None)) for a in attributes]))
213 self._cache_populated = True
215 def clear_cache(self):
217 self._attributes_cache = {}
218 self._cache_populated = False
221 class LazyAttributeMapperMixin(object):
222 def __getitem__(self, key):
223 if key not in self._cache and not self._cache_populated:
224 self._add_to_cache(key)
225 return self._cache[key]
227 def get_attribute(self, key):
228 if key not in self._attributes_cache and not self._cache_populated:
229 self._add_to_cache(key)
230 return self._attributes_cache[key]
232 def _add_to_cache(self, key):
234 attr = self.get_attributes().get(key=key)
235 except Attribute.DoesNotExist:
238 val = getattr(attr.value, 'value', None)
239 self._cache[key] = val
240 self._attributes_cache[key] = attr
243 class LazyAttributeMapper(LazyAttributeMapperMixin, AttributeMapper):
244 def get_attributes(self):
245 return super(LazyAttributeMapper, self).get_attributes().exclude(key__in=self._cache.keys())
248 class TreeAttributeMapper(AttributeMapper):
249 def get_attributes(self):
250 from philo.models import Attribute
251 ancestors = dict(self.entity.get_ancestors(include_self=True).values_list('pk', 'level'))
252 ct = ContentType.objects.get_for_model(self.entity)
253 return sorted(Attribute.objects.filter(entity_content_type=ct, entity_object_id__in=ancestors.keys()), key=lambda x: ancestors[x.entity_object_id])
256 class LazyTreeAttributeMapper(LazyAttributeMapperMixin, TreeAttributeMapper):
257 def get_attributes(self):
258 return super(LazyTreeAttributeMapper, self).get_attributes().exclude(key__in=self._cache.keys())
261 class PassthroughAttributeMapper(AttributeMapper):
262 def __init__(self, entities):
263 self._attributes = [e.attributes for e in entities]
264 super(PassthroughAttributeMapper, self).__init__(self._attributes[0].entity)
266 def _populate_cache(self):
267 if self._cache_populated:
270 for a in reversed(self._attributes):
272 self._attributes_cache.update(a._attributes_cache)
273 self._cache.update(a._cache)
275 self._cache_populated = True
277 def get_attributes(self):
278 raise NotImplementedError
280 def clear_cache(self):
281 super(PassthroughAttributeMapper, self).clear_cache()
282 for a in self._attributes:
286 class LazyPassthroughAttributeMapper(LazyAttributeMapperMixin, PassthroughAttributeMapper):
287 def _add_to_cache(self, key):
288 for a in self._attributes:
290 self._cache[key] = a[key]
291 self._attributes_cache[key] = a.get_attribute(key)
296 return self._cache[key]