我有一个模型,它有一个连接到标准的Django用户模型的created_by字段。当保存模型时,我需要自动填充这个字段为当前用户的ID。由于大多数网站部分不使用内置的Admin,因此我无法在Admin层面实现这一点。请问有哪位能提出建议,我该怎么做?
我有一个模型,它有一个连接到标准的Django用户模型的created_by字段。当保存模型时,我需要自动填充这个字段为当前用户的ID。由于大多数网站部分不使用内置的Admin,因此我无法在Admin层面实现这一点。请问有哪位能提出建议,我该怎么做?
更新于2020-01-02
⚠以下答案从未更新到最新版本的Python和Django。自几年前编写以来,已发布了解决此问题的软件包。现在我强烈建议使用django-crum
,它实现了相同的技术,但具有测试并定期更新:https://pypi.org/project/django-crum/
最不明显的方法是使用CurrentUserMiddleware
将当前用户存储在线程本地对象中:
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中的身份验证中间件之后即可。
MIDDLEWARE_CLASSES = (
...
'django.contrib.auth.middleware.AuthenticationMiddleware',
...
'current_user.CurrentUserMiddleware',
...
)
您的模型现在可以使用get_current_user
函数访问用户,而无需传递请求对象。
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.CurrentUserMiddleware
和cms.utils.permissions.get_current_user
函数来检索当前用户。
./manage.py schemamigration app --initial
时会收到以下错误提取:return _user.value AttributeError: '_thread._local' object has no attribute 'value'
。 - raratiruget_current_user
中捕获AttributeError
并返回None
。 - bikesheddercreated_by
字段。该代码片段仅修补管理员。 - bikeshedder如果你想要一个既可以在管理员页面中工作,也可以在其他地方使用的解决方案,你应该使用一个自定义模型表单。基本思路是覆盖__init__
方法,将额外的参数 - 请求(request) - 存储为表单的一个属性,然后还要覆盖保存方法,在保存到数据库之前设置用户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
pre_save
中访问它怎么办?http://stackoverflow.com/questions/25305186/accessing-in-passing-to-django-models-signal-methods-like-pre-save-pre-delete - andilabs__init__
中访问request
对象,必须在视图中初始化表单时传递它。例如:myform = MyForm(request.POST, request=request)
。 - vincebodiDaniel的答案不能直接适用于管理员,因为您需要传递请求对象。您可以通过在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()
整个方法让我感到很烦恼。我想只说一遍,所以我在中间件中实现了它。只需在身份验证中间件之后添加 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
thread local
对象才能使其正常工作。如果有疑问,只需使用 django-crum
,它可以正常工作且正确:https://pypi.org/project/django-crum/ - bikeshedder这是我使用通用视图的方法:
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)
save()
方法中存在依赖提交的自定义操作时(无论如何都会调用模型实例上的 save()
方法),请再次调用 form.save()
而不是 object.save()
。 - trybik如果您正在使用基于类的视图,那么丹尼尔的答案需要更多内容。请添加以下内容,以确保请求对象在您的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
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的save函数,它只有一个commit
参数。
对于未来的参考,我找到了关于这个主题最好的解决方案:
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)
基于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',
]
model.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)
AttributeError: '_thread._local' object has no attribute 'value'
。这很有道理,因为请求中间件在迁移期间未加载。或者是我漏掉了什么?您能否澄清一下? - freerunner最初的回答:从Django文档《模型和请求用户》中得知:要跟踪使用CreateView创建对象的用户,可以使用自定义ModelForm。在视图中,确保您不包含用户字段在要编辑的字段列表中,并覆盖form_valid()以添加用户。“如果要跟踪使用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)