白日依山尽,黄河入海流。欲穷千里目,更上一层楼。 -- 唐·王之涣

Django自带的Admin后台中如何获取当前登录用户

原文链接: Django自带的Admin后台中如何获取当前登录用户

个人公众号-全栈运维

需求背景

在使用Django快速开发一个IT 电脑、显示器资产管理小系统的时候,遇到一个问题是,当变更资产设备(新增、修改、删除)的时候,能记录是谁在什么时间进行的变更。

确认的是肯定是登录状态,但是在使用Django的signal中获取不到当前登录的用户

问题演示

1、定义资产设备模型和 自定义日志模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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、定义信号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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模型进行新增、修改的操作

过程日志输出如下

1
2
3
4
5
6
7
8
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、安装第三方模块

1
pip install django-threadlocals

2、配置对应的中间件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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 记录,发现获取到了当前登录用户

django-admin-signal-request-user

作者

Colin

发布于

2023-04-12

许可协议