Implementation of QuerySetMappers for Entities that improves the worst-case number...
[philo.git] / models / base.py
index 8370bb7..19715ab 100644 (file)
@@ -5,7 +5,7 @@ from django.contrib.contenttypes import generic
 from django.core.exceptions import ObjectDoesNotExist
 from django.core.validators import RegexValidator
 from django.utils import simplejson as json
-from django.utils.encoding import smart_str
+from django.utils.encoding import force_unicode
 from philo.exceptions import AncestorDoesNotExist
 from philo.models.fields import JSONField
 from philo.utils import ContentTypeRegistryLimiter, ContentTypeSubclassLimiter
@@ -55,10 +55,6 @@ def unregister_value_model(model):
 class AttributeValue(models.Model):
        attribute_set = generic.GenericRelation('Attribute', content_type_field='value_content_type', object_id_field='value_object_id')
        
-       @property
-       def attribute(self):
-               return self.attribute_set.all()[0]
-       
        def set_value(self, value):
                raise NotImplementedError
        
@@ -84,7 +80,7 @@ class JSONValue(AttributeValue):
        value = JSONField(verbose_name='Value (JSON)', help_text='This value must be valid JSON.', default='null', db_index=True)
        
        def __unicode__(self):
-               return smart_str(self.value)
+               return force_unicode(self.value)
        
        def value_formfields(self):
                kwargs = {'initial': self.value_json}
@@ -275,13 +271,55 @@ class EntityOptions(object):
 
 class EntityBase(models.base.ModelBase):
        def __new__(cls, name, bases, attrs):
+               entity_meta = attrs.pop('EntityMeta', None)
                new = super(EntityBase, cls).__new__(cls, name, bases, attrs)
-               entity_options = attrs.pop('EntityMeta', None)
-               setattr(new, '_entity_meta', EntityOptions(entity_options))
+               new.add_to_class('_entity_meta', EntityOptions(entity_meta))
                entity_class_prepared.send(sender=new)
                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):
        __metaclass__ = EntityBase
        
@@ -289,7 +327,7 @@ class Entity(models.Model):
        
        @property
        def attributes(self):
-               return QuerySetMapper(self.attribute_set.all())
+               return EntityAttributeMapper(self)
        
        class Meta:
                abstract = True
@@ -441,13 +479,20 @@ class TreeEntityBase(MPTTModelBase, EntityBase):
                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):
        __metaclass__ = TreeEntityBase
        
        @property
        def attributes(self):
                if self.parent:
-                       return QuerySetMapper(self.attribute_set.all(), passthrough=self.parent.attributes)
+                       return TreeEntityAttributeMapper(self)
                return super(TreeEntity, self).attributes
        
        class Meta: