X-Git-Url: http://git.ithinksw.org/philo.git/blobdiff_plain/ca4d8af448022675e9b2870a0e0149c8257037c4..4fcb99e48c980dfa220d219a093745e58fdf5371:/philo/models/base.py diff --git a/philo/models/base.py b/philo/models/base.py index 436df42..02d8456 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. @@ -34,6 +36,7 @@ class Tag(models.Model): class Titled(models.Model): + # Use of this model is deprecated. title = models.CharField(max_length=255) slug = models.SlugField(max_length=255) @@ -44,7 +47,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 +265,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 +312,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 +325,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 @@ -344,7 +337,7 @@ class TreeManager(models.Manager): def get_with_path(self, path, root=None, absolute_result=True, pathsep='/', field='slug'): """ - If ``absolute_result`` is ``True``, returns the object at ``path`` (starting at ``root``) or raises a :class:`DoesNotExist` exception. Otherwise, returns a tuple containing the deepest object found along ``path`` (or ``root`` if no deeper object is found) and the remainder of the path after that object as a string (or None if there is no remaining path). + If ``absolute_result`` is ``True``, returns the object at ``path`` (starting at ``root``) or raises an :class:`~django.core.exceptions.ObjectDoesNotExist` exception. Otherwise, returns a tuple containing the deepest object found along ``path`` (or ``root`` if no deeper object is found) and the remainder of the path after that object as a string (or None if there is no remaining path). .. note:: If you are looking for something with an exact path, it is faster to use absolute_result=True, unless the path depth is over ~40, in which case the high cost of the absolute query may make a binary search (i.e. non-absolute) faster. @@ -355,7 +348,8 @@ class TreeManager(models.Manager): :param absolute_result: Whether to return an absolute result or do a binary search :param pathsep: The path separator used in ``path`` :param field: The field on the model which should be queried for ``path`` segment matching. - :returns: An instance if absolute_result is True or (instance, remaining_path) otherwise. + :returns: An instance if ``absolute_result`` is ``True`` or an (instance, remaining_path) tuple otherwise. + :raises django.core.exceptions.ObjectDoesNotExist: if no object can be found matching the input parameters. """ @@ -500,10 +494,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:: @@ -516,10 +509,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