From e2956b87289a37747cb5431ad3a08dc202a2bcba Mon Sep 17 00:00:00 2001 From: scragly <29337040+scragly@users.noreply.github.com> Date: Sun, 20 Oct 2019 05:37:15 +1000 Subject: Set newest-first sorting for message deletion models, add log_url property. --- pydis_site/apps/api/models/bot/deleted_message.py | 4 ++-- pydis_site/apps/api/models/bot/message_deletion_context.py | 12 ++++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) (limited to 'pydis_site/apps/api/models/bot') diff --git a/pydis_site/apps/api/models/bot/deleted_message.py b/pydis_site/apps/api/models/bot/deleted_message.py index 1eb4516e..50b70d8c 100644 --- a/pydis_site/apps/api/models/bot/deleted_message.py +++ b/pydis_site/apps/api/models/bot/deleted_message.py @@ -14,6 +14,6 @@ class DeletedMessage(Message): ) class Meta: - """Sets the default ordering for list views to oldest first.""" + """Sets the default ordering for list views to newest first.""" - ordering = ["id"] + ordering = ("-id",) diff --git a/pydis_site/apps/api/models/bot/message_deletion_context.py b/pydis_site/apps/api/models/bot/message_deletion_context.py index 44a0c8ae..02a15ca0 100644 --- a/pydis_site/apps/api/models/bot/message_deletion_context.py +++ b/pydis_site/apps/api/models/bot/message_deletion_context.py @@ -1,3 +1,4 @@ +from django.contrib.sites.models import Site from django.db import models from pydis_site.apps.api.models.bot.user import User @@ -28,3 +29,14 @@ class MessageDeletionContext(ModelReprMixin, models.Model): # the deletion context does not take place in the future. help_text="When this deletion took place." ) + + @property + def log_url(self) -> str: + """Create the url for the deleted message logs.""" + domain = Site.objects.get_current().domain + return f"http://staff.{domain}/bot/logs/{self.id}/" + + class Meta: + """Set the ordering for list views to newest first.""" + + ordering = ("-creation",) -- cgit v1.2.3 From 618610fe367b0c8d6175d251c276c2e37db8aa52 Mon Sep 17 00:00:00 2001 From: scragly <29337040+scragly@users.noreply.github.com> Date: Sun, 20 Oct 2019 05:43:21 +1000 Subject: Order roles by positioning, add filters and search to api user admin page. --- pydis_site/apps/api/admin.py | 45 ++++++++++++++++++++++++++++++++-- pydis_site/apps/api/models/bot/role.py | 5 ++++ 2 files changed, 48 insertions(+), 2 deletions(-) (limited to 'pydis_site/apps/api/models/bot') diff --git a/pydis_site/apps/api/admin.py b/pydis_site/apps/api/admin.py index 6d6a9b3b..65cc0a6c 100644 --- a/pydis_site/apps/api/admin.py +++ b/pydis_site/apps/api/admin.py @@ -1,8 +1,9 @@ import json -from typing import Optional +from typing import Optional, Tuple from django import urls from django.contrib import admin +from django.db.models.query import QuerySet from django.http import HttpRequest from django.utils.html import format_html @@ -121,6 +122,46 @@ class MessageDeletionContextAdmin(admin.ModelAdmin): ) +class StaffRolesFilter(admin.SimpleListFilter): + """Filter options for Staff Roles.""" + + title = "Staff Role" + parameter_name = "staff_role" + + @staticmethod + def lookups(*_) -> Tuple[Tuple[str, str], ...]: + """Available filter options.""" + return ( + ("Owners", "Owners"), + ("Admins", "Admins"), + ("Moderators", "Moderators"), + ("Core Developers", "Core Developers"), + ("Helpers", "Helpers"), + ) + + def queryset(self, request: HttpRequest, queryset: QuerySet) -> Optional[QuerySet]: + """Returned data filter based on selected option.""" + value = self.value() + if value: + return queryset.filter(roles__name=value) + + +class UserAdmin(admin.ModelAdmin): + """Admin formatting for the User model.""" + + search_fields = ("name", "id", "roles__name", "roles__id") + list_filter = ("in_guild", StaffRolesFilter) + exclude = ("name", "discriminator") + readonly_fields = ( + "__str__", + "id", + "avatar_hash", + "top_role", + "roles", + "in_guild", + ) + + admin.site.register(BotSetting) admin.site.register(DeletedMessage, DeletedMessageAdmin) admin.site.register(DocumentationLink) @@ -131,4 +172,4 @@ admin.site.register(Nomination) admin.site.register(OffTopicChannelName) admin.site.register(Role) admin.site.register(Tag) -admin.site.register(User) +admin.site.register(User, UserAdmin) diff --git a/pydis_site/apps/api/models/bot/role.py b/pydis_site/apps/api/models/bot/role.py index 58bbf8b4..b95740da 100644 --- a/pydis_site/apps/api/models/bot/role.py +++ b/pydis_site/apps/api/models/bot/role.py @@ -65,3 +65,8 @@ class Role(ModelReprMixin, models.Model): def __le__(self, other: Role) -> bool: """Compares the roles based on their position in the role hierarchy of the guild.""" return self.position <= other.position + + class Meta: + """Set role ordering from highest to lowest position.""" + + ordering = ("-position",) -- cgit v1.2.3 From 1c41c8a1aa07d7a716561c7594f769db7aa58cf5 Mon Sep 17 00:00:00 2001 From: scragly <29337040+scragly@users.noreply.github.com> Date: Sun, 20 Oct 2019 06:33:35 +1000 Subject: Improve nominations admin list and page, add search and filter by active. --- pydis_site/apps/api/admin.py | 39 +++++++++++++++++++++++++++- pydis_site/apps/api/models/bot/nomination.py | 5 ++++ 2 files changed, 43 insertions(+), 1 deletion(-) (limited to 'pydis_site/apps/api/models/bot') diff --git a/pydis_site/apps/api/admin.py b/pydis_site/apps/api/admin.py index 74b9413b..55a9d655 100644 --- a/pydis_site/apps/api/admin.py +++ b/pydis_site/apps/api/admin.py @@ -166,6 +166,43 @@ class InfractionAdmin(admin.ModelAdmin): ) +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",) + + class StaffRolesFilter(admin.SimpleListFilter): """Filter options for Staff Roles.""" @@ -212,7 +249,7 @@ admin.site.register(DocumentationLink) admin.site.register(Infraction, InfractionAdmin) admin.site.register(LogEntry, LogEntryAdmin) admin.site.register(MessageDeletionContext, MessageDeletionContextAdmin) -admin.site.register(Nomination) +admin.site.register(Nomination, NominationAdmin) admin.site.register(OffTopicChannelName) admin.site.register(Role) admin.site.register(Tag) diff --git a/pydis_site/apps/api/models/bot/nomination.py b/pydis_site/apps/api/models/bot/nomination.py index cd9951aa..a0ba42a3 100644 --- a/pydis_site/apps/api/models/bot/nomination.py +++ b/pydis_site/apps/api/models/bot/nomination.py @@ -44,3 +44,8 @@ class Nomination(ModelReprMixin, models.Model): """Representation that makes the target and state of the nomination immediately evident.""" status = "active" if self.active else "ended" return f"Nomination of {self.user} ({status})" + + class Meta: + """Set the ordering of nominations to most recent first.""" + + ordering = ("-inserted_at",) -- cgit v1.2.3 From 56c6c85ad973a3b594d72215cdfb2d3b6c19d741 Mon Sep 17 00:00:00 2001 From: scragly <29337040+scragly@users.noreply.github.com> Date: Sun, 20 Oct 2019 09:29:58 +1000 Subject: Add new test for deleted message context log_url. --- .../api/models/bot/message_deletion_context.py | 5 ++--- pydis_site/apps/api/tests/test_deleted_messages.py | 22 ++++++++++++++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) (limited to 'pydis_site/apps/api/models/bot') diff --git a/pydis_site/apps/api/models/bot/message_deletion_context.py b/pydis_site/apps/api/models/bot/message_deletion_context.py index 02a15ca0..fde9b0a6 100644 --- a/pydis_site/apps/api/models/bot/message_deletion_context.py +++ b/pydis_site/apps/api/models/bot/message_deletion_context.py @@ -1,5 +1,5 @@ -from django.contrib.sites.models import Site from django.db import models +from django_hosts.resolvers import reverse from pydis_site.apps.api.models.bot.user import User from pydis_site.apps.api.models.utils import ModelReprMixin @@ -33,8 +33,7 @@ class MessageDeletionContext(ModelReprMixin, models.Model): @property def log_url(self) -> str: """Create the url for the deleted message logs.""" - domain = Site.objects.get_current().domain - return f"http://staff.{domain}/bot/logs/{self.id}/" + return reverse('logs', host="staff", args=(self.id,)) class Meta: """Set the ordering for list views to newest first.""" diff --git a/pydis_site/apps/api/tests/test_deleted_messages.py b/pydis_site/apps/api/tests/test_deleted_messages.py index d1e9f2f5..ccccdda4 100644 --- a/pydis_site/apps/api/tests/test_deleted_messages.py +++ b/pydis_site/apps/api/tests/test_deleted_messages.py @@ -1,5 +1,6 @@ from datetime import datetime +from django.utils import timezone from django_hosts.resolvers import reverse from .base import APISubdomainTestCase @@ -75,3 +76,24 @@ class DeletedMessagesWithActorTests(APISubdomainTestCase): self.assertEqual(response.status_code, 201) [context] = MessageDeletionContext.objects.all() self.assertEqual(context.actor.id, self.actor.id) + + +class DeletedMessagesLogURLTests(APISubdomainTestCase): + @classmethod + def setUpTestData(cls): # noqa + cls.author = cls.actor = User.objects.create( + id=324888, + name='Black Knight', + discriminator=1975, + avatar_hash=None + ) + + cls.deletion_context = MessageDeletionContext.objects.create( + actor=cls.actor, + creation=timezone.now() + ) + + def test_valid_log_url(self): + expected_url = reverse('logs', host="staff", args=(1,)) + [context] = MessageDeletionContext.objects.all() + self.assertEqual(context.log_url, expected_url) -- cgit v1.2.3 From 49e7243f6527140dbdb29a2f1dd994839f248dba Mon Sep 17 00:00:00 2001 From: Eivind Teig Date: Fri, 11 Sep 2020 23:43:06 +0200 Subject: Allow blank/null input to the nomination reason. We are lowering the threshold for nomination. By allowing the users to make a nomination without a reason might make this feature more attractive amongst members of staff. --- .../0063_Allow_blank_or_null_for_nomination_reason.py | 18 ++++++++++++++++++ pydis_site/apps/api/models/bot/nomination.py | 4 +++- 2 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 pydis_site/apps/api/migrations/0063_Allow_blank_or_null_for_nomination_reason.py (limited to 'pydis_site/apps/api/models/bot') diff --git a/pydis_site/apps/api/migrations/0063_Allow_blank_or_null_for_nomination_reason.py b/pydis_site/apps/api/migrations/0063_Allow_blank_or_null_for_nomination_reason.py new file mode 100644 index 00000000..9eb05eaa --- /dev/null +++ b/pydis_site/apps/api/migrations/0063_Allow_blank_or_null_for_nomination_reason.py @@ -0,0 +1,18 @@ +# Generated by Django 3.0.9 on 2020-09-11 21:06 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0062_merge_20200901_1459'), + ] + + operations = [ + migrations.AlterField( + model_name='nomination', + name='reason', + field=models.TextField(blank=True, help_text='Why this user was nominated.', null=True), + ), + ] diff --git a/pydis_site/apps/api/models/bot/nomination.py b/pydis_site/apps/api/models/bot/nomination.py index 21e34e87..183b22d5 100644 --- a/pydis_site/apps/api/models/bot/nomination.py +++ b/pydis_site/apps/api/models/bot/nomination.py @@ -18,7 +18,9 @@ class Nomination(ModelReprMixin, models.Model): related_name='nomination_set' ) reason = models.TextField( - help_text="Why this user was nominated." + help_text="Why this user was nominated.", + null=True, + blank=True ) user = models.ForeignKey( User, -- cgit v1.2.3 From a311ad08003203a5aa38bf49fec69d6d42f74439 Mon Sep 17 00:00:00 2001 From: scragly <29337040+scragly@users.noreply.github.com> Date: Fri, 18 Sep 2020 11:48:54 +1000 Subject: Update UserAdmin to use new role values, pretty colours. --- pydis_site/apps/api/admin.py | 78 ++++++++++++++++++++-------------- pydis_site/apps/api/models/bot/user.py | 4 ++ 2 files changed, 50 insertions(+), 32 deletions(-) (limited to 'pydis_site/apps/api/models/bot') diff --git a/pydis_site/apps/api/admin.py b/pydis_site/apps/api/admin.py index 6116fbf8..6101f88c 100644 --- a/pydis_site/apps/api/admin.py +++ b/pydis_site/apps/api/admin.py @@ -1,9 +1,10 @@ +from __future__ import annotations + import json -from typing import Optional, Tuple +from typing import Optional from django import urls from django.contrib import admin -from django.db.models.query import QuerySet from django.http import HttpRequest from django.utils.html import format_html @@ -249,45 +250,58 @@ class RoleAdmin(admin.ModelAdmin): permissions_with_calc_link.short_description = "Permissions" -class StaffRolesFilter(admin.SimpleListFilter): - """Filter options for Staff Roles.""" +class UserTopRoleFilter(admin.SimpleListFilter): + """List Filter for User list Admin page.""" - title = "Staff Role" - parameter_name = "staff_role" + title = "Role" + parameter_name = "role" - @staticmethod - def lookups(*_) -> Tuple[Tuple[str, str], ...]: - """Available filter options.""" - return ( - ("Owners", "Owners"), - ("Admins", "Admins"), - ("Moderators", "Moderators"), - ("Core Developers", "Core Developers"), - ("Helpers", "Helpers"), - ) + 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: HttpRequest, queryset: QuerySet) -> Optional[QuerySet]: - """Returned data filter based on selected option.""" - value = self.value() - if value: - return queryset.filter(roles__name=value) + 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.""" - search_fields = ("name", "id", "roles__name", "roles__id") - list_filter = ("in_guild", StaffRolesFilter) - exclude = ("name", "discriminator") - readonly_fields = ( - "__str__", - "id", - "avatar_hash", - "top_role", - "roles", - "in_guild", - ) + 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) diff --git a/pydis_site/apps/api/models/bot/user.py b/pydis_site/apps/api/models/bot/user.py index cd2d58b9..70a30449 100644 --- a/pydis_site/apps/api/models/bot/user.py +++ b/pydis_site/apps/api/models/bot/user.py @@ -75,3 +75,7 @@ class User(ModelReprMixin, models.Model): if not roles: return Role.objects.get(name="Developers") return max(roles) + + @property + def username(self): + return f"{self.name}#{self.discriminator:04d}" -- cgit v1.2.3 From dfbce19d39b0b6243865e682b8e6d86b8a028a71 Mon Sep 17 00:00:00 2001 From: scragly <29337040+scragly@users.noreply.github.com> Date: Fri, 18 Sep 2020 12:00:13 +1000 Subject: Add verbose names for user fields that need capitalisation fixes. --- pydis_site/apps/api/models/bot/user.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'pydis_site/apps/api/models/bot') diff --git a/pydis_site/apps/api/models/bot/user.py b/pydis_site/apps/api/models/bot/user.py index 70a30449..a8604001 100644 --- a/pydis_site/apps/api/models/bot/user.py +++ b/pydis_site/apps/api/models/bot/user.py @@ -26,11 +26,12 @@ class User(ModelReprMixin, models.Model): message="User IDs cannot be negative." ), ), + verbose_name="ID", help_text="The ID of this user, taken from Discord." ) name = models.CharField( max_length=32, - help_text="The username, taken from Discord." + help_text="The username, taken from Discord.", ) discriminator = models.PositiveSmallIntegerField( validators=( @@ -57,7 +58,8 @@ class User(ModelReprMixin, models.Model): ) in_guild = models.BooleanField( default=True, - help_text="Whether this user is in our server." + help_text="Whether this user is in our server.", + verbose_name="In Guild" ) def __str__(self): -- cgit v1.2.3 From 77c9e3ffce7992706202cf20ae3addf42c4dbf6c Mon Sep 17 00:00:00 2001 From: scragly <29337040+scragly@users.noreply.github.com> Date: Fri, 18 Sep 2020 12:40:24 +1000 Subject: Add return types and docstrings for new user admin changes. --- pydis_site/apps/api/admin.py | 29 ++++++++++++++++++----------- pydis_site/apps/api/models/bot/user.py | 11 ++++++++--- 2 files changed, 26 insertions(+), 14 deletions(-) (limited to 'pydis_site/apps/api/models/bot') diff --git a/pydis_site/apps/api/admin.py b/pydis_site/apps/api/admin.py index 6101f88c..d77ae620 100644 --- a/pydis_site/apps/api/admin.py +++ b/pydis_site/apps/api/admin.py @@ -1,12 +1,13 @@ from __future__ import annotations import json -from typing import Optional +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 format_html +from django.utils.html import SafeString, format_html from .models import ( BotSetting, @@ -256,12 +257,13 @@ class UserTopRoleFilter(admin.SimpleListFilter): title = "Role" parameter_name = "role" - def lookups(self, request, model_admin: UserAdmin): + def lookups(self, request: HttpRequest, model_admin: 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, queryset): + 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()) @@ -272,20 +274,23 @@ class UserTopRoleFilter(admin.SimpleListFilter): class UserAdmin(admin.ModelAdmin): """Admin formatting for the User model.""" - def top_role_coloured(self, obj: User): + def top_role_coloured(self, user: User) -> SafeString: """Returns the top role of the user with html style matching role colour.""" return format_html( - f'{obj.top_role.name}' + '{1}', + user.top_role.colour, + user.top_role.name ) top_role_coloured.short_description = "Top Role" - def all_roles_coloured(self, obj: User): + def all_roles_coloured(self, user: User) -> SafeString: """Returns all user roles with html style matching role colours.""" - roles = Role.objects.filter(id__in=obj.roles) + roles = Role.objects.filter(id__in=user.roles) return format_html( "
".join( - f'{r.name}' for r in roles + f'{r.name}' + for r in roles ) ) @@ -297,10 +302,12 @@ class UserAdmin(admin.ModelAdmin): fields = ("username", "id", "in_guild", "all_roles_coloured") sortable_by = ("username",) - def has_add_permission(self, request): + def has_add_permission(self, *args) -> bool: + """Prevent adding from django admin.""" return False - def has_change_permission(self, request, obj=None): + def has_change_permission(self, *args) -> bool: + """Prevent editing from django admin.""" return False diff --git a/pydis_site/apps/api/models/bot/user.py b/pydis_site/apps/api/models/bot/user.py index a8604001..afc5ba1e 100644 --- a/pydis_site/apps/api/models/bot/user.py +++ b/pydis_site/apps/api/models/bot/user.py @@ -64,7 +64,7 @@ class User(ModelReprMixin, models.Model): def __str__(self): """Returns the name and discriminator for the current user, for display purposes.""" - return f"{self.name}#{self.discriminator:0>4}" + return f"{self.name}#{self.discriminator:04d}" @property def top_role(self) -> Role: @@ -79,5 +79,10 @@ class User(ModelReprMixin, models.Model): return max(roles) @property - def username(self): - return f"{self.name}#{self.discriminator:04d}" + def username(self) -> str: + """ + Returns the display version with name and discriminator as a standard attribute. + + For usability in read-only fields such as Django Admin. + """ + return str(self) -- cgit v1.2.3 From bbecf18704837f087e8254b657adf1c5e859dff9 Mon Sep 17 00:00:00 2001 From: scragly <29337040+scragly@users.noreply.github.com> Date: Fri, 18 Sep 2020 12:47:32 +1000 Subject: Update Role ModelAdmin to past changes, cleanup formatting. --- pydis_site/apps/api/admin.py | 45 ++++++++++++++++++++++------------ pydis_site/apps/api/models/bot/role.py | 3 ++- 2 files changed, 31 insertions(+), 17 deletions(-) (limited to 'pydis_site/apps/api/models/bot') diff --git a/pydis_site/apps/api/admin.py b/pydis_site/apps/api/admin.py index 15a1ec78..d32b4911 100644 --- a/pydis_site/apps/api/admin.py +++ b/pydis_site/apps/api/admin.py @@ -222,34 +222,47 @@ class OffTopicChannelNameAdmin(admin.ModelAdmin): 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 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, instance: Role) -> str: + def colour_with_preview(self, role: Role) -> SafeString: """Show colour value in both int and hex, in bolded and coloured style.""" return format_html( - "{1} / #{0}", - f"{instance.colour:06x}", - instance.colour + "{0} ({1})", + f"#{role.colour:06x}", + role.colour ) - def permissions_with_calc_link(self, instance: Role) -> str: + 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}", - instance.permissions + role.permissions ) - colour_with_preview.short_description = "Colour" 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 UserTopRoleFilter(admin.SimpleListFilter): """List Filter for User list Admin page.""" diff --git a/pydis_site/apps/api/models/bot/role.py b/pydis_site/apps/api/models/bot/role.py index b23fc5f4..cfadfec4 100644 --- a/pydis_site/apps/api/models/bot/role.py +++ b/pydis_site/apps/api/models/bot/role.py @@ -22,7 +22,8 @@ class Role(ModelReprMixin, models.Model): message="Role IDs cannot be negative." ), ), - help_text="The role ID, taken from Discord." + help_text="The role ID, taken from Discord.", + verbose_name="ID" ) name = models.CharField( max_length=100, -- cgit v1.2.3 From 8ed8b9ad62fd83e93d8dd5a136b7433f7240c4c2 Mon Sep 17 00:00:00 2001 From: scragly <29337040+scragly@users.noreply.github.com> Date: Fri, 18 Sep 2020 13:01:49 +1000 Subject: Add OffensiveMessage Admin model. --- pydis_site/apps/api/admin.py | 25 +++++++++++++++++++++- .../apps/api/models/bot/offensive_message.py | 9 +++++--- 2 files changed, 30 insertions(+), 4 deletions(-) (limited to 'pydis_site/apps/api/models/bot') diff --git a/pydis_site/apps/api/admin.py b/pydis_site/apps/api/admin.py index d32b4911..59cab78d 100644 --- a/pydis_site/apps/api/admin.py +++ b/pydis_site/apps/api/admin.py @@ -218,6 +218,30 @@ class OffTopicChannelNameAdmin(admin.ModelAdmin): search_fields = ("name",) +@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.""" @@ -326,4 +350,3 @@ class UserAdmin(admin.ModelAdmin): admin.site.register(BotSetting) admin.site.register(DocumentationLink) -admin.site.register(OffensiveMessage) diff --git a/pydis_site/apps/api/models/bot/offensive_message.py b/pydis_site/apps/api/models/bot/offensive_message.py index 6c0e5ffb..74dab59b 100644 --- a/pydis_site/apps/api/models/bot/offensive_message.py +++ b/pydis_site/apps/api/models/bot/offensive_message.py @@ -24,7 +24,8 @@ class OffensiveMessage(ModelReprMixin, models.Model): limit_value=0, message="Message IDs cannot be negative." ), - ) + ), + verbose_name="Message ID" ) channel_id = models.BigIntegerField( help_text=( @@ -36,11 +37,13 @@ class OffensiveMessage(ModelReprMixin, models.Model): limit_value=0, message="Channel IDs cannot be negative." ), - ) + ), + verbose_name="Channel ID" ) delete_date = models.DateTimeField( help_text="The date on which the message will be auto-deleted.", - validators=(future_date_validator,) + validators=(future_date_validator,), + verbose_name="To Be Deleted" ) def __str__(self): -- cgit v1.2.3 From 5c2e750b6ff19248aaafbb0a1c12f8c7523b7273 Mon Sep 17 00:00:00 2001 From: scragly <29337040+scragly@users.noreply.github.com> Date: Fri, 18 Sep 2020 14:59:30 +1000 Subject: Update DeletedMessage and LogEntry Admin models, add verbose names for Message --- pydis_site/apps/api/admin.py | 167 ++++++++++++++++-------------- pydis_site/apps/api/models/bot/message.py | 6 +- 2 files changed, 95 insertions(+), 78 deletions(-) (limited to 'pydis_site/apps/api/models/bot') diff --git a/pydis_site/apps/api/admin.py b/pydis_site/apps/api/admin.py index a85b4cac..ca97512f 100644 --- a/pydis_site/apps/api/admin.py +++ b/pydis_site/apps/api/admin.py @@ -23,34 +23,77 @@ from .models import ( User ) +admin.site.site_header = "Python Discord | Administration" +admin.site.site_title = "Python Discord" + + +@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(LogEntry) 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_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 @@ -60,7 +103,7 @@ class LogEntryAdmin(admin.ModelAdmin): class DeletedMessageAdmin(admin.ModelAdmin): """Admin formatting for the DeletedMessage model.""" - readonly_fields = ( + fields = ( "id", "author", "channel_id", @@ -81,96 +124,68 @@ class DeletedMessageAdmin(admin.ModelAdmin): "deletion_context__actor__id" ) - @staticmethod - def embed_data(instance: DeletedMessage) -> Optional[str]: + def embed_data(self, message: DeletedMessage) -> Optional[str]: """Format embed data in a code block for better readability.""" - if instance.embeds: + if message.embeds: return format_html( "
"
                 "{0}
", - json.dumps(instance.embeds, indent=4) + json.dumps(message.embeds, indent=4) ) + embed_data.short_description = "Embeds" + @staticmethod - def context(instance: DeletedMessage) -> str: + 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=[instance.deletion_context.id] + args=[message.deletion_context.id] ) details = ( - f"Deleted by {instance.deletion_context.actor} at " - f"{instance.deletion_context.creation}" + f"Deleted by {message.deletion_context.actor} at " + f"{message.deletion_context.creation}" ) return format_html("{1}", link, details) @staticmethod - def view_full_log(instance: DeletedMessage) -> str: + 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", - instance.deletion_context.log_url + 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(MessageDeletionContext) class MessageDeletionContextAdmin(admin.ModelAdmin): """Admin formatting for the MessageDeletionContext model.""" - readonly_fields = ("actor", "creation", "message_log") + fields = ("actor", "creation") + list_display = ("id", "creation", "actor") + inlines = (DeletedMessageInline,) - @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.""" + def has_add_permission(self, *args) -> bool: + """Prevent adding from django admin.""" + return False - 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" - ) + def has_change_permission(self, *args) -> bool: + """Prevent editing from django admin.""" + return False class NominationActorFilter(admin.SimpleListFilter): @@ -179,7 +194,7 @@ class NominationActorFilter(admin.SimpleListFilter): title = "Actor" parameter_name = "actor" - def lookups(self, request: HttpRequest, model_admin: NominationAdmin) -> Iterable[Tuple[int, str]]: + 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) @@ -322,7 +337,7 @@ class UserTopRoleFilter(admin.SimpleListFilter): title = "Role" parameter_name = "role" - def lookups(self, request: HttpRequest, model_admin: UserAdmin) -> Iterable[Tuple[str, str]]: + 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) diff --git a/pydis_site/apps/api/models/bot/message.py b/pydis_site/apps/api/models/bot/message.py index f6ae55a5..ff06de21 100644 --- a/pydis_site/apps/api/models/bot/message.py +++ b/pydis_site/apps/api/models/bot/message.py @@ -21,7 +21,8 @@ class Message(ModelReprMixin, models.Model): limit_value=0, message="Message IDs cannot be negative." ), - ) + ), + verbose_name="ID" ) author = models.ForeignKey( User, @@ -38,7 +39,8 @@ class Message(ModelReprMixin, models.Model): limit_value=0, message="Channel IDs cannot be negative." ), - ) + ), + verbose_name="Channel ID" ) content = models.CharField( max_length=2_000, -- cgit v1.2.3 From 715dd46aa68358cdd2abee07b018ee08e54da8d9 Mon Sep 17 00:00:00 2001 From: scragly <29337040+scragly@users.noreply.github.com> Date: Sun, 20 Sep 2020 05:02:48 +1000 Subject: Allow Nomination end_reason to have a blank value for validation. Without `blank=True`, admin page editable forms could not be saved if no content was in the end_reason input. --- pydis_site/apps/api/models/bot/nomination.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'pydis_site/apps/api/models/bot') diff --git a/pydis_site/apps/api/models/bot/nomination.py b/pydis_site/apps/api/models/bot/nomination.py index 54f56c98..11b9e36e 100644 --- a/pydis_site/apps/api/models/bot/nomination.py +++ b/pydis_site/apps/api/models/bot/nomination.py @@ -34,7 +34,8 @@ class Nomination(ModelReprMixin, models.Model): ) end_reason = models.TextField( help_text="Why the nomination was ended.", - default="" + default="", + blank=True ) ended_at = models.DateTimeField( auto_now_add=False, -- cgit v1.2.3 From 9344d8ac63df05d67a79fbeacc7a8c31acf86853 Mon Sep 17 00:00:00 2001 From: scragly <29337040+scragly@users.noreply.github.com> Date: Sun, 20 Sep 2020 06:35:38 +1000 Subject: Change documentation link model to order by package. --- pydis_site/apps/api/models/bot/documentation_link.py | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'pydis_site/apps/api/models/bot') diff --git a/pydis_site/apps/api/models/bot/documentation_link.py b/pydis_site/apps/api/models/bot/documentation_link.py index 5a46460b..2a0ce751 100644 --- a/pydis_site/apps/api/models/bot/documentation_link.py +++ b/pydis_site/apps/api/models/bot/documentation_link.py @@ -24,3 +24,8 @@ class DocumentationLink(ModelReprMixin, models.Model): def __str__(self): """Returns the package and URL for the current documentation link, for display purposes.""" return f"{self.package} - {self.base_url}" + + class Meta: + """Defines the meta options for the documentation link model.""" + + ordering = ['package'] -- cgit v1.2.3