from __future__ import annotations import json from collections.abc import Iterable from django import urls from django.contrib import admin from django.db.models import QuerySet from django.http import HttpRequest from django.utils.html import SafeString, format_html from .models import ( BotSetting, DeletedMessage, DocumentationLink, Filter, FilterList, Infraction, MessageDeletionContext, Nomination, OffTopicChannelName, OffensiveMessage, Role, User ) from .models.bot.nomination import NominationEntry admin.site.site_header = "Python Discord | Administration" admin.site.site_title = "Python Discord" @admin.register(BotSetting) class BotSettingAdmin(admin.ModelAdmin): """Admin formatting for the BotSetting model.""" fields = ("name", "data") list_display = ("name",) readonly_fields = ("name",) def has_add_permission(self, *args) -> bool: """Prevent adding from django admin.""" return False def has_delete_permission(self, *args) -> bool: """Prevent deleting from django admin.""" return False @admin.register(DocumentationLink) class DocumentationLinkAdmin(admin.ModelAdmin): """Admin formatting for the DocumentationLink model.""" fields = ("package", "inventory_url", "base_url") list_display = ("package", "inventory_url", "base_url") list_editable = ("base_url", "inventory_url") search_fields = ("package",) class InfractionActorFilter(admin.SimpleListFilter): """Actor Filter for Infraction Admin list page.""" title = "Actor" parameter_name = "actor" def lookups(self, request: HttpRequest, model: NominationAdmin) -> Iterable[tuple[int, str]]: """Selectable values for viewer to filter by.""" actor_ids = Infraction.objects.order_by().values_list("actor").distinct() actors = User.objects.filter(id__in=actor_ids) return ((a.id, a.username) for a in actors) def queryset(self, request: HttpRequest, queryset: QuerySet) -> QuerySet | None: """Query to filter the list of Users against.""" if not self.value(): return None return queryset.filter(actor__id=self.value()) @admin.register(Infraction) class InfractionAdmin(admin.ModelAdmin): """Admin formatting for the Infraction model.""" fieldsets = ( ("Members", {"fields": ("user", "actor")}), ("Action", {"fields": ("type", "hidden", "active")}), ("Dates", {"fields": ("inserted_at", "expires_at")}), ("Reason", {"fields": ("reason",)}), ) readonly_fields = ( "user", "actor", "type", "inserted_at", "expires_at", "active", "hidden" ) list_display = ( "type", "active", "user", "inserted_at", "reason", ) search_fields = ( "id", "user__name", "user__id", "actor__name", "actor__id", "reason", "type" ) list_filter = ( "type", "hidden", "active", InfractionActorFilter ) def has_add_permission(self, *args) -> bool: """Prevent adding from django admin.""" return False @admin.register(DeletedMessage) class DeletedMessageAdmin(admin.ModelAdmin): """Admin formatting for the DeletedMessage model.""" fields = ( "id", "author", "channel_id", "content", "embed_data", "context", "view_full_log" ) exclude = ("embeds", "deletion_context") search_fields = ( "id", "content", "author__name", "author__id", "deletion_context__actor__name", "deletion_context__actor__id" ) list_display = ("id", "author", "channel_id") def embed_data(self, message: DeletedMessage) -> str | None: """Format embed data in a code block for better readability.""" if message.embeds: return format_html( "
"
                "{0}",
                json.dumps(message.embeds, indent=4)
            )
        return None
    embed_data.short_description = "Embeds"
    @staticmethod
    def context(message: DeletedMessage) -> str:
        """Provide full context info with a link through to context admin view."""
        link = urls.reverse(
            "admin:api_messagedeletioncontext_change",
            args=[message.deletion_context.id]
        )
        details = (
            f"Deleted by {message.deletion_context.actor} at "
            f"{message.deletion_context.creation}"
        )
        return format_html("{1}", link, details)
    @staticmethod
    def view_full_log(message: DeletedMessage) -> str:
        """Provide a link to the message logs for the relevant context."""
        return format_html(
            "Click to view full context log",
            message.deletion_context.log_url
        )
    def has_add_permission(self, *args) -> bool:
        """Prevent adding from django admin."""
        return False
    def has_change_permission(self, *args) -> bool:
        """Prevent editing from django admin."""
        return False
