Как проверить наличие дубликатов записей в админке 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()
Вернуться на верх