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

Django中数据库增删改查询历史查看工具需求(非Admin面板)

嘿,这个需求我刚好做过类似的实现,完全可以让普通用户不用依赖Admin面板就能查看数据库的增删改操作历史,分享下我的方案:

解决方案:面向普通用户的Django数据库操作历史追踪工具

核心思路

通过Django的**信号(Signals)**捕获模型的增、删、改操作,将操作详情记录到专门的历史表中,再开发一个普通用户可访问的视图和页面来展示这些历史记录。全程不需要依赖Admin面板,完全自定义权限和展示样式。

具体实现步骤

1. 创建操作历史模型

首先在你的app下创建一个用于存储操作历史的模型,记录关键信息:

# models.py
import json
from django.db import models
from django.contrib.auth.models import User
from django.utils.timezone import now

class DBOperationHistory(models.Model):
    OPERATION_TYPES = (
        ('ADD', '新增'),
        ('UPDATE', '修改'),
        ('DELETE', '删除'),
    )
    operation_type = models.CharField(max_length=10, choices=OPERATION_TYPES)
    model_name = models.CharField(max_length=100, verbose_name="模型名称")
    record_id = models.IntegerField(verbose_name="记录ID")
    operator = models.ForeignKey(
        User, 
        on_delete=models.SET_NULL, 
        null=True, blank=True,
        verbose_name="操作人"
    )
    operation_time = models.DateTimeField(default=now, verbose_name="操作时间")
    old_data = models.TextField(null=True, blank=True, verbose_name="旧数据")
    new_data = models.TextField(null=True, blank=True, verbose_name="新数据")

    class Meta:
        ordering = ['-operation_time']
        verbose_name = '数据库操作历史'
        verbose_name_plural = '数据库操作历史'

    def __str__(self):
        return f"{self.get_operation_type_display()} - {self.model_name}:{self.record_id}"

    # 可选:把JSON字符串转成字典,方便模板渲染
    def get_old_data_dict(self):
        return json.loads(self.old_data) if self.old_data else {}
    
    def get_new_data_dict(self):
        return json.loads(self.new_data) if self.new_data else {}

2. 编写信号接收器捕获操作

创建signals.py文件,用Django的post_save和post_delete信号监听所有模型的增删改操作,同时通过中间件获取当前操作人:

信号代码:

# signals.py
import json
from django.db.models.signals import post_save, post_delete, pre_save
from django.dispatch import receiver
from .models import DBOperationHistory

# 临时存储预保存的旧实例,用于修改操作的对比
_pre_save_old_instances = {}

@receiver(pre_save)
def store_old_instance(sender, instance, **kwargs):
    # 排除历史模型本身,避免循环记录
    if sender == DBOperationHistory:
        return
    if instance.pk:
        # 存储旧实例的可序列化字段
        _pre_save_old_instances[instance.pk] = {
            k: v for k, v in instance.__dict__.items() 
            if not k.startswith('_') and not callable(v)
        }

@receiver(post_save)
def track_save_operation(sender, instance, created, **kwargs):
    if sender == DBOperationHistory:
        return
    
    operator = getattr(instance, '_current_user', None)
    model_name = sender.__name__
    record_id = instance.pk

    if created:
        # 处理新增操作
        new_data = json.dumps(instance.get_new_data_dict())
        DBOperationHistory.objects.create(
            operation_type='ADD',
            model_name=model_name,
            record_id=record_id,
            operator=operator,
            new_data=new_data
        )
    else:
        # 处理修改操作,对比新旧数据
        old_data = _pre_save_old_instances.pop(instance.pk, None)
        if old_data:
            old_data_str = json.dumps(old_data)
            new_data_str = json.dumps(instance.get_new_data_dict())
            if old_data_str != new_data_str:
                DBOperationHistory.objects.create(
                    operation_type='UPDATE',
                    model_name=model_name,
                    record_id=record_id,
                    operator=operator,
                    old_data=old_data_str,
                    new_data=new_data_str
                )

@receiver(post_delete)
def track_delete_operation(sender, instance, **kwargs):
    if sender == DBOperationHistory:
        return
    
    operator = getattr(instance, '_current_user', None)
    model_name = sender.__name__
    record_id = instance.pk
    old_data = json.dumps(instance.get_old_data_dict())
    
    DBOperationHistory.objects.create(
        operation_type='DELETE',
        model_name=model_name,
        record_id=record_id,
        operator=operator,
        old_data=old_data
    )

注册信号:

在app的apps.py中注册信号,确保项目启动时加载:

# apps.py
from django.apps import AppConfig

class YourAppConfig(AppConfig):
    default_auto_field = 'django.db.models.BigAutoField'
    name = 'your_app_name'

    def ready(self):
        import your_app_name.signals  # 替换成你的app名称

3. 中间件获取当前操作人

信号本身无法直接获取请求用户,所以需要写一个中间件把当前用户绑定到模型实例上:

# middleware.py
from django.utils.deprecation import MiddlewareMixin

class CurrentUserMiddleware(MiddlewareMixin):
    def process_view(self, request, view_func, view_args, view_kwargs):
        # 仅对已登录用户生效
        if request.user.is_authenticated:
            # 给后续的模型实例绑定当前用户
            setattr(request, '_current_user', request.user)
            # 可以在这里针对特定模型做处理,或者在视图中手动绑定
        return None

