Reopened treechanges. Moved validation language to validators.py. Abstracted NodeForm...
authormelinath <stephen.r.burrows@gmail.com>
Fri, 11 Jun 2010 17:02:08 +0000 (13:02 -0400)
committermelinath <stephen.r.burrows@gmail.com>
Mon, 14 Jun 2010 19:38:32 +0000 (15:38 -0400)
admin.py
models.py
validators.py [new file with mode: 0644]

index f63b576..af97bde 100644 (file)
--- a/admin.py
+++ b/admin.py
@@ -9,6 +9,7 @@ from django.utils.html import escape
 from django.utils.text import truncate_words
 from models import *
 from django.core.exceptions import ValidationError, ObjectDoesNotExist
+from validators import TreeParentValidator, TreePositionValidator
 
 
 class AttributeInline(generic.GenericTabularInline):
@@ -46,6 +47,38 @@ class CollectionAdmin(admin.ModelAdmin):
        inlines = [CollectionMemberInline]
 
 
+class TreeForm(forms.ModelForm):
+       def __init__(self, *args, **kwargs):
+               super(TreeForm, self).__init__(*args, **kwargs)
+               instance = self.instance
+               instance_class=self.get_instance_class()
+               try:
+                       self.fields['parent'].queryset = instance_class.objects.exclude(id=instance.id)
+               except ObjectDoesNotExist:
+                       pass
+               self.fields['parent'].validators = [TreeParentValidator(*self.get_validator_args())]
+       
+       def get_instance_class(self):
+               return self.instance.__class__
+               
+       def get_validator_args(self):
+               return [self.instance]
+       
+       def clean(self):
+               cleaned_data = self.cleaned_data
+               
+               try:
+                       parent = cleaned_data['parent']
+                       slug = cleaned_data['slug']
+                       obj_class = self.get_instance_class()
+                       tpv = TreePositionValidator(parent, slug, obj_class)
+                       tpv(self.instance)
+               except KeyError:
+                       pass
+               
+               return cleaned_data
+
+
 class TemplateAdmin(admin.ModelAdmin):
        prepopulated_fields = {'slug': ('name',)}
        fieldsets = (
@@ -67,6 +100,7 @@ class TemplateAdmin(admin.ModelAdmin):
        save_on_top = True
        save_as = True
        list_display = ('__unicode__', 'slug', 'get_path',)
+       form = TreeForm
 
 
 class ModelLookupWidget(forms.TextInput):
@@ -95,41 +129,30 @@ 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
+
+class NodeForm(TreeForm):
+       def get_instance_class(self):
+               return self.instance.node_ptr.__class__
                
-               return cleaned_data
+       def get_validator_args(self):
+               return [self.instance, 'instance']
+
 
 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',)}
        fieldsets = (
@@ -209,15 +232,18 @@ class PageAdmin(EntityAdmin):
                                        contentreference.content = content
                                        contentreference.save()
 
+
 class RedirectAdmin(admin.ModelAdmin):
        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, FileAdmin)
index 878d3d4..d3bf5d4 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, ValidationError
+from django.core.exceptions import ObjectDoesNotExist
 try:
        import json
 except ImportError:
@@ -157,6 +157,7 @@ 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,7 +167,6 @@ 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,30 +177,6 @@ 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):
@@ -231,10 +207,6 @@ 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, '?')
diff --git a/validators.py b/validators.py
new file mode 100644 (file)
index 0000000..bc41d02
--- /dev/null
@@ -0,0 +1,77 @@
+from django.core.exceptions import ValidationError
+from django.utils.translation import ugettext_lazy as _
+
+
+class TreeParentValidator(object):
+       """
+       constructor takes instance and parent_attr, where instance is the model
+       being validated and parent_attr is where to look on that parent for the
+       comparison.
+       """
+       #message = _("A tree element can't be its own parent.")
+       code = 'invalid'
+       
+       def __init__(self, instance, parent_attr=None, message=None, code=None):
+               self.instance = instance
+               self.parent_attr = parent_attr
+               self.static_message = message
+               if code is not None:
+                       self.code = code
+       
+       def __call__(self, value):
+               """
+               Validates that the self.instance is not found in the parent tree of
+               the node given as value.
+               """
+               parent = value
+               
+               while parent:
+                       comparison=self.get_comparison(parent)
+                       if comparison == self.instance:
+                               # using (self.message, code=self.code) results in the admin interface
+                               # screwing with the error message and making it be 'Enter a valid value'
+                               raise ValidationError(self.message)
+                       parent=parent.parent
+       
+       def get_comparison(self, parent):
+               if self.parent_attr and hasattr(parent, self.parent_attr):
+                       return getattr(parent, self.parent_attr)
+               
+               return parent
+       
+       def get_message(self):
+               return self.static_message or _(u"A %s can't be its own parent." % self.instance.__class__.__name__)
+       message = property(get_message)
+       
+class TreePositionValidator(object):
+       code = 'invalid'
+       
+       def __init__(self, parent, slug, obj_class, message=None, code=None):
+               self.parent = parent
+               self.slug = slug
+               self.obj_class = obj_class
+               self.static_message = message
+                       
+               if code is not None:
+                       self.code = code
+       
+       def __call__(self, value):
+               """
+               Validates that there is no obj of obj_class with the same position
+               as the compared obj (value) but a different id.
+               """
+               if not isinstance(value, self.obj_class):
+                       raise ValidationError(_(u"The value must be an instance of %s." % self.obj_class.__name__))
+               
+               try:
+                       obj = self.obj_class.objects.get(slug=self.slug, parent=self.parent)
+                       
+                       if obj.id != value.id:
+                               raise ValidationError(self.message)
+                               
+               except self.obj_class.DoesNotExist:
+                       pass
+       
+       def get_message(self):
+               return self.static_message or _(u"A %s with that path (parent and slug) already exists." % self.obj_class.__name__)
+       message = property(get_message)