You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.02 04:39:02