Split utils into entities (i.e. AttributeMappers) and other utils. Added documentatio...
[philo.git] / philo / utils / entities.py
1 from UserDict import DictMixin
2
3 from django.db import models
4 from django.contrib.contenttypes.models import ContentType
5
6
7 ### AttributeMappers
8
9
10 class AttributeMapper(object, DictMixin):
11         def __init__(self, entity):
12                 self.entity = entity
13                 self.clear_cache()
14         
15         def __getitem__(self, key):
16                 if not self._cache_populated:
17                         self._populate_cache()
18                 return self._cache[key]
19         
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:
25                         attribute = old_attr
26                 else:
27                         attribute = Attribute(key=key)
28                         attribute.entity = self.entity
29                         attribute.full_clean()
30                 
31                 if isinstance(value, models.query.QuerySet):
32                         value_class = ManyToManyValue
33                 elif isinstance(value, models.Model):
34                         value_class = ForeignKeyValue
35                 else:
36                         value_class = JSONValue
37                 
38                 attribute.set_value(value=value, value_class=value_class)
39                 self._cache[key] = attribute.value.value
40                 self._attributes_cache[key] = attribute
41         
42         def get_attributes(self):
43                 return self.entity.attribute_set.all()
44         
45         def get_attribute(self, key):
46                 if not self._cache_populated:
47                         self._populate_cache()
48                 return self._attributes_cache.get(key, None)
49         
50         def keys(self):
51                 if not self._cache_populated:
52                         self._populate_cache()
53                 return self._cache.keys()
54         
55         def items(self):
56                 if not self._cache_populated:
57                         self._populate_cache()
58                 return self._cache.items()
59         
60         def values(self):
61                 if not self._cache_populated:
62                         self._populate_cache()
63                 return self._cache.values()
64         
65         def _populate_cache(self):
66                 if self._cache_populated:
67                         return
68                 
69                 attributes = self.get_attributes()
70                 value_lookups = {}
71                 
72                 for a in attributes:
73                         value_lookups.setdefault(a.value_content_type, []).append(a.value_object_id)
74                         self._attributes_cache[a.key] = a
75                 
76                 values_bulk = {}
77                 
78                 for ct, pks in value_lookups.items():
79                         values_bulk[ct] = ct.model_class().objects.in_bulk(pks)
80                 
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
83         
84         def clear_cache(self):
85                 self._cache = {}
86                 self._attributes_cache = {}
87                 self._cache_populated = False
88
89
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]
95         
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]
100         
101         def _add_to_cache(self, key):
102                 try:
103                         attr = self.get_attributes().get(key=key)
104                 except Attribute.DoesNotExist:
105                         raise KeyError
106                 else:
107                         val = getattr(attr.value, 'value', None)
108                         self._cache[key] = val
109                         self._attributes_cache[key] = attr
110
111
112 class LazyAttributeMapper(LazyAttributeMapperMixin, AttributeMapper):
113         def get_attributes(self):
114                 return super(LazyAttributeMapper, self).get_attributes().exclude(key__in=self._cache.keys())
115
116
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])
123
124
125 class LazyTreeAttributeMapper(LazyAttributeMapperMixin, TreeAttributeMapper):
126         def get_attributes(self):
127                 return super(LazyTreeAttributeMapper, self).get_attributes().exclude(key__in=self._cache.keys())
128
129
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)
134         
135         def _populate_cache(self):
136                 if self._cache_populated:
137                         return
138                 
139                 for a in reversed(self._attributes):
140                         a._populate_cache()
141                         self._attributes_cache.update(a._attributes_cache)
142                         self._cache.update(a._cache)
143                 
144                 self._cache_populated = True
145         
146         def get_attributes(self):
147                 raise NotImplementedError
148         
149         def clear_cache(self):
150                 super(PassthroughAttributeMapper, self).clear_cache()
151                 for a in self._attributes:
152                         a.clear_cache()
153
154
155 class LazyPassthroughAttributeMapper(LazyAttributeMapperMixin, PassthroughAttributeMapper):
156         def _add_to_cache(self, key):
157                 for a in self._attributes:
158                         try:
159                                 self._cache[key] = a[key]
160                                 self._attributes_cache[key] = a.get_attribute(key)
161                         except KeyError:
162                                 pass
163                         else:
164                                 break
165                 return self._cache[key]