from __future__ import annotations import json from typing import Optional from django import urls from django.contrib import admin from django.http import HttpRequest from django.utils.html import format_html from .models import ( BotSetting, DeletedMessage, DocumentationLink, Infraction, LogEntry, MessageDeletionContext, Nomination, OffTopicChannelName, OffensiveMessage, Role, User ) @admin.register(LogEntry) class LogEntryAdmin(admin.ModelAdmin): """Allows viewing logs in the Django Admin without allowing edits.""" actions = None list_display = ('timestamp', 'application', 'level', 'message') fieldsets = ( ('Overview', {'fields': ('timestamp', 'application', 'logger_name')}), ('Metadata', {'fields': ('level', 'module', 'line')}), ('Contents', {'fields': ('message',)}) ) list_filter = ('application', 'level', 'timestamp') search_fields = ('message',) readonly_fields = ( 'application', 'logger_name', 'timestamp', 'level', 'module', 'line', 'message' ) def has_add_permission(self, request: HttpRequest) -> bool: """Deny manual LogEntry creation.""" return False def has_delete_permission(self, request: HttpRequest, obj: Optional[LogEntry] = None) -> bool: """Deny LogEntry deletion.""" return False @admin.register(DeletedMessage) class DeletedMessageAdmin(admin.ModelAdmin): """Admin formatting for the DeletedMessage model.""" readonly_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" ) @staticmethod def embed_data(instance: DeletedMessage) -> Optional[str]: """Format embed data in a code block for better readability.""" if instance.embeds: return format_html( "
"
                "{0}
", json.dumps(instance.embeds, indent=4) ) @staticmethod def context(instance: DeletedMessage) -> str: """Provide full context info with a link through to context admin view.""" link = urls.reverse( "admin:api_messagedeletioncontext_change", args=[instance.deletion_context.id] ) details = ( f"Deleted by {instance.deletion_context.actor} at " f"{instance.deletion_context.creation}" ) return format_html("{1}", link, details) @staticmethod def view_full_log(instance: DeletedMessage) -> str: """Provide a link to the message logs for the relevant context.""" return format_html( "Click to view full context log", instance.deletion_context.log_url ) @admin.register(MessageDeletionContext) class MessageDeletionContextAdmin(admin.ModelAdmin): """Admin formatting for the MessageDeletionContext model.""" readonly_fields = ("actor", "creation", "message_log") @staticmethod def message_log(instance: MessageDeletionContext) -> str: """Provide a formatted link to the message logs for the context.""" return format_html( "Click to see deleted message log", instance.log_url ) @admin.register(Infraction) class InfractionAdmin(admin.ModelAdmin): """Admin formatting for the Infraction model.""" fields = ( "user", "actor", "type", "reason", "inserted_at", "expires_at", "active", "hidden" ) readonly_fields = ( "user", "actor", "type", "inserted_at" ) list_display = ( "type", "user", "actor", "inserted_at", "expires_at", "reason", "active", ) search_fields = ( "id", "user__name", "user__id", "actor__name", "actor__id", "reason", "type" ) list_filter = ( "type", "hidden", "active" ) @admin.register(Nomination) class NominationAdmin(admin.ModelAdmin): """Admin formatting for the Nomination model.""" list_display = ( "user", "active", "reason", "actor", "inserted_at", "ended_at" ) fields = ( "user", "active", "actor", "reason", "inserted_at", "ended_at", "end_reason" ) readonly_fields = ( "user", "active", "actor", "inserted_at", "ended_at" ) search_fields = ( "actor__name", "actor__id", "user__name", "user__id", "reason" ) list_filter = ("active",) @admin.register(OffTopicChannelName) class OffTopicChannelNameAdmin(admin.ModelAdmin): """Admin formatting for the OffTopicChannelName model.""" search_fields = ("name",) @admin.register(Role) class RoleAdmin(admin.ModelAdmin): """Admin formatting for the Role model.""" exclude = ("permissions", "colour") readonly_fields = ( "name", "id", "colour_with_preview", "permissions_with_calc_link", "position" ) search_fields = ("name", "id") def colour_with_preview(self, instance: Role) -> str: """Show colour value in both int and hex, in bolded and coloured style.""" return format_html( "{1} / #{0}", f"{instance.colour:06x}", instance.colour ) def permissions_with_calc_link(self, instance: Role) -> str: """Show permissions with link to API permissions calculator page.""" return format_html( "{0}", instance.permissions ) colour_with_preview.short_description = "Colour" permissions_with_calc_link.short_description = "Permissions" class UserTopRoleFilter(admin.SimpleListFilter): """List Filter for User list Admin page.""" title = "Role" parameter_name = "role" def lookups(self, request, model_admin: UserAdmin): """Selectable values for viewer to filter by.""" roles = Role.objects.all() return ((r.name, r.name) for r in roles) def queryset(self, request, queryset): if not self.value(): return 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, obj: User): """Returns the top role of the user with html style matching role colour.""" return format_html( f'{obj.top_role.name}' ) top_role_coloured.short_description = "Top Role" def all_roles_coloured(self, obj: User): """Returns all user roles with html style matching role colours.""" roles = Role.objects.filter(id__in=obj.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 = (UserTopRoleFilter, "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, request): return False def has_change_permission(self, request, obj=None): return False admin.site.register(BotSetting) admin.site.register(DocumentationLink) admin.site.register(OffensiveMessage)