+from UserDict import DictMixin
+
from django import forms
-from django.db import models
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes import generic
from django.core.exceptions import ObjectDoesNotExist
from django.core.validators import RegexValidator
+from django.db import models
from django.utils import simplejson as json
from django.utils.encoding import force_unicode
+from mptt.models import MPTTModel, MPTTModelBase, MPTTOptions
+
from philo.exceptions import AncestorDoesNotExist
from philo.models.fields import JSONField
-from philo.utils import ContentTypeRegistryLimiter, ContentTypeSubclassLimiter
from philo.signals import entity_class_prepared
+from philo.utils import ContentTypeRegistryLimiter, ContentTypeSubclassLimiter
from philo.validators import json_validator
-from UserDict import DictMixin
-from mptt.models import MPTTModel, MPTTModelBase, MPTTOptions
class Tag(models.Model):
abstract = True
+#: An instance of :class:`ContentTypeRegistryLimiter` which is used to track the content types which can be related to by ForeignKeyValues and ManyToManyValues.
value_content_type_limiter = ContentTypeRegistryLimiter()
-"""
-An instance of :class:`ContentTypeRegistryLimiter` which is used to track the content types which can be related to by ForeignKeyValues and ManyToManyValues.
-
-"""
def register_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`.
-
- """
+ """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.register_class(model)
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`.
-
- """
+ """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)
AttributeValue subclasses are expected to supply access to a clean version of their value through an attribute called "value".
"""
+
+ #: :class:`GenericRelation` to :class:`Attribute`
attribute_set = generic.GenericRelation('Attribute', content_type_field='value_content_type', object_id_field='value_object_id')
- """
- :class:`GenericRelation` to :class:`Attribute`
- """
def set_value(self, value):
"""Given a ``value``, sets the appropriate fields so that it can be correctly stored in the database."""
raise NotImplementedError
:returns: A dictionary mapping field names to formfields.
"""
+
raise NotImplementedError
def construct_instance(self, **kwargs):
abstract = True
+#: An instance of :class:`ContentTypeSubclassLimiter` which is used to track the content types which are considered valid value models for an :class:`Attribute`.
attribute_value_limiter = ContentTypeSubclassLimiter(AttributeValue)
-"""
-An instance of :class:`ContentTypeSubclassLimiter` which is used to track the content types which are considered valid value models for an :class:`Attribute`.
-
-"""
class JSONValue(AttributeValue):
class Attribute(models.Model):
- """
- Represents an arbitrary key/value pair on an arbitrary :class:`Model` where the key consists of word characters and the value is a subclass of :class:`AttributeValue`.
-
- """
+ """Represents an arbitrary key/value pair on an arbitrary :class:`Model` where the key consists of word characters and the value is a subclass of :class:`AttributeValue`."""
entity_content_type = models.ForeignKey(ContentType, related_name='attribute_entity_set', verbose_name='Entity type')
entity_object_id = models.PositiveIntegerField(verbose_name='Entity ID', db_index=True)
- entity = generic.GenericForeignKey('entity_content_type', 'entity_object_id')
- """
- :class:`GenericForeignKey` to anything (generally an instance of an Entity subclass).
- """
+ #: :class:`GenericForeignKey` to anything (generally an instance of an Entity subclass).
+ entity = generic.GenericForeignKey('entity_content_type', 'entity_object_id')
value_content_type = models.ForeignKey(ContentType, related_name='attribute_value_set', limit_choices_to=attribute_value_limiter, verbose_name='Value type', null=True, blank=True)
value_object_id = models.PositiveIntegerField(verbose_name='Value ID', null=True, blank=True, db_index=True)
- value = generic.GenericForeignKey('value_content_type', 'value_object_id')
- """
- :class:`GenericForeignKey` to an instance of a subclass of :class:`AttributeValue` as determined by the :data:`attribute_value_limiter`.
- """
+ #: :class:`GenericForeignKey` to an instance of a subclass of :class:`AttributeValue` as determined by the :data:`attribute_value_limiter`.
+ value = generic.GenericForeignKey('value_content_type', 'value_object_id')
+ #: :class:`CharField` containing a key (up to 255 characters) consisting of alphanumeric characters and underscores.
key = models.CharField(max_length=255, validators=[RegexValidator("\w+")], help_text="Must contain one or more alphanumeric characters or underscores.", db_index=True)
- """
- :class:`CharField` containing a key (up to 255 characters) consisting of alphanumeric characters and underscores.
-
- """
def __unicode__(self):
return u'"%s": %s' % (self.key, self.value)
u'eggs'
>>> entity.attributes['spam']
u'eggs'
+
"""
+
return QuerySetMapper(self.attribute_set.all())
class Meta:
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 :exception:`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 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).
.. 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.
:returns: An instance if absolute_result is True or (instance, remaining_path) otherwise.
"""
+
segments = path.split(pathsep)
# Clean out blank segments. Handles multiple consecutive pathseps.
:returns: A string representation of an object's path.
"""
+
if root == self:
return ''
class TreeEntity(Entity, TreeModel):
- """
- An abstract subclass of Entity which represents a tree relationship.
+ """An abstract subclass of Entity which represents a tree relationship."""
- """
__metaclass__ = TreeEntityBase
@property
u'eggs'
"""
+
if self.parent:
return QuerySetMapper(self.attribute_set.all(), passthrough=self.parent.attributes)
return super(TreeEntity, self).attributes