如何利用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
相关产品推荐
相关产品推荐