class DeletedMessageInline(admin.TabularInline):
    """Tabular Inline Admin model for Deleted Message to be viewed within Context."""
    model = DeletedMessage
@admin.register(FilterList)
class FilterListAdmin(admin.ModelAdmin):
    """Admin formatting for the FilterList model."""
@admin.register(Filter)
class FilterAdmin(admin.ModelAdmin):
    """Admin formatting for the Filter model."""
@admin.register(MessageDeletionContext)
class MessageDeletionContextAdmin(admin.ModelAdmin):
    """Admin formatting for the MessageDeletionContext model."""
    fields = ("actor", "creation")
    list_display = ("id", "creation", "actor")
    inlines = (DeletedMessageInline,)
    def has_add_permission(self, *args) -> bool:
        """Prevent adding from django admin."""
        return False
    def has_change_permission(self, *args) -> bool:
        """Prevent editing from django admin."""
        return False
class NominationActorFilter(admin.SimpleListFilter):
    """Actor Filter for Nomination Admin list page."""
    title = "Actor"
    parameter_name = "actor"
    def lookups(self, request: HttpRequest, model: NominationAdmin) -> Iterable[tuple[int, str]]:
        """Selectable values for viewer to filter by."""
        actor_ids = NominationEntry.objects.order_by().values_list("actor").distinct()
        actors = User.objects.filter(id__in=actor_ids)
        return ((a.id, a.username) for a in actors)
    def queryset(self, request: HttpRequest, queryset: QuerySet) -> QuerySet | None:
        """Query to filter the list of Users against."""
        if not self.value():
            return None
        nomination_ids = NominationEntry.objects.filter(
            actor__id=self.value()
        ).values_list("nomination_id").distinct()
        return queryset.filter(id__in=nomination_ids)
@admin.register(Nomination)
class NominationAdmin(admin.ModelAdmin):
    """Admin formatting for the Nomination model."""
    search_fields = (
        "user__name",
        "user__id",
        "end_reason"
    )
    list_filter = ("active", NominationActorFilter)
    list_display = (
        "user",
        "active",
        "reviewed"
    )
    fields = (
        "user",
        "active",
        "inserted_at",
        "ended_at",
        "end_reason",
        "reviewed"
    )
    # only allow end reason field to be edited.
    readonly_fields = (
        "user",
        "active",
        "inserted_at",
        "ended_at",
        "reviewed"
    )
    def has_add_permission(self, *args) -> bool:
        """Prevent adding from django admin."""
        return False
class NominationEntryActorFilter(admin.SimpleListFilter):
    """Actor Filter for NominationEntry Admin list page."""
    title = "Actor"
    parameter_name = "actor"
    def lookups(self, request: HttpRequest, model: NominationAdmin) -> Iterable[tuple[int, str]]:
        """Selectable values for viewer to filter by."""
        actor_ids = NominationEntry.objects.order_by().values_list("actor").distinct()
        actors = User.objects.filter(id__in=actor_ids)
        return ((a.id, a.username) for a in actors)
    def queryset(self, request: HttpRequest, queryset: QuerySet) -> QuerySet | None:
        """Query to filter the list of Users against."""
        if not self.value():
            return None
        return queryset.filter(actor__id=self.value())
@admin.register(NominationEntry)
class NominationEntryAdmin(admin.ModelAdmin):
    """Admin formatting for the NominationEntry model."""
    search_fields = (
        "actor__name",
        "actor__id",
        "reason",
    )
    list_filter = (NominationEntryActorFilter,)
    list_display = (
        "nomination",
        "actor",
    )
    fields = (
        "nomination",
        "actor",
        "reason",
        "inserted_at",
    )
    # only allow reason field to be edited
    readonly_fields = (
        "nomination",
        "actor",
        "inserted_at",
    )
    def has_add_permission(self, request: HttpRequest) -> bool:
        """Disable adding new nomination entry from admin."""
        return False