然后在settings.py的MIDDLEWARE列表中添加这个中间件:

MIDDLEWARE = [
    # ... 其他中间件
    'your_app_name.middleware.CurrentUserMiddleware',  # 替换成你的app路径
]

4. 开发普通用户可访问的视图和页面

视图代码:

# views.py
from django.shortcuts import render
from django.contrib.auth.decorators import login_required
from django.core.paginator import Paginator
from .models import DBOperationHistory

@login_required  # 确保只有登录用户能访问
def operation_history(request):
    # 可以根据需求添加筛选条件,比如只显示当前用户的操作
    history_list = DBOperationHistory.objects.all()
    # 分页处理,每页15条记录
    paginator = Paginator(history_list, 15)
    page_number = request.GET.get('page')
    page_obj = paginator.get_page(page_number)
    
    return render(request, 'operation_history.html', {'page_obj': page_obj})

模板代码(operation_history.html):

{% extends 'base.html' %}  <!-- 替换成你的基础模板 -->

{% block content %}
<div class="container mt-4">
    <h2 class="mb-3">数据库操作历史</h2>
    
    <table class="table table-hover">
        <thead class="table-light">
            <tr>
                <th>操作类型</th>
                <th>模型</th>
                <th>记录ID</th>
                <th>操作人</th>
                <th>操作时间</th>
                <th>变更详情</th>
            </tr>
        </thead>
        <tbody>
            {% for history in page_obj %}
            <tr>
                <td>
                    {% if history.operation_type == 'ADD' %}
                    <span class="badge bg-success">{{ history.get_operation_type_display }}</span>
                    {% elif history.operation_type == 'UPDATE' %}
                    <span class="badge bg-warning text-dark">{{ history.get_operation_type_display }}</span>
                    {% else %}
                    <span class="badge bg-danger">{{ history.get_operation_type_display }}</span>
                    {% endif %}
                </td>
                <td>{{ history.model_name }}</td>
                <td>{{ history.record_id }}</td>
                <td>{{ history.operator.username|default:"未知用户" }}</td>
                <td>{{ history.operation_time|date:"Y-m-d H:i:s" }}</td>
                <td>
                    {% if history.operation_type == 'ADD' %}
                    <small><strong>新增内容:</strong></small>
                    <ul>
                        {% for k, v in history.get_new_data_dict.items %}
                        <li>{{ k }}: {{ v }}</li>
                        {% endfor %}
                    </ul>
                    {% elif history.operation_type == 'UPDATE' %}
                    <small><strong>修改前:</strong></small>
                    <ul>
                        {% for k, v in history.get_old_data_dict.items %}
                        {% if history.get_new_data_dict.k != v %}
                        <li>{{ k }}: {{ v }}</li>
                        {% endif %}
                        {% endfor %}
                    </ul>
                    <small><strong>修改后:</strong></small>
                    <ul>
                        {% for k, v in history.get_new_data_dict.items %}
                        {% if history.get_old_data_dict.k != v %}
                        <li>{{ k }}: {{ v }}</li>
                        {% endif %}
                        {% endfor %}
                    </ul>
                    {% else %}
                    <small><strong>删除内容:</strong></small>
                    <ul>
                        {% for k, v in history.get_old_data_dict.items %}
                        <li>{{ k }}: {{ v }}</li>
                        {% endfor %}
                    </ul>
                    {% endif %}
                </td>
            </tr>
            {% empty %}
            <tr>
                <td colspan="6" class="text-center py-3">暂无操作记录</td>
            </tr>
            {% endfor %}
        </tbody>
    </table>

    <!-- 分页控件 -->
    <nav aria-label="Page navigation">
        <ul class="pagination justify-content-center">
            {% if page_obj.has_previous %}
            <li class="page-item">
                <a class="page-link" href="?page=1">首页</a>
            </li>
            <li class="page-item">
                <a class="page-link" href="?page={{ page_obj.previous_page_number }}">上一页</a>
            </li>
            {% endif %}
            
            <li class="page-item disabled">
                <span class="page-link">第 {{ page_obj.number }} 页 / 共 {{ page_obj.paginator.num_pages }} 页</span>
            </li>
            
            {% if page_obj.has_next %}
            <li class="page-item">
                <a class="page-link" href="?page={{ page_obj.next_page_number }}">下一页</a>
            </li>
            <li class="page-item">
                <a class="page-link" href="?page={{ page_obj.paginator.num_pages }}">末页</a>
            </li>
            {% endif %}
        </ul>
    </nav>
</div>
{% endblock %}

5. 添加URL路由

在app的urls.py中添加访问路由:

# urls.py
from django.urls import path
from . import views

urlpatterns = [
    # ... 其他路由
    path('operation-history/', views.operation_history, name='operation_history'),
]

额外优化建议

  • 筛选功能:可以用django-filter库添加按模型名称、操作类型、时间范围的筛选,让用户更精准查找记录。
  • 权限控制:如果需要普通用户只能查看自己的操作,修改视图中的history_list为DBOperationHistory.objects.filter(operator=request.user)。
  • 性能优化:给DBOperationHistory的operation_time、model_name字段添加数据库索引;定期归档旧记录到单独的表,避免主表数据过大。
  • 友好展示:用JavaScript把JSON数据渲染成更美观的对比格式,比如高亮修改的字段。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.26 09:59:06