Django项目中记录用户操作活动的更优实现方案有哪些?
Django操作日志实现优化方案
一、现有模型的可优化点
- 外键关联不规范:
user_id用CharField存储,无法直接关联Django内置/自定义的用户表,后续查询用户关联信息需要额外查表,也存在数据不一致风险 - 操作标识无约束:
action_flag用无约束的CharField,容易存入不规范的取值,也会浪费存储空间 - 关联模型适配性差:
class_id用字符串存储,硬编码关联模型的名称/ID,无法通用适配所有需要记录操作的模型,也无法直接反向获取操作对应的模型实例 - 时间字段设置错误:
action_taken_at的default写为datetime.now()(带括号),会在Django加载模型类时就计算固定时间,而非数据插入时的当前时间,所有日志的时间会统一变成服务启动的时间 - 缺少操作详情字段:现有模型没有存储具体操作内容的字段,无法满足记录操作详情的需求
二、优化后的实现方案
2.1 优化后的模型设计
基于Django内置的ContentType框架实现通用关联,适配所有业务模型:
from django.db import models from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.fields import GenericForeignKey from django.conf import settings class UserAction(models.Model): # 操作类型枚举 ACTION_CHOICES = [ (1, '创建'), (2, '更新'), (3, '删除'), ] # 外键关联用户表,删除用户时日志保留的话用SET_NULL user = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, verbose_name='操作人' ) # 操作类型,用小整数存储节省空间,有choices约束取值 action_flag = models.SmallIntegerField(choices=ACTION_CHOICES, verbose_name='操作类型') # 通用关联,适配任意业务模型 content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE, verbose_name='关联模型类型') object_id = models.CharField(max_length=255, verbose_name='关联模型ID') content_object = GenericForeignKey('content_type', 'object_id') # 操作详情,存JSON格式,比如修改前后的字段值、操作备注等 detail = models.JSONField(default=dict, verbose_name='操作详情') # auto_now_add会在数据插入时自动填充当前时间,无需手动设置 action_taken_at = models.DateTimeField(auto_now_add=True, verbose_name='操作时间') class Meta: ordering = ['-action_taken_at'] verbose_name = '用户操作日志' verbose_name_plural = verbose_name
2.2 自动填充日志方案,避免手动调用函数
不用在每个CRUD逻辑里手动调用写日志的函数,推荐用Django信号自动触发:
from django.db.models.signals import post_save, post_delete from django.dispatch import receiver # 示例:监听需要记录操作的业务模型,此处以Course模型为例 from yourapp.models import Course @receiver(post_save, sender=Course) def log_course_save_action(sender, instance, created, **kwargs): # 可配合自定义中间件从线程上下文获取当前登录用户,无需每次手动传参 user = get_current_user() # 自行实现获取当前登录用户的逻辑 action_flag = 1 if created else 2 detail = {} if not created: # 可对比实例变更字段,将修改前后的内容存入detail detail['changed_fields'] = instance.get_dirty_fields() # 可借助django-model-utils的DirtyFieldsMixin快速实现 UserAction.objects.create( user=user, action_flag=action_flag, content_object=instance, detail=detail ) @receiver(post_delete, sender=Course) def log_course_delete_action(sender, instance, **kwargs): user = get_current_user() UserAction.objects.create( user=user, action_flag=3, content_object=instance, detail={'deleted_data': instance.__dict__} )
三、额外优化建议
- 如果需要记录所有模型的操作,无需给每个模型单独写信号,可监听全局模型信号后排除不需要记录的模型即可
- 如果用Django REST Framework开发接口,也可以自定义视图Mixin,在
perform_create/perform_update/perform_destroy方法里统一记录日志,直接从request对象取操作用户,实现更简单 - 操作量大的场景可以把日志表存在独立库,或者定期归档,避免影响主业务的查询性能
内容的提问来源于stack exchange,提问作者raj-kapil
相关产品推荐
相关产品推荐

