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