1 from UserDict import DictMixin
3 from django.db import models
4 from django.contrib.contenttypes.models import ContentType
10 class AttributeMapper(object, DictMixin):
11 def __init__(self, entity):
15 def __getitem__(self, key):
16 if not self._cache_populated:
17 self._populate_cache()
18 return self._cache[key]
20 def __setitem__(self, key, value):
21 # Prevent circular import.
22 from philo.models.base import JSONValue, ForeignKeyValue, ManyToManyValue, Attribute
23 old_attr = self.get_attribute(key)
24 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:
27 attribute = Attribute(key=key)
28 attribute.entity = self.entity
29 attribute.full_clean()
31 if isinstance(value, models.query.QuerySet):
32 value_class = ManyToManyValue
33 elif isinstance(value, models.Model):
34 value_class = ForeignKeyValue
36 value_class = JSONValue
38 attribute.set_value(value=value, value_class=value_class)
39 self._cache[key] = attribute.value.value
40 self._attributes_cache[key] = attribute
42 def get_attributes(self):
43 return self.entity.attribute_set.all()
45 def get_attribute(self, key):
46 if not self._cache_populated:
47 self._populate_cache()
48 return self._attributes_cache.get(key, None)
51 if not self._cache_populated:
52 self._populate_cache()
53 return self._cache.keys()
56 if not self._cache_populated:
57 self._populate_cache()
58 return self._cache.items()
61 if not self._cache_populated:
62 self._populate_cache()
63 return self._cache.values()
65 def _populate_cache(self):
66 if self._cache_populated:
69 attributes = self.get_attributes()
73 value_lookups.setdefault(a.value_content_type, []).append(a.value_object_id)
74 self._attributes_cache[a.key] = a
78 for ct, pks in value_lookups.items():
79 values_bulk[ct] = ct.model_class().objects.in_bulk(pks)
81 self._cache.update(dict([(a.key, getattr(values_bulk[a.value_content_type].get(a.value_object_id), 'value', None)) for a in attributes]))
82 self._cache_populated = True
84 def clear_cache(self):
86 self._attributes_cache = {}
87 self._cache_populated = False
90 class LazyAttributeMapperMixin(object):
91 def __getitem__(self, key):
92 if key not in self._cache and not self._cache_populated:
93 self._add_to_cache(key)
94 return self._cache[key]
96 def get_attribute(self, key):
97 if key not in self._attributes_cache and not self._cache_populated:
98 self._add_to_cache(key)
99 return self._attributes_cache[key]
101 def _add_to_cache(self, key):
103 attr = self.get_attributes().get(key=key)
104 except Attribute.DoesNotExist:
107 val = getattr(attr.value, 'value', None)
108 self._cache[key] = val
109 self._attributes_cache[key] = attr
112 class LazyAttributeMapper(LazyAttributeMapperMixin, AttributeMapper):
113 def get_attributes(self):
114 return super(LazyAttributeMapper, self).get_attributes().exclude(key__in=self._cache.keys())
117 class TreeAttributeMapper(AttributeMapper):
118 def get_attributes(self):
119 from philo.models import Attribute
120 ancestors = dict(self.entity.get_ancestors(include_self=True).values_list('pk', 'level'))
121 ct = ContentType.objects.get_for_model(self.entity)
122 return sorted(Attribute.objects.filter(entity_content_type=ct, entity_object_id__in=ancestors.keys()), key=lambda x: ancestors[x.entity_object_id])
125 class LazyTreeAttributeMapper(LazyAttributeMapperMixin, TreeAttributeMapper):
126 def get_attributes(self):
127 return super(LazyTreeAttributeMapper, self).get_attributes().exclude(key__in=self._cache.keys())
130 class PassthroughAttributeMapper(AttributeMapper):
131 def __init__(self, entities):
132 self._attributes = [e.attributes for e in entities]
133 super(PassthroughAttributeMapper, self).__init__(self._attributes[0].entity)
135 def _populate_cache(self):
136 if self._cache_populated:
139 for a in reversed(self._attributes):
141 self._attributes_cache.update(a._attributes_cache)
142 self._cache.update(a._cache)
144 self._cache_populated = True
146 def get_attributes(self):
147 raise NotImplementedError
149 def clear_cache(self):
150 super(PassthroughAttributeMapper, self).clear_cache()
151 for a in self._attributes:
155 class LazyPassthroughAttributeMapper(LazyAttributeMapperMixin, PassthroughAttributeMapper):
156 def _add_to_cache(self, key):
157 for a in self._attributes:
159 self._cache[key] = a[key]
160 self._attributes_cache[key] = a.get_attribute(key)
165 return self._cache[key]