X-Git-Url: http://git.ithinksw.org/philo.git/blobdiff_plain/943e8bc4af0c11b0ace3811199e3b0844c4c3fbc..46997a51ed0ceb16ddbac712fbcb552a86cdc9e2:/philo/models/base.py diff --git a/philo/models/base.py b/philo/models/base.py index 1726d19..46fa8d5 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 @@ -14,9 +12,13 @@ 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.entities import AttributeMapper, TreeAttributeMapper from philo.validators import json_validator +__all__ = ('Tag', 'value_content_type_limiter', 'register_value_model', 'unregister_value_model', 'JSONValue', 'ForeignKeyValue', 'ManyToManyValue', 'Attribute', 'Entity', 'TreeEntity') + + class Tag(models.Model): """A simple, generic model for tagging.""" #: A CharField (max length 255) which contains the name of the tag. @@ -44,7 +46,7 @@ class Titled(models.Model): abstract = True -#: An instance of :class:`ContentTypeRegistryLimiter` which is used to track the content types which can be related to by :class:`ForeignKeyValue`\ s and :class:`ManyToManyValue`\ s. +#: An instance of :class:`.ContentTypeRegistryLimiter` which is used to track the content types which can be related to by :class:`ForeignKeyValue`\ s and :class:`ManyToManyValue`\ s. value_content_type_limiter = ContentTypeRegistryLimiter() @@ -262,35 +264,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: @@ -318,10 +311,9 @@ class Entity(models.Model): 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 an :class:`.AttributeMapper` which can be used to retrieve related :class:`Attribute`\ s' values directly. Example:: @@ -332,8 +324,8 @@ class Entity(models.Model): u'eggs' """ - - return QuerySetMapper(self.attribute_set.all()) + return mapper(self) + attributes = property(get_attribute_mapper) class Meta: abstract = True @@ -501,10 +493,9 @@ class TreeEntity(Entity, TreeModel): __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 :class:`.TreeAttributeMapper` or :class:`.AttributeMapper` which can be used to retrieve related :class:`Attribute`\ s' values directly. If an :class:`Attribute` with a given key is not related to the :class:`Entity`, then the mapper will check the parent's attributes. Example:: @@ -517,10 +508,13 @@ class TreeEntity(Entity, TreeModel): u'eggs' """ - - if self.parent: - return QuerySetMapper(self.attribute_set.all(), passthrough=self.parent.attributes) - 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