Merge branch 'treechanges'
[philo.git] / models.py
index e32265d..878d3d4 100644 (file)
--- a/models.py
+++ b/models.py
@@ -10,7 +10,7 @@ from django.template import add_to_builtins as register_templatetags
 from django.template import Template as DjangoTemplate
 from django.template import TemplateDoesNotExist
 from django.template import Context, RequestContext
 from django.template import Template as DjangoTemplate
 from django.template import TemplateDoesNotExist
 from django.template import Context, RequestContext
-from django.core.exceptions import ObjectDoesNotExist
+from django.core.exceptions import ObjectDoesNotExist, ValidationError
 try:
        import json
 except ImportError:
 try:
        import json
 except ImportError:
@@ -21,6 +21,7 @@ from django.template.loader_tags import ExtendsNode, ConstantIncludeNode, Includ
 from django.template.loader import get_template
 from django.http import Http404, HttpResponse, HttpResponseServerError, HttpResponseRedirect
 from django.core.servers.basehttp import FileWrapper
 from django.template.loader import get_template
 from django.http import Http404, HttpResponse, HttpResponseServerError, HttpResponseRedirect
 from django.core.servers.basehttp import FileWrapper
+from django.conf import settings
 
 
 def register_value_model(model):
 
 
 def register_value_model(model):
@@ -156,7 +157,6 @@ class TreeManager(models.Manager):
                                return (obj, remainder)
                raise self.model.DoesNotExist('%s matching query does not exist.' % self.model._meta.object_name)
 
                                return (obj, remainder)
                raise self.model.DoesNotExist('%s matching query does not exist.' % self.model._meta.object_name)
 
-
 class TreeModel(models.Model):
        objects = TreeManager()
        parent = models.ForeignKey('self', related_name='children', null=True, blank=True)
 class TreeModel(models.Model):
        objects = TreeManager()
        parent = models.ForeignKey('self', related_name='children', null=True, blank=True)
@@ -166,6 +166,7 @@ class TreeModel(models.Model):
                path = getattr(self, field, '?')
                parent = self.parent
                while parent:
                path = getattr(self, field, '?')
                parent = self.parent
                while parent:
+                       self.validate_parent(parent)
                        path = getattr(parent, field, '?') + pathsep + path
                        parent = parent.parent
                return path
                        path = getattr(parent, field, '?') + pathsep + path
                        parent = parent.parent
                return path
@@ -176,6 +177,30 @@ class TreeModel(models.Model):
        
        class Meta:
                abstract = True
        
        class Meta:
                abstract = True
+               
+       def validate_parents(self, parent=None):
+               if parent == None:
+                       parent = self.parent
+               
+               while parent:
+                       try:
+                               self.validate_parent(parent)
+                               parent = parent.parent
+                       except ObjectDoesNotExist:
+                               return # because it likely means the child doesn't exist 
+                       
+       def validate_parent(self, parent):
+               #Why doesn't this stop the Admin site from saving a model with itself as parent?
+               if self == parent:
+                       raise ValidationError("A %s can't be its own parent." % self.__class__.__name__)
+       
+       def clean(self):
+               super(TreeModel, self).clean()
+               self.validate_parents()
+       
+       def save(self, *args, **kwargs):
+               self.clean()
+               super(TreeModel, self).save(*args, **kwargs)
 
 
 class TreeEntity(TreeModel, Entity):
 
 
 class TreeEntity(TreeModel, Entity):
@@ -195,9 +220,22 @@ class TreeEntity(TreeModel, Entity):
                abstract = True
 
 
                abstract = True
 
 
-class Node(TreeEntity):
+class InheritableTreeEntity(TreeEntity):
        instance_type = models.ForeignKey(ContentType, editable=False)
        
        instance_type = models.ForeignKey(ContentType, editable=False)
        
+       def save(self, force_insert=False, force_update=False):
+               if not hasattr(self, 'instance_type_ptr'):
+                       self.instance_type = ContentType.objects.get_for_model(self.__class__)
+               super(InheritableTreeEntity, self).save(force_insert, force_update)
+       
+       @property
+       def instance(self):
+               return self.instance_type.get_object_for_this_type(id=self.id)
+               
+       def validate_parent(self, parent):
+               if self.instance == parent.instance:
+                       raise ValidationError("A %s can't be its own parent." % self.__class__.__name__)
+       
        def get_path(self, pathsep='/', field='slug'):
                path = getattr(self.instance, field, '?')
                parent = self.parent
        def get_path(self, pathsep='/', field='slug'):
                path = getattr(self.instance, field, '?')
                parent = self.parent
@@ -207,19 +245,30 @@ class Node(TreeEntity):
                return path
        path = property(get_path)
        
                return path
        path = property(get_path)
        
-       def save(self, force_insert=False, force_update=False):
-               if not hasattr(self, 'instance_type_ptr'):
-                       self.instance_type = ContentType.objects.get_for_model(self.__class__)
-               super(Node, self).save(force_insert, force_update)
-       
        @property
        @property
-       def instance(self):
-               return self.instance_type.get_object_for_this_type(id=self.id)
+       def attributes(self):
+               if self.parent:
+                       return QuerySetMapper(self.instance.attribute_set, passthrough=self.parent.instance.attributes)
+               return QuerySetMapper(self.instance.attribute_set)
+
+       @property
+       def relationships(self):
+               if self.parent:
+                       return QuerySetMapper(self.instance.relationship_set, passthrough=self.parent.instance.relationships)
+               return QuerySetMapper(self.instance.relationship_set)
        
        
+       class Meta:
+               abstract = True
+
+
+class Node(InheritableTreeEntity):
        accepts_subpath = False
        
        def render_to_response(self, request, path=None, subpath=None):
                return HttpResponseServerError()
        accepts_subpath = False
        
        def render_to_response(self, request, path=None, subpath=None):
                return HttpResponseServerError()
+               
+       class Meta:
+               unique_together=(('parent', 'slug',),)
 
 
 class MultiNode(Node):
 
 
 class MultiNode(Node):
@@ -244,7 +293,7 @@ class Redirect(Node):
                (302, 'Temporary'),
                (301, 'Permanent'),
        )
                (302, 'Temporary'),
                (301, 'Permanent'),
        )
-       target = models.URLField()
+       target = models.URLField(help_text='Must be a valid, absolute URL (i.e. http://)')
        status_code = models.IntegerField(choices=STATUS_CODES, default=302, verbose_name='redirect type')
        
        def render_to_response(self, request, path=None, subpath=None):
        status_code = models.IntegerField(choices=STATUS_CODES, default=302, verbose_name='redirect type')
        
        def render_to_response(self, request, path=None, subpath=None):
@@ -268,8 +317,8 @@ class File(Node):
 class Template(TreeModel):
        name = models.CharField(max_length=255)
        documentation = models.TextField(null=True, blank=True)
 class Template(TreeModel):
        name = models.CharField(max_length=255)
        documentation = models.TextField(null=True, blank=True)
-       mimetype = models.CharField(max_length=255, null=True, blank=True)
-       code = models.TextField()
+       mimetype = models.CharField(max_length=255, null=True, blank=True, help_text='Default: %s' % settings.DEFAULT_CONTENT_TYPE)
+       code = models.TextField(verbose_name='django template code')
        
        @property
        def origin(self):
        
        @property
        def origin(self):
@@ -292,7 +341,7 @@ class Template(TreeModel):
                                nodes = []
                                for node in nodelist:
                                        try:
                                nodes = []
                                for node in nodelist:
                                        try:
-                                               for nodelist_name in ('nodelist', 'nodelist_loop', 'nodelist_empty', 'nodelist_true', 'nodelist_false'):
+                                               for nodelist_name in ('nodelist', 'nodelist_loop', 'nodelist_empty', 'nodelist_true', 'nodelist_false', 'nodelist_main'):
                                                        if hasattr(node, nodelist_name):
                                                                nodes.extend(nodelist_container_nodes(getattr(node, nodelist_name)))
                                                if isinstance(node, ContainerNode):
                                                        if hasattr(node, nodelist_name):
                                                                nodes.extend(nodelist_container_nodes(getattr(node, nodelist_name)))
                                                if isinstance(node, ContainerNode):
@@ -337,6 +386,9 @@ class Template(TreeModel):
 
 
 class Page(Node):
 
 
 class Page(Node):
+       """
+       Represents an HTML page. The page will have a number of related Contentlets depending on the template selected - but these will appear only after the page has been saved with that template.
+       """
        template = models.ForeignKey(Template, related_name='pages')
        title = models.CharField(max_length=255)
        
        template = models.ForeignKey(Template, related_name='pages')
        title = models.CharField(max_length=255)