|
|
|
|
@ -1,91 +1,136 @@
|
|
|
|
|
#姜雨菲: 导入Django管理后台核心模块
|
|
|
|
|
from django.contrib import admin
|
|
|
|
|
from django.contrib.admin.models import DELETION
|
|
|
|
|
from django.contrib.contenttypes.models import ContentType
|
|
|
|
|
#姜雨菲: 导入日志相关常量和模型
|
|
|
|
|
from django.contrib.admin.models import DELETION # 表示"删除"操作的常量
|
|
|
|
|
from django.contrib.contenttypes.models import ContentType # 内容类型模型,用于关联不同模型
|
|
|
|
|
#姜雨菲: 导入URL反向解析和异常处理
|
|
|
|
|
from django.urls import reverse, NoReverseMatch
|
|
|
|
|
# 导入字符串处理工具
|
|
|
|
|
from django.utils.encoding import force_str
|
|
|
|
|
# 导入HTML转义工具
|
|
|
|
|
from django.utils.html import escape
|
|
|
|
|
# 导入安全字符串标记工具(用于渲染HTML)
|
|
|
|
|
from django.utils.safestring import mark_safe
|
|
|
|
|
# 导入国际化翻译工具
|
|
|
|
|
from django.utils.translation import gettext_lazy as _
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class LogEntryAdmin(admin.ModelAdmin):
|
|
|
|
|
"""
|
|
|
|
|
自定义管理员日志(LogEntry)的管理类
|
|
|
|
|
用于在Django admin后台展示和管理系统操作日志
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
# 列表页的筛选器:按内容类型筛选
|
|
|
|
|
list_filter = [
|
|
|
|
|
'content_type'
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
# 搜索字段:支持按对象表示和变更消息搜索
|
|
|
|
|
search_fields = [
|
|
|
|
|
'object_repr',
|
|
|
|
|
'change_message'
|
|
|
|
|
'object_repr', # 对象的字符串表示
|
|
|
|
|
'change_message' # 操作变更的描述信息
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
# 列表页中可点击的链接字段
|
|
|
|
|
list_display_links = [
|
|
|
|
|
'action_time',
|
|
|
|
|
'get_change_message',
|
|
|
|
|
'action_time', # 操作时间
|
|
|
|
|
'get_change_message', # 变更消息
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
# 列表页展示的字段
|
|
|
|
|
list_display = [
|
|
|
|
|
'action_time',
|
|
|
|
|
'user_link',
|
|
|
|
|
'content_type',
|
|
|
|
|
'object_link',
|
|
|
|
|
'get_change_message',
|
|
|
|
|
'action_time', # 操作时间
|
|
|
|
|
'user_link', # 操作用户(带链接)
|
|
|
|
|
'content_type', # 操作的内容类型(模型)
|
|
|
|
|
'object_link', # 操作的对象(带链接)
|
|
|
|
|
'get_change_message', # 变更消息
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
def has_add_permission(self, request):
|
|
|
|
|
"""禁用添加权限:不允许手动添加日志记录"""
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
def has_change_permission(self, request, obj=None):
|
|
|
|
|
"""
|
|
|
|
|
限制修改权限:
|
|
|
|
|
- 仅超级用户或拥有change_logentry权限的用户可查看
|
|
|
|
|
- 禁止POST请求(即不允许修改日志内容)
|
|
|
|
|
"""
|
|
|
|
|
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):
|
|
|
|
|
"""
|
|
|
|
|
生成操作对象的链接(若对象存在)
|
|
|
|
|
对于已删除的对象,仅显示文本;对于存在的对象,显示可点击的链接
|
|
|
|
|
"""
|
|
|
|
|
# 先对对象的字符串表示进行HTML转义,防止XSS攻击
|
|
|
|
|
object_link = escape(obj.object_repr)
|
|
|
|
|
# 获取操作对象的内容类型
|
|
|
|
|
content_type = obj.content_type
|
|
|
|
|
|
|
|
|
|
# 如果不是删除操作且内容类型存在,尝试生成编辑链接
|
|
|
|
|
if obj.action_flag != DELETION and content_type is not None:
|
|
|
|
|
# try returning an actual link instead of object repr string
|
|
|
|
|
try:
|
|
|
|
|
# 反向解析对象的编辑页面URL
|
|
|
|
|
url = reverse(
|
|
|
|
|
# 生成admin的URL名称格式:app_label_model_change
|
|
|
|
|
'admin:{}_{}_change'.format(content_type.app_label,
|
|
|
|
|
content_type.model),
|
|
|
|
|
args=[obj.object_id]
|
|
|
|
|
args=[obj.object_id] # 传递对象ID作为参数
|
|
|
|
|
)
|
|
|
|
|
# 生成带链接的HTML
|
|
|
|
|
object_link = '<a href="{}">{}</a>'.format(url, object_link)
|
|
|
|
|
except NoReverseMatch:
|
|
|
|
|
# 若无法解析URL(如模型未注册到admin),则只显示文本
|
|
|
|
|
pass
|
|
|
|
|
# 标记为安全字符串,允许Django渲染HTML
|
|
|
|
|
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))
|
|
|
|
|
# 对用户名进行HTML转义
|
|
|
|
|
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]
|
|
|
|
|
args=[obj.user.pk] # 传递用户ID作为参数
|
|
|
|
|
)
|
|
|
|
|
# 生成带链接的HTML
|
|
|
|
|
user_link = '<a href="{}">{}</a>'.format(url, user_link)
|
|
|
|
|
except NoReverseMatch:
|
|
|
|
|
# 若无法解析URL,只显示用户名
|
|
|
|
|
pass
|
|
|
|
|
# 标记为安全字符串,允许渲染HTML
|
|
|
|
|
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):
|
|
|
|
|
"""优化查询集:预加载content_type,减少数据库查询次数"""
|
|
|
|
|
queryset = super(LogEntryAdmin, self).get_queryset(request)
|
|
|
|
|
return queryset.prefetch_related('content_type')
|
|
|
|
|
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
|
|
|
|
|
return actions
|