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