django - 如何交叉检查ModelAdmin及其内联?
我有两个模型(ModelParent 和 ModelChild),它们在 Subject 模型上有相同的多对多字段。ModelChild 有一个指向 ModelParent 的外键,并且在管理页面上,ModelChild 是作为 ModelParent 的内联显示的。
### models.py ###
class Subject(Models.Model):
pass
class ModelParent(models.Model):
subjects_parent = ManyToManyField(Subject)
class ModelChild(models.Model):
parent = ForeignKey(ModelParent)
subjects_child = ManyToManyField(Subject)
### admin.py ###
class ModelChildInline(admin.TabularInline):
model = ModelChild
class ModelParentAdmin(admin.ModelAdmin):
inlines = [ModelChildInline]
admin.site.register(ModelParent, ModelParentAdmin)
不过,我有一个重要的限制,ModelChild 的 subjects_child 字段不能引用 ModelParent 的 subjects_parent 字段所引用的任何主题。
所以,如果我在管理页面上为这两个模型选择了相同的主题(在 subject_parent 和 subject_child 中),我该如何验证这一点呢?如果只有一个字段发生变化,你可以把它和数据库进行验证,但如果两个字段都发生变化(subject_parent 和 subject_child)呢?我该如何在保存之前一起验证这两个表单?
2 个回答
管理员类是没有clean()这个方法的,但它们的表单是有的。每个管理员类都有一个叫做form的参数。你只需要扩展默认的表单(这就是普通的ModelAdmin表单),实现clean()方法,然后把这个表单添加到管理员类里。
举个例子:
class SomeForm(ModelForm):
#some code
def clean(self):
#some code
class SomeAdminClass(ModelAdmin):
#some code
form = SomeForm
#more code
我创建了一个新的类,叫做ModelAdminWithInline,它是从admin.ModelAdmin继承来的。我对add_view(...)和change_view(...)这两个方法进行了修改,让它们调用一个名为is_cross_valid(self, form, formsets)的函数,这个函数可以一起验证所有的表单。
#...
if all_valid(formsets) and form_validated:
#...
这两个方法的内容改成了:
#...
formsets_validated = all_valid(formsets)
cross_validated = self.is_cross_valid(form, formsets)
if formsets_validated and form_validated and cross_validated:
#...
新定义的is_cross_valid(...)函数是这样写的:
def is_cross_valid(self, form, formsets):
return True
所以,如果你不修改is_cross_valid(...)这个函数,新类的工作方式应该和ModelAdmin完全一样。
现在我的admin.py文件看起来是这样的:
###admin.py###
class ModelAdminWithInline(admin.ModelAdmin):
def is_cross_valid(self, form, formsets):
return True
def add_view(self, request, form_url='', extra_context=None):
#modified code
def change_view(self, request, object_id, extra_context=None):
#modified code
class ModelChildInline(admin.TabularInline):
model = ModelChild
class ModelParentAdmin(ModelAdminWithInline):
inlines = [ModelChildInline]
def is_cross_valid(self, form, formsets):
#Do some cross validation on forms
#For example, here is my particular validation:
valid = True
if hasattr(form, 'cleaned_data'):
subjects_parent = form.cleaned_data.get("subjects_parent")
#You can access forms from formsets like this:
for formset in formsets:
for formset_form in formset.forms:
if hasattr(formset_form, 'cleaned_data'):
subjects_child = formset_form.cleaned_data.get("subjects_child")
delete_form = formset_form.cleaned_data.get("DELETE")
if subjects_child and (delete_form == False):
for subject in subjects_child:
if subject in subjects_parent:
valid = False
#From here you can still report errors like in regular forms:
if "subjects_child" in formset_form.cleaned_data.keys():
formset_form._errors["subjects_child"] = ErrorList([u"Subject %s is already selected in parent ModelParent" % subject])
del formset_form.cleaned_data["subjects_child"]
else:
formset_form._errors["subjects_child"] += ErrorList(u"Subject %s is already selected in parent ModelParent" % subject])
#return True on success or False otherwise.
return valid
admin.site.register(ModelParent, ModelParentAdmin)
这个解决方案有点小技巧,但确实有效 :)。错误信息的显示方式和普通的ModelForm和ModelAdmin类是一样的。Django 1.2(应该很快就会发布)将会有模型验证功能,所以我希望到时候这个问题能得到更好的解决。