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