Допустив дальнейшего переопределения сохранить formset с на ModelAdmin

довольно простой сценарий использования здесь. Я хочу сохранить пользователя, который создал объект, и пользователя, который последний раз его изменил. Тем не менее, это встроенная модель, поэтому мне, конечно, нужно использовать save_formset. Документы Django имеют следующий пример кода:

class ArticleAdmin(admin.ModelAdmin):
    def save_formset(self, request, form, formset, change):
        instances = formset.save(commit=False)
        for instance in instances:
            instance.user = request.user
            instance.save()
        formset.save_m2m()

дело в том, если вы заметили, так как super никогда не вызывается, это тупик. Если ModelAdmin является подклассом, и этот метод переопределяется таким же образом, вы теряете функциональность, присущую родителю. Этот вопрос поскольку это такой распространенный сценарий использования, что я хочу исключить функциональность, поэтому я создал следующее:

class TrackableInlineAdminMixin(admin.ModelAdmin):
    def save_formset(self, request, form, formset, change):
        instances = formset.save(commit=False)
        for instance in instances:
            if hasattr(instance, 'created_by') and hasattr(instance, 'modified_by'):
                if not instance.pk:
                    instance.created_by = request.user
                instance.modified_by = request.user
            instance.save()
        formset.save_m2m()
        super(TrackableInlineAdminMixin, self).save_formset(request, form, formset, change)

Я нажал на кнопку вызова super по привычке больше, чем что-либо еще, не думая, что это на самом деле заставит formset экономить дважды. Тем не менее, он по-прежнему работает в каждом сценарии, кроме одного: удаление. Как только вы попытаетесь удалить встроенную в Администраторе, вы получите сообщение об ошибке. Ошибка довольно расплывчата и не совсем соответствует моему вопросу здесь, но Я считаю, что это связано с попыткой сохранить formset снова после того, как вы только что удалили один из экземпляров в нем. Код работает просто отлично, когда вызов super удалены.

длинный и короткий, есть ли способ, которым мне не хватает, чтобы настроить поведение сохранения набора форм и разрешить подклассам выполнять собственное переопределение?

2 ответов


это doozie.

я повеселился, ковыряясь вокруг, и, похоже, все действие происходит здесь в django.forms.models.BaseModelFormSet.

проблема в том, что ModelFormSet.save() удаляет экземпляры независимо от commit флаг и не изменяет формы, чтобы отразить удаленном состоянии.

если вы называете save() снова он повторяет над формами и в ModelChoiceField очистка пытается вытащить указанный идентификатор и выдает ошибку недопустимого выбора.

def save_existing_objects(self, commit=True):
    self.changed_objects = []
    self.deleted_objects = []
    if not self.initial_forms:
        return []

    saved_instances = []
    for form in self.initial_forms:
        pk_name = self._pk_field.name
        raw_pk_value = form._raw_value(pk_name)

        # clean() for different types of PK fields can sometimes return
        # the model instance, and sometimes the PK. Handle either.
        pk_value = form.fields[pk_name].clean(raw_pk_value) 
        pk_value = getattr(pk_value, 'pk', pk_value)

        obj = self._existing_object(pk_value)
        if self.can_delete and self._should_delete_form(form):
            self.deleted_objects.append(obj)
            obj.delete()  
            # problem here causes `clean` 6 lines up to fail next round

            # patched line here for future save()
            # to not attempt a second delete
            self.forms.remove(form)

только способ, которым я смог исправить это, - это патч BaseModelFormset.save_existing_objects удалить форму из self.forms если объект удаляется.

провел некоторое тестирование, и, похоже, никаких побочных эффектов нет.


@Chris Pratt помог:

Я нажал на звонок супер по привычке больше, чем что-либо еще, не думая, что это на самом деле заставит formset экономить дважды.

Я пытался дополнительно переопределить save_formset для того, чтобы отправить сообщение сохранить сигнал. Я просто не мог понять, что вызов super() спасал formset с второй раз.

для того, чтобы разобраться с super() проблема, я создал save_formset_now метод с моим пользовательский код, который я вызываю, когда переопределяю save_formset через admin.ModelAdmin дети.

это код, который, похоже, также заботится об удалении проблемы, используя Django 1.10 в 2016 году.

class BaseMixinAdmin(object):
    def save_formset_now(self, request, form, formset, change):
        instances = formset.save(commit=False)
        for obj in formset.deleted_objects:
            obj.delete()
        for instance in instances:
            # *** Start Coding for Custom Needs ***
                ....
            # *** End Coding for Custom Needs ***
            instance.save()
        formset.save_m2m()

class BaseAdmin(BaseMixinAdmin, admin.ModelAdmin):
    def save_formset(self, request, form, formset, change):
        self.save_formset_now(request, form, formset, change)


class ChildAdmin(BaseAdmin):
    def save_formset(self, request, form, formset, change):
        self.save_formset_now(request, form, formset, change)
        my_signal.send(...)