Как перепроверить ModelAdmin и его inline?
У меня есть две модели (ModelParent и ModelChild) с одинаковыми полями m2m на предметной модели. 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)
У меня есть одно важное ограничение, хотя поле subject_child ModelChild не должно ссылаться на какой-либо предмет, который subject_parent делает со своим subject_parent.
Итак, если я выберу один и тот же предмет (в subject_parent и subject_child) на странице администратора для обеих моделей, как я могу это проверить? Если изменяется только одно поле, вы проверяете его по БД, но что, если оба изменяются (subject_parent и subject_child)? Как я могу проверить обе формы вместе перед сохранением?
2 ответа
Я унаследовал новый класс с именем 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
поэтому новый класс должен работать точно так же, как ModelAdmin, если вы не измените функцию is_cross_valid(...).
Теперь мой 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 (который должен быть выпущен в ближайшее время) должен пройти валидацию модели, поэтому я надеюсь, что тогда эта проблема будет решена более красиво.
Административные классы не имеют метода clean(). Их формы делают. Каждый класс администратора имеет параметр с именем form. Вы просто расширяете форму по умолчанию (это обычная форма ModelAdmin), реализуете метод clean() и добавляете форму в класс администратора. Пример:
class SomeForm(ModelForm):
#some code
def clean(self):
#some code
class SomeAdminClass(ModelAdmin):
#some code
form = SomeForm
#more code