Moved efficient QuerySetMappers into philo.utils and replaced them with a more comple...
[philo.git] / philo / models / base.py
index 5d8fdc2..0ab9b70 100644 (file)
@@ -1,5 +1,3 @@
-from UserDict import DictMixin
-
 from django import forms
 from django.contrib.contenttypes.models import ContentType
 from django.contrib.contenttypes import generic
@@ -13,7 +11,7 @@ from mptt.models import MPTTModel, MPTTModelBase, MPTTOptions
 from philo.exceptions import AncestorDoesNotExist
 from philo.models.fields import JSONField
 from philo.signals import entity_class_prepared
-from philo.utils import ContentTypeRegistryLimiter, ContentTypeSubclassLimiter
+from philo.utils import ContentTypeRegistryLimiter, ContentTypeSubclassLimiter, AttributeMapper, TreeAttributeMapper
 from philo.validators import json_validator
 
 
@@ -262,35 +260,26 @@ class Attribute(models.Model):
        def __unicode__(self):
                return u'"%s": %s' % (self.key, self.value)
        
+       def set_value(self, value, value_class=JSONValue):
+               """Given a value and a value class, sets up self.value appropriately."""
+               if isinstance(self.value, value_class):
+                       val = self.value
+               else:
+                       if isinstance(self.value, models.Model):
+                               self.value.delete()
+                       val = value_class()
+               
+               val.set_value(value)
+               val.save()
+               
+               self.value = val
+               self.save()
+       
        class Meta:
                app_label = 'philo'
                unique_together = (('key', 'entity_content_type', 'entity_object_id'), ('value_content_type', 'value_object_id'))
 
 
-class QuerySetMapper(object, DictMixin):
-       def __init__(self, queryset, passthrough=None):
-               self.queryset = queryset
-               self.passthrough = passthrough
-       
-       def __getitem__(self, key):
-               try:
-                       value = self.queryset.get(key__exact=key).value
-               except ObjectDoesNotExist:
-                       if self.passthrough is not None:
-                               return self.passthrough.__getitem__(key)
-                       raise KeyError
-               else:
-                       if value is not None:
-                               return value.value
-                       return value
-       
-       def keys(self):
-               keys = set(self.queryset.values_list('key', flat=True).distinct())
-               if self.passthrough is not None:
-                       keys |= set(self.passthrough.keys())
-               return list(keys)
-
-
 class EntityOptions(object):
        def __init__(self, options):
                if options is not None:
@@ -312,58 +301,15 @@ 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):
+       def get_attribute_mapper(self, mapper=AttributeMapper):
                """
-               Property that returns a dictionary-like object which can be used to retrieve related :class:`Attribute`\ s' values directly.
+               Returns a dictionary-like object which can be used to retrieve related :class:`Attribute`\ s' values directly.
 
                Example::
 
@@ -374,7 +320,8 @@ class Entity(models.Model):
                        u'eggs'
                
                """
-               return EntityAttributeMapper(self)
+               return mapper(self)
+       attributes = property(get_attribute_mapper)
        
        class Meta:
                abstract = True
@@ -536,22 +483,14 @@ 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):
        """An abstract subclass of Entity which represents a tree relationship."""
        
        __metaclass__ = TreeEntityBase
        
-       @property
-       def attributes(self):
+       def get_attribute_mapper(self, mapper=None):
                """
-               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.
+               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::
 
@@ -564,10 +503,13 @@ class TreeEntity(Entity, TreeModel):
                        u'eggs'
                
                """
-               
-               if self.parent:
-                       return TreeEntityAttributeMapper(self)
-               return super(TreeEntity, self).attributes
+               if mapper is None:
+                       if self.parent:
+                               mapper = TreeAttributeMapper
+                       else:
+                               mapper = AttributeMapper
+               return super(TreeEntity, self).get_attribute_mapper(mapper)
+       attributes = property(get_attribute_mapper)
        
        class Meta:
                abstract = True
\ No newline at end of file