aboutsummaryrefslogtreecommitdiffstats
path: root/pydis_site/apps/api/admin.py
diff options
context:
space:
mode:
authorGravatar ks129 <[email protected]>2020-10-07 16:10:17 +0300
committerGravatar GitHub <[email protected]>2020-10-07 16:10:17 +0300
commit469bbb9da632e8dfe787b7a446bfacfd55b5c5ad (patch)
treeea8abddca51e42ebdf6422b4b72baa307d658cfd /pydis_site/apps/api/admin.py
parentUpdate guides URL to match with latest changes in #393 (diff)
parentMerge pull request #405 from python-discord/remove_django_wiki (diff)
Merge branch 'dewikification' into resources-home
Diffstat (limited to 'pydis_site/apps/api/admin.py')
-rw-r--r--pydis_site/apps/api/admin.py431
1 files changed, 403 insertions, 28 deletions
diff --git a/pydis_site/apps/api/admin.py b/pydis_site/apps/api/admin.py
index dd1291b8..5093e605 100644
--- a/pydis_site/apps/api/admin.py
+++ b/pydis_site/apps/api/admin.py
@@ -1,7 +1,13 @@
-from typing import Optional
+from __future__ import annotations
+import json
+from typing import Iterable, Optional, Tuple
+
+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,
@@ -17,50 +23,419 @@ from .models import (
User
)
+admin.site.site_header = "Python Discord | Administration"
+admin.site.site_title = "Python Discord"
+
+
[email protected](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
+
+
[email protected](DocumentationLink)
+class DocumentationLinkAdmin(admin.ModelAdmin):
+ """Admin formatting for the DocumentationLink model."""
+
+ fields = ("package", "base_url", "inventory_url")
+ list_display = ("package", "base_url", "inventory_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) -> Optional[QuerySet]:
+ """Query to filter the list of Users against."""
+ if not self.value():
+ return
+ return queryset.filter(actor__id=self.value())
+
+
[email protected](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
+
class LogEntryAdmin(admin.ModelAdmin):
"""Allows viewing logs in the Django Admin without allowing edits."""
actions = None
- list_display = ('timestamp', 'application', 'level', 'message')
+ list_display = ('timestamp', 'level', 'message')
fieldsets = (
('Overview', {'fields': ('timestamp', 'application', 'logger_name')}),
('Metadata', {'fields': ('level', 'module', 'line')}),
('Contents', {'fields': ('message',)})
)
- list_filter = ('application', 'level', 'timestamp')
+ list_filter = ('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:
+ def has_change_permission(self, *args) -> bool:
+ """Prevent editing from django admin."""
+ return False
+
+ def has_delete_permission(self, request: HttpRequest, obj: Optional[LogEntry] = None) -> bool:
"""Deny LogEntry deletion."""
return False
-admin.site.register(BotSetting)
-admin.site.register(DeletedMessage)
-admin.site.register(DocumentationLink)
-admin.site.register(Infraction)
-admin.site.register(LogEntry, LogEntryAdmin)
-admin.site.register(MessageDeletionContext)
-admin.site.register(Nomination)
-admin.site.register(OffensiveMessage)
-admin.site.register(OffTopicChannelName)
-admin.site.register(Role)
-admin.site.register(User)
[email protected](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) -> Optional[str]:
+ """Format embed data in a code block for better readability."""
+ if message.embeds:
+ return format_html(
+ "<pre style='word-wrap: break-word; white-space: pre-wrap; overflow-x: auto;'>"
+ "<code>{0}</code></pre>",
+ json.dumps(message.embeds, indent=4)
+ )
+
+ 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("<a href='{0}'>{1}</a>", link, details)
+
+ @staticmethod
+ def view_full_log(message: DeletedMessage) -> str:
+ """Provide a link to the message logs for the relevant context."""
+ return format_html(
+ "<a href='{0}'>Click to view full context log</a>",
+ 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
+
+
[email protected](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 = Nomination.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) -> Optional[QuerySet]:
+ """Query to filter the list of Users against."""
+ if not self.value():
+ return
+ return queryset.filter(actor__id=self.value())
+
+
[email protected](Nomination)
+class NominationAdmin(admin.ModelAdmin):
+ """Admin formatting for the Nomination model."""
+
+ search_fields = (
+ "user__name",
+ "user__id",
+ "actor__name",
+ "actor__id",
+ "reason",
+ "end_reason"
+ )
+
+ list_filter = ("active", NominationActorFilter)
+
+ list_display = (
+ "user",
+ "active",
+ "reason",
+ "actor",
+ )
+
+ fields = (
+ "user",
+ "active",
+ "actor",
+ "reason",
+ "inserted_at",
+ "ended_at",
+ "end_reason"
+ )
+
+ # only allow reason fields to be edited.
+ readonly_fields = (
+ "user",
+ "active",
+ "actor",
+ "inserted_at",
+ "ended_at"
+ )
+
+ def has_add_permission(self, *args) -> bool:
+ """Prevent adding from django admin."""
+ return False
+
+
[email protected](OffTopicChannelName)
+class OffTopicChannelNameAdmin(admin.ModelAdmin):
+ """Admin formatting for the OffTopicChannelName model."""
+
+ search_fields = ("name",)
+ list_filter = ("used",)
+
+
[email protected](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(
+ '<a href="https://canary.discordapp.com/channels/267624335836053506/{0}/{1}">{1}</a>',
+ 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
+
+
+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(
+ '<span style="color: {0}!important; font-weight: bold;">{1}</span>',
+ 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(
+ "<span style='color: {0}; font-weight: bold;'>{0} ({1})</span>",
+ 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(
+ "<a href='https://discordapi.com/permissions.html#{0}' target='_blank'>{0}</a>",
+ 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) -> Optional[QuerySet]:
+ """Query to filter the list of Users against."""
+ if not self.value():
+ return
+ role = Role.objects.get(name=self.value())
+ return queryset.filter(roles__contains=[role.id])
+
+
+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(
+ '<span style="color: {0}; font-weight: bold;">{1}</span>',
+ 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(
+ "</br>".join(
+ f'<span style="color: #{r.colour:06X}; font-weight: bold;">{r.name}</span>'
+ 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