From bd4085d74f6ee6e743455b9be8b512ea41b37588 Mon Sep 17 00:00:00 2001 From: melinath Date: Fri, 11 Jun 2010 13:02:08 -0400 Subject: [PATCH] Reopened treechanges. Moved validation language to validators.py. Abstracted NodeForm to TreeForm. --- admin.py | 70 +++++++++++++++++++++++++++++++--------------- models.py | 32 ++------------------- validators.py | 77 +++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 127 insertions(+), 52 deletions(-) create mode 100644 validators.py diff --git a/admin.py b/admin.py index f63b576..af97bde 100644 --- 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) diff --git a/models.py b/models.py index 878d3d4..d3bf5d4 100644 --- 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 index 0000000..bc41d02 --- /dev/null +++ b/validators.py @@ -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) -- 2.20.1