在Django InlineModelAdmin中重写save_model

19 投票
6 回答
12464 浏览
提问于 2025-04-15 15:59

我有一个模型,其中有一个 user 字段,这个字段需要自动填充为当前登录的用户。如果这个 user 字段在一个标准的 ModalAdmin 中,我可以按照指定的方法让它工作,具体可以参考 这里。但是,如果我正在处理的模型是在一个 InlineModelAdmin 中,并且是从另一个模型的记录中保存的,那么它就无法正常工作了。

6 个回答

2

只有你正在编辑的模型的 save_model 会被执行,如果想要更新嵌套的数据,你需要使用 post_save 信号。

(这不是完全重复的问题,但基本上在 这个链接 中回答了类似的问题,讨论的是嵌套模型表单是否会发出 post_save 信号。)

6

我知道我来得有点晚,但这是我的情况和我想到的解决办法,希望对将来其他人有帮助。

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

  • 其中2个是 created_by 类型的字段。(在创建时设置一次)
  • 另外2个是 closed_by 类型的字段。(只有在特定条件下设置)

我使用了 rafadev 提供的答案,并把它做成了一个简单的混入类,这样我就可以在其他地方指定用户字段的名称。

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 中的混入类

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

注意...

...因为这个混入类的代码会在每次每个用户操作时都设置当前登录的用户。如果你只希望在创建时或特定更新时填充这个字段,你需要在模型的保存方法中处理这个问题。以下是一些示例:

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 还比较陌生,但目前这就是我项目中的实现。

15

我觉得这是最好的解决办法。我花了一些时间才找到它……这个答案给了我线索:https://stackoverflow.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

撰写回答