.. autoclass:: Entity
:members:
-.. autoclass:: TreeManager
+.. autoclass:: TreeEntityManager
:members:
.. autoclass:: TreeEntity
.. attribute:: objects
- An instance of :class:`TreeManager`.
+ An instance of :class:`TreeEntityManager`.
.. automethod:: get_path
\ No newline at end of file
return db_field.formfield(**kwargs)
-class TreeAdmin(MPTTModelAdmin):
- pass
-
-
-class TreeEntityAdmin(EntityAdmin, TreeAdmin):
+class TreeEntityAdmin(EntityAdmin, MPTTModelAdmin):
pass
from django.conf import settings
from django.contrib import admin
-from philo.admin.base import COLLAPSE_CLASSES, TreeAdmin
+from philo.admin.base import COLLAPSE_CLASSES, TreeEntityAdmin
from philo.admin.forms.containers import *
from philo.admin.nodes import ViewAdmin
from philo.models.pages import Page, Template, Contentlet, ContentReference
return super(PageAdmin, self).response_add(request, obj, post_url_continue)
-class TemplateAdmin(TreeAdmin):
+class TemplateAdmin(TreeEntityAdmin):
prepopulated_fields = {'slug': ('name',)}
fieldsets = (
(None, {
--- /dev/null
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+
+ # Deleting field 'NavigationItem.slug'
+ db.delete_column('shipherd_navigationitem', 'slug')
+
+
+ def backwards(self, orm):
+
+ # User chose to not deal with backwards NULL issues for 'NavigationItem.slug'
+ raise RuntimeError("Cannot reverse this migration. 'NavigationItem.slug' and its values cannot be restored.")
+
+
+ models = {
+ 'contenttypes.contenttype': {
+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+ },
+ 'philo.attribute': {
+ 'Meta': {'unique_together': "(('key', 'entity_content_type', 'entity_object_id'), ('value_content_type', 'value_object_id'))", 'object_name': 'Attribute'},
+ 'entity_content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'attribute_entity_set'", 'to': "orm['contenttypes.ContentType']"}),
+ 'entity_object_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
+ 'value_content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'attribute_value_set'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
+ 'value_object_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'})
+ },
+ 'philo.node': {
+ 'Meta': {'object_name': 'Node'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'level': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+ 'lft': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+ 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['philo.Node']"}),
+ 'rght': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '255', 'db_index': 'True'}),
+ 'tree_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+ 'view_content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'node_view_set'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
+ 'view_object_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'})
+ },
+ 'shipherd.navigation': {
+ 'Meta': {'unique_together': "(('node', 'key'),)", 'object_name': 'Navigation'},
+ 'depth': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '3'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
+ 'node': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'navigation_set'", 'to': "orm['philo.Node']"})
+ },
+ 'shipherd.navigationitem': {
+ 'Meta': {'object_name': 'NavigationItem'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'level': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+ 'lft': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+ 'navigation': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'roots'", 'null': 'True', 'to': "orm['shipherd.Navigation']"}),
+ 'order': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '0'}),
+ 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['shipherd.NavigationItem']"}),
+ 'reversing_parameters': ('philo.models.fields.JSONField', [], {'blank': 'True'}),
+ 'rght': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+ 'target_node': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'shipherd_navigationitem_related'", 'null': 'True', 'to': "orm['philo.Node']"}),
+ 'text': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
+ 'tree_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+ 'url_or_subpath': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'})
+ }
+ }
+
+ complete_apps = ['shipherd']
from django.db import models
from django.forms.models import model_to_dict
-from philo.models.base import TreeEntity, TreeManager, Entity
+from philo.models.base import TreeEntity, TreeEntityManager, Entity
from philo.models.nodes import Node, TargetURLModel
unique_together = ('node', 'key')
-class NavigationItemManager(TreeManager):
+class NavigationItemManager(TreeEntityManager):
use_for_related = True
def get_query_set(self):
self._initial_data = model_to_dict(self)
self._is_cached = False
- def __unicode__(self):
- return self.get_path(field='text', pathsep=u' › ')
+ def get_path(self, root=None, pathsep=u' › ', field='text'):
+ return super(NavigationItem, self).get_path(root, pathsep, field)
+ path = property(get_path)
def clean(self):
super(NavigationItem, self).clean()
--- /dev/null
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+class Migration(SchemaMigration):
+
+ def forwards(self, orm):
+
+ # Adding unique constraint on 'Node', fields ['slug', 'parent']
+ db.create_unique('philo_node', ['slug', 'parent_id'])
+
+ # Adding unique constraint on 'Template', fields ['slug', 'parent']
+ db.create_unique('philo_template', ['slug', 'parent_id'])
+
+
+ def backwards(self, orm):
+
+ # Removing unique constraint on 'Template', fields ['slug', 'parent']
+ db.delete_unique('philo_template', ['slug', 'parent_id'])
+
+ # Removing unique constraint on 'Node', fields ['slug', 'parent']
+ db.delete_unique('philo_node', ['slug', 'parent_id'])
+
+
+ models = {
+ 'contenttypes.contenttype': {
+ 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+ 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+ },
+ 'philo.attribute': {
+ 'Meta': {'unique_together': "(('key', 'entity_content_type', 'entity_object_id'), ('value_content_type', 'value_object_id'))", 'object_name': 'Attribute'},
+ 'entity_content_type': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'attribute_entity_set'", 'to': "orm['contenttypes.ContentType']"}),
+ 'entity_object_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'key': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
+ 'value_content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'attribute_value_set'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
+ 'value_object_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'})
+ },
+ 'philo.collection': {
+ 'Meta': {'object_name': 'Collection'},
+ 'description': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+ },
+ 'philo.collectionmember': {
+ 'Meta': {'object_name': 'CollectionMember'},
+ 'collection': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'members'", 'to': "orm['philo.Collection']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'index': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
+ 'member_content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+ 'member_object_id': ('django.db.models.fields.PositiveIntegerField', [], {})
+ },
+ 'philo.contentlet': {
+ 'Meta': {'object_name': 'Contentlet'},
+ 'content': ('philo.models.fields.TemplateField', [], {}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
+ 'page': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'contentlets'", 'to': "orm['philo.Page']"})
+ },
+ 'philo.contentreference': {
+ 'Meta': {'object_name': 'ContentReference'},
+ 'content_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'}),
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255', 'db_index': 'True'}),
+ 'page': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'contentreferences'", 'to': "orm['philo.Page']"})
+ },
+ 'philo.file': {
+ 'Meta': {'object_name': 'File'},
+ 'file': ('django.db.models.fields.files.FileField', [], {'max_length': '100'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'mimetype': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+ },
+ 'philo.foreignkeyvalue': {
+ 'Meta': {'object_name': 'ForeignKeyValue'},
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']", 'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'object_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True', 'null': 'True', 'blank': 'True'})
+ },
+ 'philo.jsonvalue': {
+ 'Meta': {'object_name': 'JSONValue'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'value': ('philo.models.fields.JSONField', [], {'default': "'null'", 'db_index': 'True'})
+ },
+ 'philo.manytomanyvalue': {
+ 'Meta': {'object_name': 'ManyToManyValue'},
+ 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']", 'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'values': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['philo.ForeignKeyValue']", 'null': 'True', 'blank': 'True'})
+ },
+ 'philo.node': {
+ 'Meta': {'unique_together': "(('parent', 'slug'),)", 'object_name': 'Node'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'level': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+ 'lft': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+ 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['philo.Node']"}),
+ 'rght': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '255', 'db_index': 'True'}),
+ 'tree_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+ 'view_content_type': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'node_view_set'", 'null': 'True', 'to': "orm['contenttypes.ContentType']"}),
+ 'view_object_id': ('django.db.models.fields.PositiveIntegerField', [], {'null': 'True', 'blank': 'True'})
+ },
+ 'philo.page': {
+ 'Meta': {'object_name': 'Page'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'template': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'pages'", 'to': "orm['philo.Template']"}),
+ 'title': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+ },
+ 'philo.redirect': {
+ 'Meta': {'object_name': 'Redirect'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'reversing_parameters': ('philo.models.fields.JSONField', [], {'blank': 'True'}),
+ 'status_code': ('django.db.models.fields.IntegerField', [], {'default': '302'}),
+ 'target_node': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'philo_redirect_related'", 'null': 'True', 'to': "orm['philo.Node']"}),
+ 'url_or_subpath': ('django.db.models.fields.CharField', [], {'max_length': '200', 'blank': 'True'})
+ },
+ 'philo.tag': {
+ 'Meta': {'ordering': "('name',)", 'object_name': 'Tag'},
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '255', 'db_index': 'True'})
+ },
+ 'philo.template': {
+ 'Meta': {'unique_together': "(('parent', 'slug'),)", 'object_name': 'Template'},
+ 'code': ('philo.models.fields.TemplateField', [], {}),
+ 'documentation': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+ 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+ 'level': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+ 'lft': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+ 'mimetype': ('django.db.models.fields.CharField', [], {'default': "'text/html'", 'max_length': '255'}),
+ 'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+ 'parent': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "'children'", 'null': 'True', 'to': "orm['philo.Template']"}),
+ 'rght': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'}),
+ 'slug': ('django.db.models.fields.SlugField', [], {'max_length': '255', 'db_index': 'True'}),
+ 'tree_id': ('django.db.models.fields.PositiveIntegerField', [], {'db_index': 'True'})
+ }
+ }
+
+ complete_apps = ['philo']
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()
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).
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'):
"""
return ''
if root is None and self.is_root_node():
- return self.slug
+ return getattr(self, field, '?')
if root is not None and not self.is_descendant_of(root):
raise AncestorDoesNotExist(root)
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.
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)
+
class Meta:
+ unique_together = ('parent', 'slug')
abstract = True
\ No newline at end of file
from django.utils.encoding import smart_str
from philo.exceptions import MIDDLEWARE_NOT_CONFIGURED, ViewCanNotProvideSubpath, ViewDoesNotProvideSubpaths
-from philo.models.base import TreeEntity, Entity, register_value_model
+from philo.models.base import SlugTreeEntity, Entity, register_value_model
from philo.models.fields import JSONField
from philo.utils import ContentTypeSubclassLimiter
from philo.utils.entities import LazyPassthroughAttributeMapper
_view_content_type_limiter = ContentTypeSubclassLimiter(None)
-class Node(TreeEntity):
+class Node(SlugTreeEntity):
"""
:class:`Node`\ s are the basic building blocks of a website using Philo. They define the URL hierarchy and connect each URL to a :class:`View` subclass instance which is used to generate an HttpResponse.
return '%s%s%s%s' % (domain, root_url, path, subpath)
- class Meta:
+ class Meta(SlugTreeEntity.Meta):
app_label = 'philo'
from django.template.loader_tags import BlockNode, ExtendsNode, BlockContext
from django.utils.datastructures import SortedDict
-from philo.models.base import TreeModel, register_value_model
+from philo.models.base import SlugTreeEntity, register_value_model
from philo.models.fields import TemplateField
from philo.models.nodes import View
from philo.signals import page_about_to_render_to_string, page_finished_rendering_to_string
return nodelists
-class Template(TreeModel):
+class Template(SlugTreeEntity):
"""Represents a database-driven django template."""
#: The name of the template. Used for organization and debugging.
name = models.CharField(max_length=255)
"""Returns the value of the :attr:`name` field."""
return self.name
- class Meta:
+ class Meta(SlugTreeEntity.Meta):
app_label = 'philo'