X-Git-Url: http://git.ithinksw.org/philo.git/blobdiff_plain/c492d5a74a124881271104ba4a0957a887723e76..e96369a63407b44b8e363b057194a89dc4c5fd69:/philo/models/base.py?ds=inline diff --git a/philo/models/base.py b/philo/models/base.py index 9ba1ff6..2f798ae 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,7 +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') +__all__ = ('Tag', 'value_content_type_limiter', 'register_value_model', 'unregister_value_model', 'JSONValue', 'ForeignKeyValue', 'ManyToManyValue', 'Attribute', 'Entity', 'TreeEntity', 'SlugTreeEntity') class Tag(models.Model): @@ -35,18 +35,6 @@ class Tag(models.Model): ordering = ('name',) -class Titled(models.Model): - # Use of this model is deprecated. - title = models.CharField(max_length=255) - slug = models.SlugField(max_length=255) - - def __unicode__(self): - return self.title - - class Meta: - 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. value_content_type_limiter = ContentTypeRegistryLimiter() @@ -331,16 +319,29 @@ 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 -class TreeManager(models.Manager): +class TreeEntityBase(MPTTModelBase, EntityBase): + def __new__(meta, name, bases, attrs): + attrs['_mptt_meta'] = MPTTOptions(attrs.pop('MPTTMeta', None)) + cls = EntityBase.__new__(meta, name, bases, attrs) + + return meta.register(cls) + + +class TreeEntityManager(models.Manager): use_for_related_fields = True - def get_with_path(self, path, root=None, absolute_result=True, pathsep='/', field='slug'): + def get_with_path(self, path, root=None, absolute_result=True, pathsep='/', field='pk'): """ 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). @@ -450,10 +451,12 @@ class TreeManager(models.Manager): return find_obj(segments, len(segments)/2 or len(segments)) -class TreeModel(MPTTModel): - objects = TreeManager() +class TreeEntity(Entity, MPTTModel): + """An abstract subclass of Entity which represents a tree relationship.""" + + __metaclass__ = TreeEntityBase + objects = TreeEntityManager() parent = models.ForeignKey('self', related_name='children', null=True, blank=True) - slug = models.SlugField(max_length=255) def get_path(self, root=None, pathsep='/', field='slug'): """ @@ -467,6 +470,9 @@ class TreeModel(MPTTModel): if root == self: return '' + if root is None and self.is_root_node(): + return getattr(self, field, '?') + if root is not None and not self.is_descendant_of(root): raise AncestorDoesNotExist(root) @@ -478,27 +484,6 @@ class TreeModel(MPTTModel): return pathsep.join([getattr(parent, field, '?') for parent in qs]) path = property(get_path) - def __unicode__(self): - return self.path - - class Meta: - unique_together = (('parent', 'slug'),) - abstract = True - - -class TreeEntityBase(MPTTModelBase, EntityBase): - def __new__(meta, name, bases, attrs): - attrs['_mptt_meta'] = MPTTOptions(attrs.pop('MPTTMeta', None)) - cls = EntityBase.__new__(meta, name, bases, attrs) - - return meta.register(cls) - - -class TreeEntity(Entity, TreeModel): - """An abstract subclass of Entity which represents a tree relationship.""" - - __metaclass__ = TreeEntityBase - def get_attribute_mapper(self, mapper=None): """ 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. @@ -520,7 +505,36 @@ class TreeEntity(Entity, TreeModel): else: mapper = AttributeMapper return super(TreeEntity, self).get_attribute_mapper(mapper) - attributes = property(get_attribute_mapper) + + def __unicode__(self): + return self.path + + class Meta: + abstract = True + + +class SlugTreeEntityManager(TreeEntityManager): + def get_with_path(self, path, root=None, absolute_result=True, pathsep='/', field='slug'): + return super(SlugTreeEntityManager, self).get_with_path(path, root, absolute_result, pathsep, field) + + +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) + path = property(get_path) + + def clean(self): + if self.parent 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