X-Git-Url: http://git.ithinksw.org/philo.git/blobdiff_plain/ac54db264df3ba87306945a5494bd89e38266c38..5c919ea0dec366d8d78fee8944577c454f335b2a:/philo/models/base.py diff --git a/philo/models/base.py b/philo/models/base.py index 0218261..e7918f5 100644 --- a/philo/models/base.py +++ b/philo/models/base.py @@ -1,7 +1,7 @@ from django import forms from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes import generic -from django.core.exceptions import ObjectDoesNotExist +from django.core.exceptions import ValidationError from django.core.validators import RegexValidator from django.db import models from django.utils import simplejson as json @@ -16,23 +16,7 @@ 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. - name = models.CharField(max_length=255) - #: A CharField (max length 255) which contains the tag's unique slug. - slug = models.SlugField(max_length=255, unique=True) - - def __unicode__(self): - """Returns the value of the :attr:`name` field""" - return self.name - - class Meta: - app_label = 'philo' - ordering = ('name',) +__all__ = ('value_content_type_limiter', 'register_value_model', 'unregister_value_model', 'JSONValue', 'ForeignKeyValue', 'ManyToManyValue', 'Attribute', 'Entity', 'TreeEntity', 'SlugTreeEntity') #: 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. @@ -44,9 +28,6 @@ def register_value_model(model): value_content_type_limiter.register_class(model) -register_value_model(Tag) - - def unregister_value_model(model): """Registers a model as a valid content type for a :class:`ForeignKeyValue` or :class:`ManyToManyValue` through the :data:`value_content_type_limiter`.""" value_content_type_limiter.unregister_class(model) @@ -319,7 +300,12 @@ class Entity(models.Model): """ return mapper(self) - attributes = property(get_attribute_mapper) + + @property + def attributes(self): + if not hasattr(self, '_attributes'): + self._attributes = self.get_attribute_mapper() + return self._attributes class Meta: abstract = True @@ -453,11 +439,12 @@ class TreeEntity(Entity, MPTTModel): objects = TreeEntityManager() parent = models.ForeignKey('self', related_name='children', null=True, blank=True) - def get_path(self, root=None, pathsep='/', field='slug'): + def get_path(self, root=None, pathsep='/', field='pk', memoize=True): """ :param root: Only return the path since this object. :param pathsep: The path separator to use when constructing an instance's path :param field: The field to pull path information from for each ancestor. + :param memoize: Whether to use memoized results. Since, in most cases, the ancestors of a TreeEntity will not change over the course of an instance's lifetime, this defaults to ``True``. :returns: A string representation of an object's path. """ @@ -465,18 +452,33 @@ class TreeEntity(Entity, MPTTModel): if root == self: return '' - if root is None and self.is_root_node(): + parent_id = getattr(self, "%s_id" % self._mptt_meta.parent_attr) + if getattr(root, 'pk', None) == parent_id: return getattr(self, field, '?') if root is not None and not self.is_descendant_of(root): raise AncestorDoesNotExist(root) + if memoize: + memo_args = (parent_id, getattr(root, 'pk', None), pathsep, getattr(self, field, '?')) + try: + return self._path_memo[memo_args] + except AttributeError: + self._path_memo = {} + except KeyError: + pass + qs = self.get_ancestors(include_self=True) if root is not None: qs = qs.filter(**{'%s__gt' % self._mptt_meta.level_attr: root.get_level()}) - return pathsep.join([getattr(parent, field, '?') for parent in qs]) + path = pathsep.join([getattr(parent, field, '?') for parent in qs]) + + if memoize: + self._path_memo[memo_args] = path + + return path path = property(get_path) def get_attribute_mapper(self, mapper=None): @@ -495,12 +497,11 @@ class TreeEntity(Entity, MPTTModel): """ if mapper is None: - if self.parent: + if getattr(self, "%s_id" % self._mptt_meta.parent_attr): mapper = TreeAttributeMapper else: mapper = AttributeMapper return super(TreeEntity, self).get_attribute_mapper(mapper) - attributes = property(get_attribute_mapper) def __unicode__(self): return self.path @@ -518,10 +519,19 @@ class SlugTreeEntity(TreeEntity): objects = SlugTreeEntityManager() slug = models.SlugField(max_length=255) - def get_path(self, root=None, pathsep='/', field='slug'): - return super(SlugTreeEntity, self).get_path(root, pathsep, field) + def get_path(self, root=None, pathsep='/', field='slug', memoize=True): + return super(SlugTreeEntity, self).get_path(root, pathsep, field, memoize) path = property(get_path) + def clean(self): + if getattr(self, "%s_id" % self._mptt_meta.parent_attr) is None: + try: + self._default_manager.exclude(pk=self.pk).get(slug=self.slug, parent__isnull=True) + except self.DoesNotExist: + pass + else: + raise ValidationError(self.unique_error_message(self.__class__, ('parent', 'slug'))) + class Meta: unique_together = ('parent', 'slug') abstract = True \ No newline at end of file