Added admin form validation to prevent:
authormelinath <stephen.r.burrows@gmail.com>
Thu, 10 Jun 2010 22:02:52 +0000 (18:02 -0400)
committermelinath <stephen.r.burrows@gmail.com>
Mon, 14 Jun 2010 19:38:32 +0000 (15:38 -0400)
1. a treemodel being its own parent (uses a validate_parents method added to the model.)
2. a node having the same path (i.e. parent+slug) as another
Also added a ModelAdmin for the File model.

admin.py
models.py

index 73dea0a..f63b576 100644 (file)
--- a/admin.py
+++ b/admin.py
@@ -8,6 +8,7 @@ from django.utils.safestring import mark_safe
 from django.utils.html import escape
 from django.utils.text import truncate_words
 from models import *
+from django.core.exceptions import ValidationError, ObjectDoesNotExist
 
 
 class AttributeInline(generic.GenericTabularInline):
@@ -94,6 +95,40 @@ class ModelLookupWidget(forms.TextInput):
                                pass
                return mark_safe(output)
 
+class NodeForm(forms.ModelForm):
+       
+       def __init__(self, *args, **kwargs):
+               super(NodeForm, self).__init__(*args, **kwargs)
+               instance = self.instance
+               try:
+                       self.fields['parent'].queryset = instance.node_ptr.__class__.objects.exclude(id=instance.id)
+               except ObjectDoesNotExist:
+                       pass
+       
+       def clean(self):
+               cleaned_data = self.cleaned_data
+               parent = self.cleaned_data['parent']
+               self.instance.validate_parents(parent)
+                       
+               try:
+                       Node.objects.get(slug=self.cleaned_data['slug'], parent=parent)
+                       raise ValidationError("A node with that path (parent and slug) already exists.")
+               except ObjectDoesNotExist:
+                       pass
+               
+               return cleaned_data
+
+class PageAdminForm(NodeForm):
+       class Meta:
+               model=Page
+
+class RedirectAdminForm(NodeForm):
+       class Meta:
+               model=Redirect
+               
+class FileAdminForm(NodeForm):
+       class Meta:
+               model=File
 
 class PageAdmin(EntityAdmin):
        prepopulated_fields = {'slug': ('title',)}
@@ -109,6 +144,7 @@ class PageAdmin(EntityAdmin):
        list_display = ('title', 'path', 'template')
        list_filter = ('template',)
        search_fields = ['title', 'slug', 'contentlets__content']
+       form = PageAdminForm
        
        def get_fieldsets(self, request, obj=None, **kwargs):
                fieldsets = list(self.fieldsets)
@@ -174,11 +210,16 @@ class PageAdmin(EntityAdmin):
                                        contentreference.save()
 
 class RedirectAdmin(admin.ModelAdmin):
-       list_display=('slug', 'target', 'status_code',)
+       list_display=('slug', 'target', 'path', 'status_code',)
        list_filter=('status_code',)
+       form = RedirectAdminForm
+
+class FileAdmin(admin.ModelAdmin):
+       form=FileAdminForm
+       list_display=('slug', 'mimetype', 'path', 'file',)
 
 admin.site.register(Collection, CollectionAdmin)
 admin.site.register(Redirect, RedirectAdmin)
-admin.site.register(File)
+admin.site.register(File, FileAdmin)
 admin.site.register(Page, PageAdmin)
 admin.site.register(Template, TemplateAdmin)
index 6eac9de..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:
@@ -157,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)
@@ -167,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
@@ -177,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):
@@ -207,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, '?')
@@ -238,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):