@admin.register(OffTopicChannelName)
class OffTopicChannelNameAdmin(admin.ModelAdmin):
    """Admin formatting for the OffTopicChannelName model."""
    search_fields = ("name",)
    list_filter = ("used",)
@admin.register(OffensiveMessage)
class OffensiveMessageAdmin(admin.ModelAdmin):
    """Admin formatting for the OffensiveMessage model."""
    def message_jumplink(self, message: OffensiveMessage) -> SafeString:
        """Message ID hyperlinked to the direct discord jumplink."""
        return format_html(
            '{1}',
            message.channel_id,
            message.id
        )
    message_jumplink.short_description = "Message ID"
    search_fields = ("id", "channel_id")
    list_display = ("id", "channel_id", "delete_date")
    fields = ("message_jumplink", "channel_id", "delete_date")
    readonly_fields = ("message_jumplink", "channel_id")
    def has_add_permission(self, *args) -> bool:
        """Prevent adding from django admin."""
        return False
@admin.register(Role)
class RoleAdmin(admin.ModelAdmin):
    """Admin formatting for the Role model."""
    def coloured_name(self, role: Role) -> SafeString:
        """Role name with html style colouring."""
        return format_html(
            '{1}',
            f"#{role.colour:06X}",
            role.name
        )
    coloured_name.short_description = "Name"
    def colour_with_preview(self, role: Role) -> SafeString:
        """Show colour value in both int and hex, in bolded and coloured style."""
        return format_html(
            "{0} ({1})",
            f"#{role.colour:06x}",
            role.colour
        )
    colour_with_preview.short_description = "Colour"
    def permissions_with_calc_link(self, role: Role) -> SafeString:
        """Show permissions with link to API permissions calculator page."""
        return format_html(
            "{0}",
            role.permissions
        )
    permissions_with_calc_link.short_description = "Permissions"
    search_fields = ("name", "id")
    list_display = ("coloured_name",)
    fields = ("id", "name", "colour_with_preview", "permissions_with_calc_link", "position")
    def has_add_permission(self, *args) -> bool:
        """Prevent adding from django admin."""
        return False
    def has_change_permission(self, *args) -> bool:
        """Prevent editing from django admin."""
        return False
class UserRoleFilter(admin.SimpleListFilter):
    """List Filter for User list Admin page."""
    title = "Role"
    parameter_name = "role"
    def lookups(self, request: HttpRequest, model: UserAdmin) -> Iterable[tuple[str, str]]:
        """Selectable values for viewer to filter by."""
        roles = Role.objects.all()
        return ((r.name, r.name) for r in roles)
    def queryset(self, request: HttpRequest, queryset: QuerySet) -> QuerySet | None:
        """Query to filter the list of Users against."""
        if not self.value():
            return None
        role = Role.objects.get(name=self.value())
        return queryset.filter(roles__contains=[role.id])
@admin.register(User)
class UserAdmin(admin.ModelAdmin):
    """Admin formatting for the User model."""
    def top_role_coloured(self, user: User) -> SafeString:
        """Returns the top role of the user with html style matching role colour."""
        return format_html(
            '{1}',
            f"#{user.top_role.colour:06X}",
            user.top_role.name
        )
    top_role_coloured.short_description = "Top Role"
    def all_roles_coloured(self, user: User) -> SafeString:
        """Returns all user roles with html style matching role colours."""
        roles = Role.objects.filter(id__in=user.roles)
        return format_html(
            "".join(
                f'{r.name}'
                for r in roles
            )
        )
    all_roles_coloured.short_description = "All Roles"
    search_fields = ("name", "id", "roles")
    list_filter = (UserRoleFilter, "in_guild")
    list_display = ("username", "top_role_coloured", "in_guild")
    fields = ("username", "id", "in_guild", "all_roles_coloured")
    sortable_by = ("username",)
    def has_add_permission(self, *args) -> bool:
        """Prevent adding from django admin."""
        return False
    def has_change_permission(self, *args) -> bool:
        """Prevent editing from django admin."""
        return False