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 = '{}'.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 = '{}'.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