X-Git-Url: http://git.ithinksw.org/philo.git/blobdiff_plain/c9802c5cb6ad16a895cd5434d6f7695d1ae930f7..9cbb247dfb4faa47617438770bb6135155fade16:/philo/models/base.py diff --git a/philo/models/base.py b/philo/models/base.py index 5d8fdc2..0ab9b70 100644 --- a/philo/models/base.py +++ b/philo/models/base.py @@ -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