# 导入Django管理后台核心模块
from django.contrib import admin
# 导入日志相关常量和模型：DELETION表示删除操作的标记
from django.contrib.admin.models import DELETION
# 导入ContentType模型，用于处理模型与数据库表的映射关系
from django.contrib.contenttypes.models import ContentType
# 导入URL反向解析和异常处理
from django.urls import reverse, NoReverseMatch
# 导入字符串处理工具
from django.utils.encoding import force_str
# 导入HTML转义工具，防止XSS攻击
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'  # 操作描述消息
    ]

    # 列表页中可点击的链接字段
    list_display_links = [
        'action_time',  # 操作时间
        'get_change_message',  # 操作消息
    ]

    # 列表页展示的字段
    list_display = [
        '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):
        """
        限制修改日志的权限：
        - 仅超级用户或拥有'admin.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):
        """
        生成操作对象的链接：
        - 若操作不是删除且存在关联模型，尝试生成指向该对象编辑页的链接
        - 否则显示对象的字符串表示
        """
        object_link = escape(obj.object_repr)  # 转义对象名称，防止XSS
        content_type = obj.content_type

        # 非删除操作且存在内容类型时尝试生成链接
        if obj.action_flag != DELETION and content_type is not None:
            try:
                # 反向解析对象的admin编辑页URL
                url = reverse(
                    'admin:{}_{}_change'.format(content_type.app_label,
                                                content_type.model),
                    args=[obj.object_id]
                )
                # 生成带链接的HTML
                object_link = '<a href="{}">{}</a>'.format(url, object_link)
            except NoReverseMatch:
                # 解析URL失败时，仅显示对象名称
                pass
        # 标记为安全HTML，避免被转义
        return mark_safe(object_link)

    # 配置列表页字段的排序和显示名称
    object_link.admin_order_field = 'object_repr'  # 允许按对象名称排序
    object_link.short_description = _('object')  # 字段显示名称(支持国际化)

    def user_link(self, obj):
        """
        生成操作用户的链接：
        - 尝试生成指向该用户编辑页的链接
        - 否则显示用户的字符串表示
        """
        # 获取用户模型对应的ContentType
        content_type = ContentType.objects.get_for_model(type(obj.user))
        user_link = escape(force_str(obj.user))  # 转义用户名

        try:
            # 反向解析用户的admin编辑页URL
            url = reverse(
                'admin:{}_{}_change'.format(content_type.app_label,
                                            content_type.model),
                args=[obj.user.pk]
            )
            # 生成带链接的HTML
            user_link = '<a href="{}">{}</a>'.format(url, user_link)
        except NoReverseMatch:
            # 解析URL失败时，仅显示用户名
            pass
        return mark_safe(user_link)

    # 配置列表页字段的排序和显示名称
    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')

    def get_actions(self, request):
        """
        移除批量删除操作：日志不允许批量删除
        """
        actions = super(LogEntryAdmin, self).get_actions(request)
        if 'delete_selected' in actions:
            del actions['delete_selected']
        return actions