File size: 4,030 Bytes
b4f2238 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 | """
审计日志 Admin 配置
Requirements: 6.6
"""
from __future__ import annotations
from typing import Any, ClassVar
from django.contrib import admin
from django.http import HttpRequest
from django.utils.html import format_html, format_html_join
from django.utils.translation import gettext_lazy as _
from apps.documents.models import TemplateAuditLog
class TemplateAuditLogAdmin(admin.ModelAdmin[TemplateAuditLog]):
"""
模板审计日志管理
只读界面,用于查看模板修改历史.
"""
list_display = (
"id",
"content_type",
"object_id",
"object_repr_display",
"action",
"user",
"created_at",
)
list_filter = (
"content_type",
"action",
"created_at",
)
search_fields = (
"object_repr",
"user__name",
)
readonly_fields = (
"content_type",
"object_id",
"object_repr",
"action",
"changes_display",
"user",
"ip_address",
"user_agent",
"created_at",
)
ordering = ("-created_at",)
list_select_related: ClassVar[tuple[str, ...]] = ("user",)
date_hierarchy = "created_at"
fieldsets = (
(_("对象信息"), {"fields": ("content_type", "object_id", "object_repr")}),
(_("操作信息"), {"fields": ("action", "changes_display")}),
(_("操作人信息"), {"fields": ("user", "ip_address", "user_agent")}),
(_("时间"), {"fields": ("created_at",)}),
)
def has_add_permission(self, request: HttpRequest) -> bool:
"""禁止手动添加"""
return False
def has_change_permission(self, request: HttpRequest, obj: Any = None) -> bool:
"""禁止修改"""
return False
def has_delete_permission(self, request: HttpRequest, obj: Any = None) -> bool:
"""禁止删除"""
return False
@admin.display(description=_("对象描述"))
def object_repr_display(self, obj: TemplateAuditLog) -> str:
"""显示对象描述(截断)"""
text = obj.object_repr
if len(text) > 50:
text = text[:50] + "..."
return text
@admin.display(description=_("变更详情"))
def changes_display(self, obj: TemplateAuditLog) -> Any:
"""格式化显示变更内容"""
if not obj.changes:
return _("无变更记录")
rows: list[tuple[Any, Any, Any]] = []
for field, change in obj.changes.items():
old_val = change.get("old", "-")
new_val = change.get("new", "-")
# 截断过长的值
if isinstance(old_val, str) and len(old_val) > 100:
old_val = old_val[:100] + "..."
if isinstance(new_val, str) and len(new_val) > 100:
new_val = new_val[:100] + "..."
rows.append((field, old_val, new_val))
rows_html = format_html_join(
"",
"<tr>"
'<td style="padding: 8px; border: 1px solid #ccc; font-weight: bold;">{}</td>'
'<td style="padding: 8px; border: 1px solid #ccc; color: #c62828;">{}</td>'
'<td style="padding: 8px; border: 1px solid #ccc; color: #2e7d32;">{}</td>'
"</tr>",
rows,
)
return format_html(
'<div style="background: #f5f5f5; padding: 10px; border-radius: 4px;'
' max-height: 400px; overflow-y: auto;">'
'<table style="width: 100%; border-collapse: collapse;">'
'<tr style="background: #e0e0e0;">'
'<th style="padding: 8px; text-align: left; border: 1px solid #ccc;">{}</th>'
'<th style="padding: 8px; text-align: left; border: 1px solid #ccc;">{}</th>'
'<th style="padding: 8px; text-align: left; border: 1px solid #ccc;">{}</th>'
"</tr>"
"{}"
"</table></div>",
_("字段"),
_("旧值"),
_("新值"),
rows_html,
)
|