From: Stephen Burrows Date: Tue, 10 May 2011 13:58:51 +0000 (-0400) Subject: Merge branch 'efficient_attributes' into attribute_access X-Git-Tag: philo-0.9~12^2~18^2~1 X-Git-Url: http://git.ithinksw.org/philo.git/commitdiff_plain/c9802c5cb6ad16a895cd5434d6f7695d1ae930f7?ds=inline;hp=--cc Merge branch 'efficient_attributes' into attribute_access Conflicts: philo/models/base.py --- c9802c5cb6ad16a895cd5434d6f7695d1ae930f7 diff --cc philo/models/base.py index 436df42,19715ab..5d8fdc2 --- a/philo/models/base.py +++ b/philo/models/base.py @@@ -312,28 -278,56 +312,69 @@@ class EntityBase(models.base.ModelBase) return new + class EntityAttributeMapper(object, DictMixin): + def __init__(self, entity): + self.entity = entity + + def get_attributes(self): + return self.entity.attribute_set.all() + + def make_cache(self): + attributes = self.get_attributes() + value_lookups = {} + + for a in attributes: + value_lookups.setdefault(a.value_content_type, []).append(a.value_object_id) + + values_bulk = {} + + for ct, pks in value_lookups.items(): + values_bulk[ct] = ct.model_class().objects.in_bulk(pks) + + self._cache = dict([(a.key, getattr(values_bulk[a.value_content_type].get(a.value_object_id), 'value', None)) for a in attributes]) + + def __getitem__(self, key): + if not hasattr(self, '_cache'): + self.make_cache() + return self._cache[key] + + def keys(self): + if not hasattr(self, '_cache'): + self.make_cache() + return self._cache.keys() + + def items(self): + if not hasattr(self, '_cache'): + self.make_cache() + return self._cache.items() + + def values(self): + if not hasattr(self, '_cache'): + self.make_cache() + return self._cache.values() + + class Entity(models.Model): + """An abstract class that simplifies access to related attributes. Most models provided by Philo subclass Entity.""" __metaclass__ = EntityBase attribute_set = generic.GenericRelation(Attribute, content_type_field='entity_content_type', object_id_field='entity_object_id') @property def attributes(self): + """ + Property that returns a dictionary-like object which can be used to retrieve related :class:`Attribute`\ s' values directly. + + Example:: + + >>> attr = entity.attribute_set.get(key='spam') + >>> attr.value.value + u'eggs' + >>> entity.attributes['spam'] + u'eggs' + + """ - - return QuerySetMapper(self.attribute_set.all()) + return EntityAttributeMapper(self) class Meta: abstract = True @@@ -495,30 -479,20 +536,37 @@@ class TreeEntityBase(MPTTModelBase, Ent return meta.register(cls) + class TreeEntityAttributeMapper(EntityAttributeMapper): + def get_attributes(self): + ancestors = dict(self.entity.get_ancestors(include_self=True).values_list('pk', 'level')) + ct = ContentType.objects.get_for_model(self.entity) + return sorted(Attribute.objects.filter(entity_content_type=ct, entity_object_id__in=ancestors.keys()), key=lambda x: ancestors[x.entity_object_id]) + + class TreeEntity(Entity, TreeModel): + """An abstract subclass of Entity which represents a tree relationship.""" + __metaclass__ = TreeEntityBase @property def attributes(self): + """ + Property that returns a dictionary-like object which can be used to retrieve related :class:`Attribute`\ s' values directly. If an attribute with a given key is not related to the :class:`Entity`, then the object will check the parent's attributes. + + Example:: + + >>> attr = entity.attribute_set.get(key='spam') + DoesNotExist: Attribute matching query does not exist. + >>> attr = entity.parent.attribute_set.get(key='spam') + >>> attr.value.value + u'eggs' + >>> entity.attributes['spam'] + u'eggs' + + """ + if self.parent: - return QuerySetMapper(self.attribute_set.all(), passthrough=self.parent.attributes) + return TreeEntityAttributeMapper(self) return super(TreeEntity, self).attributes class Meta: