Added admin form validation to prevent:
[philo.git] / models.py
index cea36c2..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.core.exceptions import ObjectDoesNotExist
+from django.core.exceptions import ObjectDoesNotExist, ValidationError
 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.conf import settings
 
 
 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)
 
-
 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:
+                       self.validate_parent(parent)
                        path = getattr(parent, field, '?') + pathsep + path
                        parent = parent.parent
                return path
@@ -176,6 +177,30 @@ class TreeModel(models.Model):
        
        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):
@@ -206,6 +231,10 @@ class InheritableTreeEntity(TreeEntity):
        @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, '?')
@@ -237,6 +266,9 @@ class Node(InheritableTreeEntity):
        
        def render_to_response(self, request, path=None, subpath=None):
                return HttpResponseServerError()
+               
+       class Meta:
+               unique_together=(('parent', 'slug',),)
 
 
 class MultiNode(Node):
@@ -261,7 +293,7 @@ class Redirect(Node):
                (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):
@@ -285,8 +317,8 @@ class File(Node):
 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):
@@ -309,7 +341,7 @@ class Template(TreeModel):
                                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):
@@ -354,6 +386,9 @@ class Template(TreeModel):
 
 
 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)