Django:保存模型时填充用户 ID

Posted

技术标签:

【中文标题】Django:保存模型时填充用户 ID【英文标题】:Django: Populate user ID when saving a model 【发布时间】:2010-10-26 03:21:10 【问题描述】:

我有一个带有 created_by 字段的模型,该字段链接到标准 Django 用户模型。保存模型时,我需要使用当前用户的 ID 自动填充它。我不能在 Admin 层执行此操作,因为站点的大部分部分都不会使用内置的 Admin。谁能建议我应该如何处理这个问题?

【问题讨论】:

blog entry 正是讨论了这一点。 这篇文章很老了。现在有没有简单的方法来实现这一点? @seanf 这个问题的答案是什么? 【参考方案1】:

2020-01-02 更新 ⚠ 以下答案从未更新到最新的 Python 和 Django 版本。自从几年前写这篇文章以来,已经发布了包来解决这个问题。现在我强烈推荐使用django-crum,它实现了相同的技术,但有测试并定期更新:https://pypi.org/project/django-crum/

最不显眼的方法是使用CurrentUserMiddleware 将当前用户存储在线程本地对象中:

current_user.py

​​>
from threading import local

_user = local()

class CurrentUserMiddleware(object):
    def process_request(self, request):
        _user.value = request.user

def get_current_user():
    return _user.value

现在您只需将这个中间件添加到您的 MIDDLEWARE_CLASSES 身份验证中间件之后。

settings.py

​​>
MIDDLEWARE_CLASSES = (
    ...
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    ...
    'current_user.CurrentUserMiddleware',
    ...
)

您的模型现在可以使用get_current_user 函数来访问用户,而无需传递请求对象。

models.py

​​>
from django.db import models
from current_user import get_current_user

class MyModel(models.Model):
    created_by = models.ForeignKey('auth.User', default=get_current_user)

提示:

如果您使用 Django CMS,您甚至不需要定义自己的 CurrentUserMiddleware,但可以使用 cms.middleware.user.CurrentUserMiddlewarecms.utils.permissions.get_current_user 函数来检索当前用户。

【讨论】:

看起来很彻底,但在 django 1.6.2 和 Python 3.3.2 中,当我使用 south ./manage.py schemamigration app --initial 时收到以下错误提取:return _user.value AttributeError: '_thread._local' object has no attribute 'value' 这是正确的,因为中间件永远不会被调用。要使 South(可能还有许多其他管理命令)正常工作,您需要在 get_current_user 中捕获 AttributeError 并返回 None 谢谢! Your model can now use the get_current_user function to access the user without having to pass the request object around.。这就是为什么使用中间件类比使用this snippet 更好的做法还是它们可以被视为等效解决方案的原因? @rara_tiru 这取决于您的需求。通过使用线程本地对象和中间件,在请求期间保存的任何经过身份验证的用户的对象都将填充created_by 字段。 sn -p 只修补管理员。 出于某种原因,这对我来说似乎不太干净,也许只是因为我还没有习惯它,但我喜欢这样做的事实。我开始觉得在模型函数中完全不可能达到任意变量,事实上有些人已经直截了当地说了这么多。谢谢你分享这个。 +1【参考方案2】:

如果您想要在管理员和其他地方都可以使用的东西,您应该使用自定义模型表单。基本思想是重写__init__方法,获取一个额外的参数——request——并将其存储为表单的一个属性,然后也重写save方法来设置用户id,然后保存到数据库。

class MyModelForm(forms.ModelForm):

   def __init__(self, *args, **kwargs):
       self.request = kwargs.pop('request', None)
       return super(MyModelForm, self).__init__(*args, **kwargs)


   def save(self, *args, **kwargs):
       kwargs['commit']=False
       obj = super(MyModelForm, self).save(*args, **kwargs)
       if self.request:
           obj.user = self.request.user
       obj.save()
       return obj

【讨论】:

我无法弄清楚如何让管理员使用“请求”对象初始化 MyModelForm。甚至可以不修改 contrib.admin 代码本身吗? @jholster 请看我的回答。 如果使用基于类的视图,那么在下面查看 Monster 和 Florentin 的答案非常重要,它们是必不可少的步骤。 但是在模型的pre_save 中访问它呢? ***.com/questions/25305186/… 只是为了完成答案。为了能够访问 __init__ 中的 request 对象,必须在视图中初始化表单时传递它。例如:myform = MyForm(request.POST, request=request)【参考方案3】:

