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

如何利用django-simple-history获取Django模型变更字段并构建历史视图

嘿,这个需求我刚好在几个项目里落地过,django-simple-history确实是做模型变更追踪的首选工具,完全能满足你要展示变更人、时间、字段、新旧值的需求。下面给你一步步拆解实现思路,重点解决“对比连续历史找变更字段”这个核心问题:

一、先做好基础配置

首先确保你已经安装了django-simple-history,然后给你的Application模型挂上HistoricalRecords:

from django.db import models
from simple_history.models import HistoricalRecords

class Application(models.Model):
    name = models.CharField(max_length=100)
    status = models.CharField(max_length=20, choices=[('active', '启用'), ('inactive', '停用')])
    description = models.TextField(blank=True)
    # 你的其他业务字段...
    
    # 加上这行,自动生成历史记录表
    history = HistoricalRecords()

执行migrate后,会自动生成一个historicalapplication表,存储所有版本的实例数据,还自带history_user(变更人)、history_date(变更时间)、history_type(变更类型:+创建、~更新、-删除)这些字段,完美匹配你的需求。

二、核心:对比相邻历史记录找变更字段

每个Application实例的history属性就是它的所有历史版本,默认按变更时间倒序排列。我们需要写一个辅助函数,对比两个相邻的历史条目,找出差异字段:

def get_changed_fields(old_history_entry, new_history_entry):
    changed_fields = []
    # 排除历史模型自带的字段,只对比业务字段
    exclude_fields = ['id', 'history_id', 'history_date', 'history_user', 'history_type']
    business_fields = [field.name for field in Application._meta.fields if field.name not in exclude_fields]
    
    for field in business_fields:
        old_val = getattr(old_history_entry, field, None)
        new_val = getattr(new_history_entry, field, None)
        if old_val != new_val:
            changed_fields.append({
                'field': field,
                'old_value': old_val,
                'new_value': new_val,
                'operator': new_history_entry.history_user,
                'change_time': new_history_entry.history_date,
                'change_type': new_history_entry.history_type
            })
    return changed_fields

这个函数逻辑很直白:遍历所有业务字段,对比两个历史条目的字段值,把不一样的字段信息收集起来。

三、视图层处理历史数据

接下来在视图里把所有历史条目按时间正序排列,逐个对比相邻条目,生成完整的变更记录列表:

from django.shortcuts import get_object_or_404, render
from .models import Application

def application_change_history(request, pk):
    app = get_object_or_404(Application, pk=pk)
    # 按时间正序排列,从创建到最新变更
    history_entries = app.history.all().order_by('history_date')
    change_records = []

    # 处理创建记录(第一个历史条目)
    if history_entries:
        create_entry = history_entries[0]
        if create_entry.history_type == '+':
            create_records = []
            for field in [f.name for f in Application._meta.fields if f.name != 'id']:
                val = getattr(create_entry, field, None)
                if val is not None and val != '':
                    create_records.append({
                        'change_type': '创建',
                        'operator': create_entry.history_user,
                        'change_time': create_entry.history_date,
                        'field': field,
                        'old_value': '-',
                        'new_value': val
                    })
            change_records.extend(create_records)

        # 处理后续的更新/删除记录
        for i in range(1, len(history_entries)):
            old_entry = history_entries[i-1]
            new_entry = history_entries[i]
            # 获取本次变更的所有字段
            field_changes = get_changed_fields(old_entry, new_entry)
            # 把变更类型转成友好的中文
            for change in field_changes:
                if change['change_type'] == '~':
                    change['change_type'] = '更新'
                elif change['change_type'] == '-':
                    change['change_type'] = '删除'
            change_records.extend(field_changes)

    return render(request, 'app_history.html', {'app': app, 'change_records': change_records})

这里额外处理了创建记录的场景——创建时没有旧值,直接展示所有字段的初始值。

四、模板渲染成表格

最后写个模板,把变更记录渲染成你要的表格:

<!-- templates/app_history.html -->
<h2>{{ app.name }} 的变更历史</h2>
<table class="history-table" border="1" cellpadding="8" cellspacing="0">
    <thead>
        <tr>
            <th>操作类型</th>
            <th>变更人</th>
            <th>变更时间</th>
            <th>变更字段</th>
            <th>旧值</th>
            <th>新值</th>
        </tr>
    </thead>
    <tbody>
        {% for record in change_records %}
        <tr>
            <td>{{ record.change_type }}</td>
            <td>{{ record.operator.username|default:"系统操作" }}</td>
            <td>{{ record.change_time|date:"Y-m-d H:i:s" }}</td>
            <td>{{ record.field }}</td>
            <td>{{ record.old_value|default:"-" }}</td>
            <td>{{ record.new_value|default:"-" }}</td>
        </tr>
        {% empty %}
        <tr>
            <td colspan="6" style="text-align:center;">暂无任何变更记录</td>
        </tr>
        {% endfor %}
    </tbody>
</table>
一些额外提示
  • 如果是前端表单提交变更,记得在保存模型时设置history_user,比如app.history_user = request.user,这样才能正确记录变更人;后台admin操作的话,simple-history会自动关联当前登录的管理员。
  • 如果需要处理外键、多对多这类复杂字段,可以在辅助函数里做特殊处理,比如取外键实例的__str__值展示。
  • 替代工具的话,django-reversion和django-auditlog也能实现类似功能,但django-simple-history更轻量、配置简单,查询历史数据也更直接,完全满足你的需求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.19 07:25:11