Merge branch 'efficient_attributes' into attribute_access
authorStephen Burrows <stephen.r.burrows@gmail.com>
Tue, 10 May 2011 13:58:51 +0000 (09:58 -0400)
committerStephen Burrows <stephen.r.burrows@gmail.com>
Tue, 10 May 2011 13:58:51 +0000 (09:58 -0400)
Conflicts:
philo/models/base.py

1  2 
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):
-               
-               return QuerySetMapper(self.attribute_set.all())
 +              """
 +              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 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: