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

在Django Admin中记录模型更新的差异日志

Django Admin 专属操作日志(带字段差异对比)

既然只需要记录Admin后台的操作,直接从Admin层面入手就好,不用挨个改模型的save方法。下面是一套可复用的方案:

1. 先建日志模型

用来存储操作记录,包含核心信息:

from django.db import models
from django.contrib.auth.models import User
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.fields import GenericForeignKey

class AdminOperationLog(models.Model):
    OPERATION_TYPE = (
        ('create', '新增'),
        ('update', '修改'),
        ('delete', '删除'),
    )
    user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, verbose_name='操作人')
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    object_id = models.CharField(max_length=255)
    content_object = GenericForeignKey('content_type', 'object_id')
    operation_type = models.CharField(max_length=10, choices=OPERATION_TYPE, verbose_name='操作类型')
    diff_data = models.JSONField(verbose_name='字段差异', blank=True, null=True)
    operated_at = models.DateTimeField(auto_now_add=True, verbose_name='操作时间')

    class Meta:
        verbose_name = 'Admin操作日志'
        verbose_name_plural = verbose_name
        ordering = ['-operated_at']

2. 写一个通用的Admin基类

所有需要日志的模型Admin都继承这个基类,不用重复写逻辑:

from django.contrib import admin
from .models import AdminOperationLog

class LoggableAdminMixin(admin.ModelAdmin):
    # 可以在这里定义不需要记录的字段,比如自动更新的时间戳、主键等
    EXCLUDE_FIELDS = ['id', 'created_at', 'updated_at']

    def get_old_object(self, obj):
        # 获取数据库中当前对象的旧数据
        return self.model.objects.get(pk=obj.pk)

    def get_field_diff(self, old_obj, new_obj):
        # 对比新旧对象的字段差异
        diff = {}
        for field in self.model._meta.fields:
            field_name = field.name
            if field_name in self.EXCLUDE_FIELDS:
                continue
            old_value = getattr(old_obj, field_name)
            new_value = getattr(new_obj, field_name)
            # 处理外键:用__str__展示更友好
            if field.is_relation:
                old_value = str(old_value) if old_value else None
                new_value = str(new_value) if new_value else None
            if old_value != new_value:
                diff[field_name] = {
                    'old': old_value,
                    'new': new_value
                }
        return diff

    def save_model(self, request, obj, form, change):
        # 先执行原保存逻辑
        super().save_model(request, obj, form, change)
        # 记录日志
        if change:
            # 修改操作:对比新旧数据
            old_obj = self.get_old_object(obj)
            diff_data = self.get_field_diff(old_obj, obj)
            if diff_data:  # 只有有变化才记录
                AdminOperationLog.objects.create(
                    user=request.user,
                    content_object=obj,
                    operation_type='update',
                    diff_data=diff_data
                )
        else:
            # 新增操作:记录所有字段值
            field_data = {}
            for field in self.model._meta.fields:
                field_name = field.name
                if field_name in self.EXCLUDE_FIELDS:
                    continue
                value = getattr(obj, field_name)
                if field.is_relation:
                    value = str(value) if value else None
                field_data[field_name] = value
            AdminOperationLog.objects.create(
                user=request.user,
                content_object=obj,
                operation_type='create',
                diff_data=field_data
            )

    def delete_model(self, request, obj):
        # 先记录删除日志
        field_data = {}
        for field in self.model._meta.fields:
            field_name = field.name
            if field_name in self.EXCLUDE_FIELDS:
                continue
            value = getattr(obj, field_name)
            if field.is_relation:
                value = str(value) if value else None
            field_data[field_name] = value
        AdminOperationLog.objects.create(
            user=request.user,
            content_object=obj,
            operation_type='delete',
            diff_data=field_data
        )
        # 再执行原删除逻辑
        super().delete_model(request, obj)

3. 给需要日志的模型Admin加继承

比如你的Book模型,只需要让它的Admin继承这个Mixin:

from django.contrib import admin
from .models import Book
from .admin_mixins import LoggableAdminMixin

@admin.register(Book)
class BookAdmin(LoggableAdminMixin, admin.ModelAdmin):
    # 你的原有配置
    list_display = ['title', 'author', 'price']

额外说明

  • 可以根据需求调整EXCLUDE_FIELDS,排除不需要记录的字段
  • 如果需要处理多对多字段,可以在save_model里额外处理,对比多对多的关联对象变化
  • 日志模型里的diff_data用JSONField存储,方便后续展示差异(比如在Admin里做一个友好的差异视图)
  • 这个方案只针对Admin后台的操作,API或其他代码调用save/delete不会触发日志,完全符合你的需求

内容的提问来源于stack exchange,提问作者user10611897

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 08:22:22