Daniel 的回答不能直接为管理员工作,因为您需要传入请求对象。您可以通过覆盖 ModelAdmin 类中的 get_form 方法来做到这一点,但远离表单自定义并只需覆盖 ModelAdmin 中的 save_model 可能更容易。

def save_model(self, request, obj, form, change):
    """When creating a new object, set the creator field.
    """
    if not change:
        obj.creator = request.user
    obj.save()

【讨论】:

Tim Fletcher,您好,如果在尝试创建新条目(类的模型对象并且该类具有 m2m 字段)时在 admin.py 中定义了 save_model,我会收到错误:“'MyClass'在使用多对多关系之前,实例需要有一个主键值。”。你能建议应该添加什么吗?【参考方案4】:

这整个方法让我大吃一惊。我想只说一次,所以我在中间件中实现了它。只需在身份验证中间件之后添加 WhodidMiddleware。

如果您的 created_by 和 modified_by 字段设置为 editable = False,那么您根本不需要更改任何表单。

"""Add user created_by and modified_by foreign key refs to any model automatically.
   Almost entirely taken from https://github.com/Atomidata/django-audit-log/blob/master/audit_log/middleware.py"""
from django.db.models import signals
from django.utils.functional import curry

class WhodidMiddleware(object):
    def process_request(self, request):
        if not request.method in ('GET', 'HEAD', 'OPTIONS', 'TRACE'):
            if hasattr(request, 'user') and request.user.is_authenticated():
                user = request.user
            else:
                user = None

            mark_whodid = curry(self.mark_whodid, user)
            signals.pre_save.connect(mark_whodid,  dispatch_uid = (self.__class__, request,), weak = False)

    def process_response(self, request, response):
        signals.pre_save.disconnect(dispatch_uid =  (self.__class__, request,))
        return response

    def mark_whodid(self, user, sender, instance, **kwargs):
        if 'created_by' in instance._meta.fields and not instance.created_by:
            instance.created_by = user
        if 'modified_by' in instance._meta.fields:
            instance.modified_by = user

【讨论】:

最佳答案。我最终使用 getattr(instance, 'pk') 来确定我的模型是否正在创建。 此代码不是线程安全的。如果您使用的是使用线程的 WSGI 容器(例如 Apache2 + mod_wsgi、uWSGI 等),您最终可能会存储错误的用户。 这段代码确实不是线程安全的;在生产环境中最好避免 @giantas 此代码在概念上是错误的。您需要使用 thread local 对象才能使其正常工作。如有疑问,请使用django-crum,它有效且正确:pypi.org/project/django-crum【参考方案5】:

这是我使用通用视图的方法:

class MyView(CreateView):
    model = MyModel

    def form_valid(self, form):
        object = form.save(commit=False)
        object.owner = self.request.user
        object.save()
        return super(MyView, self).form_valid(form)

【讨论】:

