Как проверить наличие дубликатов записей в админке Django inline?
Как выполнить проверку валидности многострочных форм в админке Django?
Например, у меня есть простая модель Parent/Child, с интерфейсом администратора, показывающим детей в инлайн-таблице на странице изменения администратора родителя.
У каждого ребенка есть поле "имя", которое должно быть уникальным.
В дочерней модели я реализовал метод clean() для обеспечения выполнения этого правила, вызывая forms.ValidationError, чтобы ошибка отображалась в удобном для пользователя виде в пользовательском интерфейсе администратора. Этот метод вызывается из метода full_clean() модели, который вызывается Django admin во время шага валидации для каждой инлайн-формы. Таким образом, если пользователь попытается создать дочернюю запись, эта проверка кэширует ошибку.
Однако, поскольку Django выполняет проверку для каждой инлайн-таблицы отдельно перед сохранением записей, он не кэширует дубликаты в новых данных. Поэтому, если пользователь создает две новые строки и вводит дубликаты имен в каждую из этих строк, они проходят проверку, но когда Django переходит к сохранению записей, он сталкивается с исключением, которое теперь обрабатывается как очень недружелюбная для пользователя ошибка 500.
Есть ли простой способ исправить это? Просматривая код Django, я не вижу ничего очевидного в _changeform_view(), где находится большая часть логики валидации формы администратора.
Предположительно, я бы переопределил что-то в ModelForm инлайна, но даже метод clean в нем проверяет поля только для одной записи, а не для нескольких записей.
У меня у самого была похожая проблема, и я потратил довольно много времени на ее решение. Я не уверен, нашли ли вы ответ ( поскольку прошло уже 5 месяцев с тех пор, как вы спрашивали), но в любом случае я думаю, что поделиться своим решением может быть полезно, так что вот, пожалуйста:
Я также пытался переопределить метод clean() различных классов, но безрезультатно. Затем я нашел эту страницу (Customize Save In Django Admin Inline Form), где предлагалось переопределить методы save_new_objects и save_existing_objects в классе CustomInLineFormSet.
Итак, в файле admin.py я добавил следующий метод в класс CustomInLineFormSet (или в вашем случае это будет предназначено для модели Child):
class ChildInLineFormSet(BaseInLineFormSet):
def save_new_objects(self, commit=True):
saved_instances = super(ChildInLineFormSet, self).save_new_objects(commit)
if commit:
for instance in saved_instances:
instance.delete()
try:
ChildModel.objects.get(name=instance.name)
except ChildModel.DoesNotExist:
instance.save()
else:
saved_instances.remove(instance)
return saved_instances
Кроме того, везде, где вы объявили свой класс InLine, вы также должны добавить определение для поля formset:
class ChildInLine(admin.StackedInline):
formset = ChildInLineFormSet #add this to whatever you already have
Надеюсь, это поможет!
EDIT: Я еще немного покопался в этом вопросе: Использование пользовательского набора форм НЕ необходимо в конце концов.
Вы можете переопределить метод save_formset() в классе администратора и получить тот же результат без необходимости сохранения моделей в базе данных:
class ParentAdmin(admin.ModelAdmin):
def save_formset(self, request, form, formset, change):
instances = formset.save(commit=False)
unique_names = []
for obj in formset.deleted_objects:
obj.delete()
for instance in instances:
if (instance.name) in unique_names:
instance.delete()
continue
unique_names.append(instance.name)
instance.save()
formset.save_m2m()