允许在ModelAdmin上进一步覆盖save_formset

时间:2012-04-27 18:07:42

标签: django django-forms django-admin

这里非常基本的使用场景。我想保存创建对象的用户和上次修改它的用户。但是,它是一个内联模型,所以我当然需要使用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有关。 <!1}}的调用被删除后,代码就可以了。

多长时间,是否有任何方法我都缺少自定义formset保存行为允许子类做自己的覆盖?

2 个答案:

答案 0 :(得分:5)

这是一个doozie。

我玩得很开心,似乎所有动作都发生在django.forms.models.BaseModelFormSet

问题是ModelFormSet.save()删除了与commit标志无关的实例,并且不修改表单以反映已删除的状态。

如果再次调用save(),它将遍历表单,并在ModelChoiceField清除尝试中提取引用的ID并引发无效的选择错误。

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删除表单。

进行了一些测试并且似乎没有任何不良影响。

答案 1 :(得分:0)

@Chris Pratt帮助:

  

我提出了超越习惯的呼吁,而不是其他任何事情,   不认为它实际上会导致formset保存两次。

我试图进一步覆盖save_formset以发送保存后信号。我只是无法理解调用super()只是第二次保存formset。

为了处理super()问题,我使用自定义代码创建了save_formset_now方法,当我通过save_formset子代覆盖admin.ModelAdmin时,我会调用该方法。

这是代码,它似乎也在2016年使用Django 1.10处理删除问题。

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(...)
相关问题