再次调用form.save(),而不是object.save(),以防save()方法中存在依赖于提交的自定义(无论如何都会在模型实例上调用save()【参考方案6】:

如果您使用基于类的视图,丹尼尔的答案需要更多。添加以下内容以确保请求对象在您的 ModelForm 对象中可供我们使用

class BaseCreateView(CreateView):
    def get_form_kwargs(self):
        """
        Returns the keyword arguments for instanciating the form.
        """
        kwargs = 'initial': self.get_initial()
        if self.request.method in ('POST', 'PUT'):
            kwargs.update(
                'data': self.request.POST,
                'files': self.request.FILES,
                'request': self.request)
        return kwargs

另外,如前所述,您需要在 ModelForm.save() 的末尾返回 obj

【讨论】:

【参考方案7】:

使用类似的东西有什么问题:

class MyModelForm(forms.ModelForm):
    class Meta:
        model = MyModel
        exclude = ['created_by']

    def save(self, user):
        obj = super().save(commit = False)
        obj.created_by = user
        obj.save()
        return obj

现在在视图中将其称为myform.save(request.user)

这里是ModelForm's save function,它只有一个commit参数。

【讨论】:

【参考方案8】:

根据bikeshedder 的回答,我找到了一个解决方案,因为他实际上并不适合我。

    app/middleware/current_user.py

    from threading import local
    
    _user = local()
    
    class CurrentUserMiddleware(object):
    
    def __init__(self, get_response):
        self.get_response = get_response
    
    def __call__(self, request):
        _user.value = request.user
        return self.get_response(request)
    
    def get_current_user():
        return _user.value
    

    settings.py

    MIDDLEWARE = [
        'django.middleware.security.SecurityMiddleware',
        'django.contrib.sessions.middleware.SessionMiddleware',
        'django.middleware.common.CommonMiddleware',
        'django.middleware.csrf.CsrfViewMiddleware',
        'django.contrib.auth.middleware.AuthenticationMiddleware',
        'django.contrib.messages.middleware.MessageMiddleware',
        'django.middleware.clickjacking.XFrameOptionsMiddleware',
    
        'common.middleware.current_user.CurrentUserMiddleware',
    ]
    

    模型.py

    from common.middleware import current_user
    created_by = models.ForeignKey(User, blank=False, related_name='created_by', editable=False, default=current_user.get_current_user)
    

我正在使用 python 3.5 和 django 1.11.3

【讨论】:

在运行 ./manage.py migrate 时,我收到以下错误:AttributeError: '_thread._local' object has no attribute 'value' 这是有道理的,因为在迁移期间未加载请求中间件。还是我在这里遗漏了什么?你能澄清一下吗?【参考方案9】:

为了将来的参考,我找到了关于这个主题的最佳解决方案:

https://pypi.python.org/pypi/django-crum/0.6.1

这个库由一些中间件组成。 设置好这个库后,只需重写模型的保存方法并执行以下操作,

from crum import get_current_user        


def save(self, *args, **kwargs):
    user = get_current_user()
    if not self.pk:
        self.created_by = user
    else:
        self.changed_by = user
    super(Foomodel, self).save(*args, **kwargs)

如果您为所有模型创建和抽象模型并从它继承,您将获得自动填充的 created_by 和 changed_by 字段。

【讨论】:

【参考方案10】:

来自 Django 文档Models and request.user:

" 要跟踪使用 CreateView 创建对象的用户,您可以 使用自定义 ModelForm。在视图中,确保您 不要在要编辑的字段列表中包含 [用户字段],并覆盖 form_valid() 添加用户:

from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic.edit import CreateView
from myapp.models import Author

class AuthorCreate(LoginRequiredMixin, CreateView):
    model = Author
    fields = ['name']

    def form_valid(self, form):
        form.instance.created_by = self.request.user
        return super().form_valid(form)

【讨论】:

【参考方案11】:

forms.ModelForm 中的 'save' 方法返回保存的实例。

您应该在 MyModelForm 中添加最后一行: ... 返回对象

如果您使用 create_object 或 update_object 通用视图,则此更改是必要的。 他们使用保存的对象进行重定向。

【讨论】:

【参考方案12】:

我不相信 Daniel 的答案是最好的,因为它通过始终保存对象来改变模型表单的默认行为。

我将使用的代码:

forms.py

from django import forms

class MyModelForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        self.user = kwargs.pop('user', None)
        super(MyModelForm, self).__init__(*args, **kwargs)

    def save(self, commit=True):
        obj = super(MyModelForm, self).save(commit=False)

        if obj.created_by_id is None:
            obj.created_by = self.user

        if commit:
            obj.save()
        return obj

【讨论】:

【参考方案13】:

请注意您是否正在寻找此内容,但添加以下内容

user = models.ForeignKey('auth.User')

添加到模型中可以将用户 ID 添加到模型中。

在下文中,每个层次结构都属于一个用户。

class Hierarchy(models.Model):
    user = models.ForeignKey('auth.User')
    name = models.CharField(max_length=200)
    desc = models.CharField(max_length=1500)

【讨论】:

以上是关于Django:保存模型时填充用户 ID的主要内容,如果未能解决你的问题,请参考以下文章

如何在将密码保存到用户模型 Django 之前对其进行加密?

Django动态模型设计

如何在 DRF + django-rest-auth 中使用自定义用户模型保存注册时的额外字段

为什么Django引发IntegrityError:列“user_id”中的null值在提交表单时违反了非空约束?

如何在 Django 会话模型中设置自定义字段?

如何在 Django 中从用户模型的字段自动填充和显示数据到来自不同应用程序的另一个模型?