|
|
from django.contrib import admin # 导入Django Admin核心模块
|
|
|
from django.contrib.admin.models import DELETION # 导入表示"删除"操作的常量
|
|
|
from django.contrib.contenttypes.models import ContentType # 导入内容类型模型(用于关联不同模型)
|
|
|
from django.urls import reverse, NoReverseMatch # 导入URL反向解析工具及异常
|
|
|
from django.utils.encoding import force_str # 用于字符串编码转换(兼容Python 2/3)
|
|
|
from django.utils.html import escape # 用于HTML转义(防止XSS攻击)
|
|
|
from django.utils.safestring import mark_safe # 标记安全的HTML字符串(允许在模板中渲染)
|
|
|
from django.utils.translation import gettext_lazy as _ # 国际化翻译工具
|
|
|
|
|
|
|
|
|
class LogEntryAdmin(admin.ModelAdmin):
|
|
|
"""
|
|
|
wr自定义LogEntry模型的Admin配置类
|
|
|
LogEntry是Django自带的模型,用于记录管理员在后台的操作日志(如新增、修改、删除对象)
|
|
|
此类控制日志在Admin后台的显示、搜索、过滤及操作权限
|
|
|
"""
|
|
|
|
|
|
#wr 列表页的过滤条件(右侧过滤器):按内容类型(即操作的模型类型)过滤
|
|
|
list_filter = [
|
|
|
'content_type'
|
|
|
]
|
|
|
|
|
|
#wr 搜索字段:可通过对象名称(object_repr)和操作描述(change_message)搜索日志
|
|
|
search_fields = [
|
|
|
'object_repr',
|
|
|
'change_message'
|
|
|
]
|
|
|
|
|
|
#wr 列表页中可点击的字段(点击跳转到日志详情页)
|
|
|
list_display_links = [
|
|
|
'action_time', #wr 操作时间
|
|
|
'get_change_message', #wr 操作描述(自定义方法)
|
|
|
]
|
|
|
|
|
|
#wr 列表页显示的字段(按顺序排列)
|
|
|
list_display = [
|
|
|
'action_time', #wr 操作时间
|
|
|
'user_link', #wr 操作人(带链接的自定义字段)
|
|
|
'content_type', #wr 操作的模型类型(如文章、用户等)
|
|
|
'object_link', #wr 操作的对象(带链接的自定义字段)
|
|
|
'get_change_message', #wr 操作描述(Django原生方法,返回格式化的操作信息)
|
|
|
]
|
|
|
|
|
|
def has_add_permission(self, request):
|
|
|
"""
|
|
|
wr控制是否允许添加日志条目:返回False,禁止手动添加日志
|
|
|
原因:日志是系统自动记录的,不允许人工干预
|
|
|
"""
|
|
|
return False
|
|
|
|
|
|
def has_change_permission(self, request, obj=None):
|
|
|
"""
|
|
|
wr控制是否允许修改日志条目:仅允许超级用户或有修改权限的用户以非POST方式访问(即仅查看)
|
|
|
原因:日志记录应保持原始性,禁止修改
|
|
|
"""
|
|
|
return (
|
|
|
request.user.is_superuser or #wr 超级用户有权限
|
|
|
request.user.has_perm('admin.change_logentry') #wr 有明确权限的用户
|
|
|
) and request.method != 'POST' #wr 禁止POST请求(即禁止提交修改)
|
|
|
|
|
|
def has_delete_permission(self, request, obj=None):
|
|
|
"""
|
|
|
wr控制是否允许删除日志条目:返回False,禁止删除日志
|
|
|
原因:日志是系统操作记录,需长期保存用于审计
|
|
|
"""
|
|
|
return False
|
|
|
|
|
|
def object_link(self, obj):
|
|
|
"""
|
|
|
wr自定义字段:显示操作对象的链接(若存在)
|
|
|
功能:如果操作不是删除,且能获取到内容类型,尝试生成对象的Admin修改页链接
|
|
|
"""
|
|
|
object_link = escape(obj.object_repr) #wr 转义对象名称(防止XSS)
|
|
|
content_type = obj.content_type #wr 获取操作的模型类型
|
|
|
|
|
|
#wr 若操作不是删除,且内容类型存在(即有对应的模型)
|
|
|
if obj.action_flag != DELETION and content_type is not None:
|
|
|
try:
|
|
|
#wr 生成对象在Admin中的修改页URL(格式:admin:应用名_模型名_change)
|
|
|
url = reverse(
|
|
|
'admin:{}_{}_change'.format(content_type.app_label,
|
|
|
content_type.model),
|
|
|
args=[obj.object_id] #wr 传入对象ID
|
|
|
)
|
|
|
#wr 生成带链接的对象名称
|
|
|
object_link = '<a href="{}">{}</a>'.format(url, object_link)
|
|
|
except NoReverseMatch:
|
|
|
#wr 若URL反向解析失败(如模型未注册到Admin),则仅显示对象名称
|
|
|
pass
|
|
|
return mark_safe(object_link) #wr 标记为安全HTML,允许在页面渲染链接
|
|
|
|
|
|
#wr 配置自定义字段的排序和显示名称
|
|
|
object_link.admin_order_field = 'object_repr' #wr 允许按对象名称排序
|
|
|
object_link.short_description = _('object') #wr 列表页表头显示名称(支持国际化)
|
|
|
|
|
|
def user_link(self, obj):
|
|
|
"""
|
|
|
wr自定义字段:显示操作人的链接(跳转到用户的Admin修改页)
|
|
|
"""
|
|
|
#wr 获取操作人(User模型)的内容类型
|
|
|
content_type = ContentType.objects.get_for_model(type(obj.user))
|
|
|
user_link = escape(force_str(obj.user)) #wr 转义用户名并确保为字符串
|
|
|
|
|
|
try:
|
|
|
#wr 生成用户在Admin中的修改页URL
|
|
|
url = reverse(
|
|
|
'admin:{}_{}_change'.format(content_type.app_label,
|
|
|
content_type.model),
|
|
|
args=[obj.user.pk] #wr 传入用户ID
|
|
|
)
|
|
|
#wr 生成带链接的用户名
|
|
|
user_link = '<a href="{}">{}</a>'.format(url, user_link)
|
|
|
except NoReverseMatch:
|
|
|
#wr 若URL解析失败,仅显示用户名
|
|
|
pass
|
|
|
return mark_safe(user_link) #wr 标记为安全HTML
|
|
|
|
|
|
#wr 配置自定义字段的排序和显示名称
|
|
|
user_link.admin_order_field = 'user' #wr 允许按用户排序
|
|
|
user_link.short_description = _('user') #wr 列表页表头显示名称(支持国际化)
|
|
|
|
|
|
def get_queryset(self, request):
|
|
|
"""
|
|
|
wr优化查询集:预加载content_type关联数据,减少数据库查询次数
|
|
|
提升列表页加载性能(避免N+1查询问题)
|
|
|
"""
|
|
|
queryset = super(LogEntryAdmin, self).get_queryset(request)
|
|
|
return queryset.prefetch_related('content_type') #wr 预加载content_type
|
|
|
|
|
|
def get_actions(self, request):
|
|
|
"""
|
|
|
wr移除"删除选中项"操作:确保日志无法通过批量操作删除
|
|
|
"""
|
|
|
actions = super(LogEntryAdmin, self).get_actions(request)
|
|
|
if 'delete_selected' in actions:
|
|
|
del actions['delete_selected'] #wr 删除批量删除操作
|
|
|
return actions |