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