覆盖 Django InlineModelAdmin 上的 save_model

Posted

技术标签:

【中文标题】覆盖 Django InlineModelAdmin 上的 save_model【英文标题】:Override save_model on Django InlineModelAdmin 【发布时间】:2010-12-16 11:19:38 【问题描述】:

我有一个模型,它有一个 user 字段,需要从当前登录的用户自动填充。如果user 字段在标准ModalAdmin 中,我可以让它按指定的here 工作,但如果我正在使用的模型在InlineModelAdmin 中并从Admin 内另一个模型的记录中保存,不会的。

【问题讨论】:

【参考方案1】:

我知道我迟到了,但这是我的情况和我想出的,将来可能对其他人有用。

我有 4 个需要当前登录用户的内联模型。

2 作为created_by 类型字段。 (在创建时设置一次) 和另外 2 个作为 closed_by 类型字段。 (仅在条件下设置)

我使用了rafadev 提供的答案并将其制成了一个简单的mixin,这使我可以在其他地方指定用户字段名称。

forms.py 中的通用表单集

from django.forms.models import BaseInlineFormSet

class SetCurrentUserFormset(forms.models.BaseInlineFormSet):
    """
    This assume you're setting the 'request' and 'user_field' properties
    before using this formset.
    """
    def save_new(self, form, commit=True):
        """
        This is called when a new instance is being created.
        """
        obj = super(SetCurrentUserFormset, self).save_new(form, commit=False)
        setattr(obj, self.user_field, self.request.user)
        if commit:
            obj.save()
        return obj

    def save_existing(self, form, instance, commit=True):
        """
        This is called when updating an instance.
        """
        obj = super(SetCurrentUserFormset, self).save_existing(form, instance, commit=False)
        setattr(obj, self.user_field, self.request.user)
        if commit:
            obj.save()
        return obj

你的 admin.py 中的 Mixin 类

class SetCurrentUserFormsetMixin(object):
    """
    Use a generic formset which populates the 'user_field' model field
    with the currently logged in user.
    """
    formset = SetCurrentUserFormset
    user_field = "user" # default user field name, override this to fit your model

    def get_formset(self, request, obj=None, **kwargs):
        formset = super(SetCurrentUserFormsetMixin, self).get_formset(request, obj, **kwargs)
        formset.request = request
        formset.user_field = self.user_field
        return formset

如何使用

class YourModelInline(SetCurrentUserFormsetMixin, admin.TabularInline):
    model = YourModel
    fields = ['description', 'closing_user', 'closing_date']
    readonly_fields = ('closing_user', 'closing_date')
    user_field = 'closing_user' # overriding only if necessary

小心...

...因为这个 mixin 代码每次都会为每个用户设置当前登录的用户。如果您需要仅在创建或特定更新时填充该字段,则需要在模型保存方法中处理此问题。下面是一些例子:

class UserOnlyOnCreationExampleModel(models.Model):
    # your fields
    created_by = # user field...
    comment = ...

    def save(self, *args, **kwargs):
        if not self.id:
            # on creation, let the user field populate
            self.date = datetime.today().date()
            super(UserOnlyOnCreationExampleModel, self).save(*args, **kwargs)
        else:
            # on update, remove the user field from the list
            super(UserOnlyOnCreationExampleModel, self).save(update_fields=['comment',], *args, **kwargs)

或者,如果您只需要设置特定字段的用户(如布尔字段closed):

def save(self, *args, **kwargs):

    if self.closed and self.closing_date is None:
        self.closing_date = datetime.today().date()
        # let the closing_user field set
    elif not self.closed :
        self.closing_date = None
        self.closing_user = None # unset it otherwise

    super(YourOtherModel, self).save(*args, **kwargs)  # Call the "real" save() method.

这个代码可能会变得更通用,因为我对 python 还很陌生,但这就是我现在项目中的内容。

【讨论】:

【参考方案2】:

这是我认为最好的解决方案。我花了一段时间才找到它......这个答案给了我线索:https://***.com/a/24462173/2453104

在您的 admin.py 上:

class YourInline(admin.TabularInline):
    model = YourInlineModel
    formset = YourInlineFormset

    def get_formset(self, request, obj=None, **kwargs):
        formset = super(YourInline, self).get_formset(request, obj, **kwargs)
        formset.request = request
        return formset

在您的 forms.py 上:

class YourInlineFormset(forms.models.BaseInlineFormSet):
    def save_new(self, form, commit=True):
        obj = super(YourInlineFormset, self).save_new(form, commit=False)
        # here you can add anything you need from the request
        obj.user = self.request.user

        if commit:
            obj.save()

        return obj

【讨论】:

这确实是最简单最好的方法 我把它做成了一个 mixin,简化了它在多个内联模型管理器上的使用。 我已经做了,作为下面的答案,将最初的想法/答案归功于您。 ***.com/a/30387234/1218980【参考方案3】:

我尝试在一个内联模型中填充一个用户字段时遇到了类似的问题。就我而言,父模型还定义了用户字段,因此我在子模型上覆盖了save,如下所示:

class inline_model:
    parent = models.ForeignKey(parent_model)
    modified_by = models.ForeignKey(User,editable=False) 
    def save(self,*args,**kwargs):
        self.modified_by = self.parent.modified_by
        super(inline_model,self).save(*args,**kwargs)

用户字段最初是通过覆盖父模型的 ModelAdmin 中的 save_model 并分配来自动填充到父模型上的

obj.modified_by = request.user

请记住,如果您还有一个独立的子模型管理页面,您将需要一些其他机制来保持父和子 modified_by 字段同步(例如,您可以覆盖子 ModelAdmin 上的 save_model 和在子级调用save 之前更新/保存父级上的 modified_by 字段)。

如果用户不在父模型中,我还没有找到处理此问题的好方法。我不知道如何使用信号(例如post_save)检索request.user,但也许其他人可以提供更多详细信息。

【讨论】:

【参考方案4】:

仅执行您正在编辑的模型的save_model,而不是您需要使用post_save 信号来更新内联数据。

(不是真正的重复,但在Do inline model forms emmit post_save signals? (django) 中回答了基本相同的问题)

【讨论】:

【参考方案5】:

您是否尝试过按照documentation 中的说明在管理员中实施自定义验证?覆盖模型表单上的 clean_user() 函数可能会为您解决问题。

想到另一个更复杂的选择。您可以覆盖呈现更改表单的管理模板。覆盖更改表单将允许您构建自定义模板标签,将登录用户传递给 ModelForm。然后,您可以在自动设置用户的模型表单上编写自定义 init 函数。 This answer 提供了一个很好的例子来说明如何做到这一点,您在问题中引用的 b-list 上的链接也是如此。

【讨论】:

【参考方案6】:

其他模型是否拯救了用户?在这种情况下,您可以使用post_save 信号将该信息添加到内联模型的集合中。

【讨论】:

以上是关于覆盖 Django InlineModelAdmin 上的 save_model的主要内容,如果未能解决你的问题,请参考以下文章

Django:覆盖每个应用程序而不是每个项目的“不可覆盖”管理模板?

在 django 中覆盖管理 css

覆盖 Django 小部件默认模板

覆盖 Django 用户模型

Django:覆盖和扩展应用程序模板

Django 单元测试 查看覆盖率