需求背景
在使用Django快速开发一个IT 电脑、显示器资产管理小系统的时候,遇到一个问题是,当变更资产设备(新增、修改、删除)的时候,能记录是谁在什么时间进行的变更。
确认的是肯定是登录状态,但是在使用Django的signal中获取不到当前登录的用户
问题演示
1、定义资产设备模型和 自定义日志模型
class Device(models.Model):
"""
IT资产设备,主要是电脑和显示器
"""
pass
class CustomLogEntry(models.Model):
"""日志模型,记录其他模型上的变化记录"""
user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True)
action_time = models.DateTimeField(auto_now_add=True)
model_name = models.CharField(max_length=100)
model_pk = models.CharField(max_length=100)
action = models.CharField(max_length=10)
data = models.TextField()
def __str__(self):
return f"{self.user} {self.model_name} {self.action}"
2、定义信号
from django.contrib.auth.models import User
from django.contrib.admin.models import LogEntry
from django.db.models.signals import post_save
from django.dispatch import receiver
# 这里只记录Device模型的变化
# @receiver(post_save, sender=Device)
@receiver(post_save)
def log_save(sender, instance, created, **kwargs):
print("sender: ", sender)
action = 'CREATE' if created else 'UPDATE'
print("current request in kwargs: ", kwargs)
# current request in kwargs: {'signal': <django.db.models.signals.ModelSignal object at 0x10b73d490>, 'update_fields': None, 'raw': False, 'using': 'default'}
request = kwargs.get('requqest')
print("instance: ", instance)
print("current request in kwargs: ", request)
3、Device模型注册到admin后台,然后创建超级管理员账号,登录后台对 Device模型进行新增、修改的操作
过程日志输出如下
sender: <class 'demoapp.models.Device'>
current request in kwargs: {'signal': <django.db.models.signals.ModelSignal object at 0x103ec9730>, 'update_fields': None, 'raw': False, 'using': 'default'}
instance: 华为 x14 (2)
current request in kwargs: None
sender: <class 'django.contrib.admin.models.LogEntry'>
current request in kwargs: {'signal': <django.db.models.signals.ModelSignal object at 0x103ec9730>, 'update_fields': None, 'raw': False, 'using': 'default'}
instance: Changed “华为 x14 (2)” — Changed 状态.
current request in kwargs: None
从上面的日志分析
3.1、从上面的log_save信号函数中知道,除了 sender/instance/created 之外的参数都在 kwargs 中
3.2、输出kwargs尝试获取request 我们发现是request是None,所以Django的信号中是没有request的参数的,那么就无法通过request来获取当前登录的用户
3.3、同时我们发现在未明确指定sender的情况,除了我们明确操作的Device模型之外,还多出来个 django.contrib.admin.models.LogEntry
3.4、查看 django_admin_log 表中的数据发现,Django默认是帮忙记录了各个模型的变化(可以尝试新增用户、修改用户,发现变更操作也记录到了 django_admin_log 表中去)
这里主要是研究自定义logEntry的情况下如果通过signal信号获取当前登录用户,关于 django_admin_log 记录的逻辑,我们下篇文章介绍
问题修复
查找了相关文档之后发现,django-threadlocals 模块可以很好的帮我们解决该问题
1、安装第三方模块
pip install django-threadlocals
2、配置对应的中间件
MIDDLEWARE = [
# ... Other midddleware
'threadlocals.middleware.ThreadLocalMiddleware',
]
3、在然后使用 threadlocals中的 `get_current_request`
可以获取Django 后台的request,这个request中包括当前登录的user
```python
from django.db.models.signals import post_save
from django.contrib.auth import get_user_model
from django.contrib.auth.models import AnoymousUser
from django.dispatch import receiver
from threadlocals.threadlocals import get_current_request
@receiver(post_save, sender=Device)
def log_save(sender, instance, created, **kwargs):
if sender in [SysUser, Device]:
request = get_current_request()
if request:
user = request.user
else:
user = AnonymousUser()
action = 'CREATE' if created else 'UPDATE'
data = str(instance.__dict__)
CustomLogEntry.objects.create(user=user, model_name=sender.__name__, model_pk=instance.pk, action=action, data=data)
然后操作 Device的数据变更,然后查询 CustomLogEntry 记录,发现获取到了当前登录用户