|
|
|
|
@ -1,27 +1,37 @@
|
|
|
|
|
# 导入 Django Admin 核心模块:用于自定义后台管理界面
|
|
|
|
|
from django.contrib import admin
|
|
|
|
|
# 导入 Admin 日志相关常量和模型:处理日志操作类型(如删除)
|
|
|
|
|
from django.contrib.admin.models import DELETION
|
|
|
|
|
from django.contrib.contenttypes.models import ContentType
|
|
|
|
|
# 导入 Django URL 和字符串处理工具:生成反向链接、处理编码和转义
|
|
|
|
|
from django.urls import reverse, NoReverseMatch
|
|
|
|
|
from django.utils.encoding import force_str
|
|
|
|
|
from django.utils.html import escape
|
|
|
|
|
from django.utils.safestring import mark_safe
|
|
|
|
|
# 导入国际化工具:支持后台文字的多语言翻译
|
|
|
|
|
from django.utils.translation import gettext_lazy as _
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 自定义 LogEntry Admin 类:用于在 Django 后台管理 Admin 操作日志(记录用户对模型的增删改操作)
|
|
|
|
|
class LogEntryAdmin(admin.ModelAdmin):
|
|
|
|
|
# 列表页筛选器:按“内容类型”(即操作的模型,如 Article、Comment)筛选日志
|
|
|
|
|
list_filter = [
|
|
|
|
|
'content_type'
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
# 列表页搜索框:支持按“对象名称”(如文章标题)和“操作描述”(如“修改了标题”)搜索
|
|
|
|
|
search_fields = [
|
|
|
|
|
'object_repr',
|
|
|
|
|
'change_message'
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
# 列表页可点击的链接:点击“操作时间”或“操作描述”可进入日志详情页
|
|
|
|
|
list_display_links = [
|
|
|
|
|
'action_time',
|
|
|
|
|
'get_change_message',
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
# 列表页展示的字段:操作时间、操作用户(带链接)、操作模型、操作对象(带链接)、操作描述
|
|
|
|
|
list_display = [
|
|
|
|
|
'action_time',
|
|
|
|
|
'user_link',
|
|
|
|
|
@ -30,62 +40,81 @@ class LogEntryAdmin(admin.ModelAdmin):
|
|
|
|
|
'get_change_message',
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
# 权限控制:禁止添加日志(日志由系统自动生成,不允许手动添加)
|
|
|
|
|
def has_add_permission(self, request):
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
# 权限控制:仅允许超级用户或拥有“修改日志”权限的用户查看/修改日志,且禁止 POST 请求(避免提交修改)
|
|
|
|
|
def has_change_permission(self, request, obj=None):
|
|
|
|
|
return (
|
|
|
|
|
request.user.is_superuser or
|
|
|
|
|
request.user.has_perm('admin.change_logentry')
|
|
|
|
|
) and request.method != 'POST'
|
|
|
|
|
|
|
|
|
|
# 权限控制:禁止删除日志(日志需留存,不允许手动删除)
|
|
|
|
|
def has_delete_permission(self, request, obj=None):
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
# 自定义列表字段:操作对象(生成带链接的对象名称,点击可跳转到对象的编辑页)
|
|
|
|
|
def object_link(self, obj):
|
|
|
|
|
# 转义对象名称(避免 XSS 攻击)
|
|
|
|
|
object_link = escape(obj.object_repr)
|
|
|
|
|
# 获取操作对象的内容类型(即所属模型)
|
|
|
|
|
content_type = obj.content_type
|
|
|
|
|
|
|
|
|
|
# 若操作不是“删除”(DELETION)且内容类型存在(排除异常情况)
|
|
|
|
|
if obj.action_flag != DELETION and content_type is not None:
|
|
|
|
|
# try returning an actual link instead of object repr string
|
|
|
|
|
try:
|
|
|
|
|
# 生成对象编辑页的 URL(格式:admin/应用名/模型名/change/对象ID/)
|
|
|
|
|
url = reverse(
|
|
|
|
|
'admin:{}_{}_change'.format(content_type.app_label,
|
|
|
|
|
content_type.model),
|
|
|
|
|
args=[obj.object_id]
|
|
|
|
|
)
|
|
|
|
|
# 将对象名称转为链接(点击跳转到编辑页)
|
|
|
|
|
object_link = '<a href="{}">{}</a>'.format(url, object_link)
|
|
|
|
|
except NoReverseMatch:
|
|
|
|
|
# 若无法生成链接(如模型未注册到 Admin),则保留纯文本名称
|
|
|
|
|
pass
|
|
|
|
|
# 标记为安全 HTML(告诉 Django 无需转义,避免链接被当作文本显示)
|
|
|
|
|
return mark_safe(object_link)
|
|
|
|
|
|
|
|
|
|
object_link.admin_order_field = 'object_repr'
|
|
|
|
|
object_link.short_description = _('object')
|
|
|
|
|
# 配置自定义字段的排序和显示名称
|
|
|
|
|
object_link.admin_order_field = 'object_repr' # 支持按“对象名称”排序
|
|
|
|
|
object_link.short_description = _('object') # 列表页字段显示名称(支持翻译)
|
|
|
|
|
|
|
|
|
|
# 自定义列表字段:操作用户(生成带链接的用户名,点击可跳转到用户的编辑页)
|
|
|
|
|
def user_link(self, obj):
|
|
|
|
|
# 获取用户模型的内容类型
|
|
|
|
|
content_type = ContentType.objects.get_for_model(type(obj.user))
|
|
|
|
|
# 转义用户名(避免 XSS 攻击)
|
|
|
|
|
user_link = escape(force_str(obj.user))
|
|
|
|
|
try:
|
|
|
|
|
# try returning an actual link instead of object repr string
|
|
|
|
|
# 生成用户编辑页的 URL
|
|
|
|
|
url = reverse(
|
|
|
|
|
'admin:{}_{}_change'.format(content_type.app_label,
|
|
|
|
|
content_type.model),
|
|
|
|
|
args=[obj.user.pk]
|
|
|
|
|
)
|
|
|
|
|
# 将用户名转为链接(点击跳转到用户编辑页)
|
|
|
|
|
user_link = '<a href="{}">{}</a>'.format(url, user_link)
|
|
|
|
|
except NoReverseMatch:
|
|
|
|
|
# 若无法生成链接(如用户模型未注册到 Admin),则保留纯文本用户名
|
|
|
|
|
pass
|
|
|
|
|
return mark_safe(user_link)
|
|
|
|
|
|
|
|
|
|
user_link.admin_order_field = 'user'
|
|
|
|
|
user_link.short_description = _('user')
|
|
|
|
|
# 配置自定义字段的排序和显示名称
|
|
|
|
|
user_link.admin_order_field = 'user' # 支持按“用户”排序
|
|
|
|
|
user_link.short_description = _('user') # 列表页字段显示名称(支持翻译)
|
|
|
|
|
|
|
|
|
|
# 优化查询性能:预加载“内容类型”关联数据(避免列表页加载时产生大量数据库查询)
|
|
|
|
|
def get_queryset(self, request):
|
|
|
|
|
queryset = super(LogEntryAdmin, self).get_queryset(request)
|
|
|
|
|
return queryset.prefetch_related('content_type')
|
|
|
|
|
|
|
|
|
|
# 自定义批量操作:移除“批量删除”按钮(防止误删日志)
|
|
|
|
|
def get_actions(self, request):
|
|
|
|
|
actions = super(LogEntryAdmin, self).get_actions(request)
|
|
|
|
|
if 'delete_selected' in actions:
|
|
|
|
|
del actions['delete_selected']
|
|
|
|
|
return actions
|
|
|
|
|
del actions['delete_selected'] # 删除“批量删除”操作
|
|
|
|
|
return actions
|