From 7917bf5e1d2afcf1d163410200dc39d04b72fb60 Mon Sep 17 00:00:00 2001 From: Samuel Cattini-Schultz Date: Mon, 10 Jun 2024 21:55:50 +1000 Subject: [PATCH 1/2] Improve admin list pages --- leaderboards/admin.py | 41 ++++++++++- leaderboards/models.py | 2 +- osuauth/admin.py | 10 ++- profiles/admin.py | 162 ++++++++++++++++++++++++++++++++++++++++- profiles/models.py | 2 +- users/admin.py | 6 ++ users/models.py | 2 +- 7 files changed, 216 insertions(+), 9 deletions(-) diff --git a/leaderboards/admin.py b/leaderboards/admin.py index 87b1c5f..49f5e4b 100644 --- a/leaderboards/admin.py +++ b/leaderboards/admin.py @@ -1,5 +1,7 @@ from django.contrib import admin +from common.osu.enums import Gamemode +from leaderboards.enums import LeaderboardAccessType from leaderboards.models import Invite, Leaderboard, Membership @@ -10,11 +12,39 @@ class LeaderboardAdmin(admin.ModelAdmin): "score_filter", ) + list_display = [ + "__str__", + "gamemode_display", + "access_type_display", + "member_count", + "archived", + "owner", + "creation_time", + ] + list_select_related = ["owner"] + list_filter = ["gamemode", "access_type", "archived"] + + @admin.display(description="Gamemode") + def gamemode_display(self, obj: Leaderboard): + return Gamemode(obj.gamemode).name + + @admin.display(description="Access Type") + def access_type_display(self, obj: Leaderboard): + return LeaderboardAccessType(obj.access_type).name + class MembershipAdmin(admin.ModelAdmin): model = Membership - filter_horizontal = ("scores",) - raw_id_fields = ("leaderboard", "user", "scores") + raw_id_fields = ("leaderboard", "user") + + list_display = [ + "id", + "leaderboard", + "user", + "pp", + "rank", + "score_count", + ] class InviteAdmin(admin.ModelAdmin): @@ -24,6 +54,13 @@ class InviteAdmin(admin.ModelAdmin): "user", ) + list_display = [ + "id", + "leaderboard", + "user", + "invite_date", + ] + admin.site.register(Leaderboard, LeaderboardAdmin) admin.site.register(Membership, MembershipAdmin) diff --git a/leaderboards/models.py b/leaderboards/models.py index c0cf24d..125226c 100644 --- a/leaderboards/models.py +++ b/leaderboards/models.py @@ -116,7 +116,7 @@ def update_member_count(self): self.save() def __str__(self): - return f"[{Gamemode(self.gamemode).name}] {self.name}" + return f"{self.name}" class Meta: indexes = [models.Index(fields=["gamemode"])] diff --git a/osuauth/admin.py b/osuauth/admin.py index 2236335..7c5e34f 100644 --- a/osuauth/admin.py +++ b/osuauth/admin.py @@ -9,7 +9,15 @@ class UserAdmin(BaseUserAdmin): Custom user admin """ - list_display = ("username", "osu_user", "is_staff") + list_display = ( + "username", + "osu_user", + "is_staff", + "last_login", + "date_joined", + "last_active", + ) + list_select_related = ("osu_user",) fieldsets = ( (None, {"fields": ("username", "password")}), ( diff --git a/profiles/admin.py b/profiles/admin.py index 408be57..0279bbc 100644 --- a/profiles/admin.py +++ b/profiles/admin.py @@ -1,5 +1,8 @@ from django.contrib import admin +from common.osu import utils +from common.osu.enums import BeatmapStatus, Gamemode, Mods +from profiles.enums import ScoreMutation, ScoreResult from profiles.models import ( Beatmap, DifficultyCalculation, @@ -13,25 +16,115 @@ ) +class OsuUserAdmin(admin.ModelAdmin): + model = OsuUser + + list_display = [ + "__str__", + "id", + "country", + "join_date", + ] + + class UserStatsAdmin(admin.ModelAdmin): model = UserStats raw_id_fields = ("user",) + list_display = [ + "id", + "gamemode_display", + "user", + "rank", + "pp", + ] + list_filter = ["gamemode"] + + @admin.display(description="Gamemode") + def gamemode_display(self, obj: UserStats): + return Gamemode(obj.gamemode).name + class BeatmapAdmin(admin.ModelAdmin): model = Beatmap raw_id_fields = ("creator",) + list_display = [ + "__str__", + "gamemode_display", + "status_display", + "submission_date", + "last_updated", + "approval_date", + ] + list_filter = ["gamemode", "status"] + + @admin.display(description="Gamemode") + def gamemode_display(self, obj: Beatmap): + return Gamemode(obj.gamemode).name + + @admin.display(description="Status") + def status_display(self, obj: Beatmap): + return BeatmapStatus(obj.status).name + class DifficultyCalculationAdmin(admin.ModelAdmin): model = DifficultyCalculation raw_id_fields = ("beatmap",) + list_display = [ + "id", + "beatmap", + "mods_display", + "calculator_engine", + "calculator_version", + ] + list_filter = ["calculator_engine"] + # NOTE: for some reason it's automatically joining on beatmap__creator unless we explicitly list_select_related + list_select_related = ["beatmap"] + + @admin.display(description="Mods") + def mods_display(self, obj: DifficultyCalculation): + if obj.mods == Mods.NONE: + return None + return utils.get_mods_string(obj.mods) + class DifficultyValueAdmin(admin.ModelAdmin): - model = DifficultyCalculation + model = DifficultyValue raw_id_fields = ("calculation",) + list_display = [ + "id", + "beatmap_display", + "mods_display", + "calculator_engine_display", + "calculator_version_display", + "name", + "value", + ] + list_filter = ["calculation__calculator_engine", "name"] + # NOTE: for some reason it's automatically joining on beatmap__creator unless we explicitly list_select_related + list_select_related = ["calculation__beatmap"] + + @admin.display(description="Mods") + def mods_display(self, obj: DifficultyValue): + if obj.calculation.mods == Mods.NONE: + return None + return utils.get_mods_string(obj.calculation.mods) + + @admin.display(description="Beatmap") + def beatmap_display(self, obj: DifficultyValue): + return obj.calculation.beatmap + + @admin.display(description="Calculator Engine") + def calculator_engine_display(self, obj: DifficultyValue): + return obj.calculation.calculator_engine + + @admin.display(description="Calculator Version") + def calculator_version_display(self, obj: DifficultyValue): + return obj.calculation.calculator_version + class ScoreAdmin(admin.ModelAdmin): model = Score @@ -40,6 +133,39 @@ class ScoreAdmin(admin.ModelAdmin): "user_stats", ) + list_display = [ + "__str__", + "user", + "mods_display", + "accuracy_display", + "beatmap", + "result_display", + "mutation_display", + ] + list_select_related = ["user_stats__user", "beatmap"] + + @admin.display(description="User") + def user(self, obj: Score): + return obj.user_stats.user + + @admin.display(description="Mods") + def mods_display(self, obj: Score): + if obj.mods == Mods.NONE: + return None + return utils.get_mods_string(obj.mods) + + @admin.display(description="Accuracy") + def accuracy_display(self, obj: Score): + return f"{obj.accuracy:.2f}%" + + @admin.display(description="Result") + def result_display(self, obj: Score): + return ScoreResult(obj.result).name + + @admin.display(description="Mutation") + def mutation_display(self, obj: Score): + return ScoreMutation(obj.mutation).name + class PerformanceCalculationAdmin(admin.ModelAdmin): model = PerformanceCalculation @@ -48,13 +174,43 @@ class PerformanceCalculationAdmin(admin.ModelAdmin): "difficulty_calculation", ) + # no joins because it gets really slow, especially on high page numbers + list_display = [ + "id", + "score_id", + "calculator_engine", + "calculator_version", + ] + class PerformanceValueAdmin(admin.ModelAdmin): - model = PerformanceCalculation + model = PerformanceValue raw_id_fields = ("calculation",) + list_display = [ + "id", + "score_id_display", + "calculator_engine_display", + "calculator_version_display", + "name", + "value", + ] + list_select_related = ["calculation"] + + @admin.display(description="Score ID") + def score_id_display(self, obj: PerformanceValue): + return obj.calculation.score_id + + @admin.display(description="Calculator Engine") + def calculator_engine_display(self, obj: PerformanceValue): + return obj.calculation.calculator_engine + + @admin.display(description="Calculator Version") + def calculator_version_display(self, obj: PerformanceValue): + return obj.calculation.calculator_version + -admin.site.register(OsuUser) +admin.site.register(OsuUser, OsuUserAdmin) admin.site.register(UserStats, UserStatsAdmin) admin.site.register(Beatmap, BeatmapAdmin) admin.site.register(DifficultyCalculation, DifficultyCalculationAdmin) diff --git a/profiles/models.py b/profiles/models.py index 10ccadc..53fa026 100644 --- a/profiles/models.py +++ b/profiles/models.py @@ -685,7 +685,7 @@ def update_performance_values( error_reporter.report_error(e) def __str__(self): - return f"{self.beatmap_id}: {self.performance_total:.0f}pp" + return f"{Gamemode(self.gamemode).name} {self.id}" class Meta: constraints = [ diff --git a/users/admin.py b/users/admin.py index 6700bdb..267e5fc 100644 --- a/users/admin.py +++ b/users/admin.py @@ -10,5 +10,11 @@ class ScoreFilterPresetAdmin(admin.ModelAdmin): "score_filter", ) + list_display = [ + "__str__", + "user", + ] + list_select_related = ["user__osu_user"] + admin.site.register(ScoreFilterPreset, ScoreFilterPresetAdmin) diff --git a/users/models.py b/users/models.py index bc93cab..e0d56f0 100644 --- a/users/models.py +++ b/users/models.py @@ -13,4 +13,4 @@ class ScoreFilterPreset(models.Model): score_filter = models.ForeignKey(ScoreFilter, on_delete=models.CASCADE) def __str__(self): - return f"{self.user_id}: {self.name}" + return self.name From c289f0ecb385caffbab0a0b105965171e7580038 Mon Sep 17 00:00:00 2001 From: Samuel Cattini-Schultz Date: Mon, 10 Jun 2024 22:00:46 +1000 Subject: [PATCH 2/2] fixup! Improve admin list pages --- leaderboards/test_models.py | 7 +++---- profiles/test_models.py | 3 --- users/test_models.py | 2 +- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/leaderboards/test_models.py b/leaderboards/test_models.py index 2b5b9af..a7a4cb5 100644 --- a/leaderboards/test_models.py +++ b/leaderboards/test_models.py @@ -1,13 +1,12 @@ import pytest from leaderboards.models import Invite, Leaderboard, Membership -from profiles.models import OsuUser @pytest.mark.django_db class TestLeaderboard: def test_magic_str(self, leaderboard: Leaderboard): - assert str(leaderboard) == "[STANDARD] test leaderboard" + assert str(leaderboard) == "test leaderboard" def test_get_pp_record(self): # TODO: this @@ -29,7 +28,7 @@ def test_update_member_count(self): @pytest.mark.django_db class TestMembership: def test_magic_str(self, membership: Membership): - assert str(membership) == "[STANDARD] test leaderboard: TestOsuUser" + assert str(membership) == "test leaderboard: TestOsuUser" def test_recalculate(self): # TODO: this @@ -39,4 +38,4 @@ def test_recalculate(self): @pytest.mark.django_db class TestInvite: def test_magic_str(self, invite: Invite): - assert str(invite) == "[STANDARD] test leaderboard: TestOsuUser" + assert str(invite) == "test leaderboard: TestOsuUser" diff --git a/profiles/test_models.py b/profiles/test_models.py index f0bca49..dce5514 100644 --- a/profiles/test_models.py +++ b/profiles/test_models.py @@ -46,9 +46,6 @@ def test_update_difficulty_values(self, beatmap: Beatmap): @pytest.mark.django_db class TestScore: - def test_magic_str(self, score: Score): - assert str(score) == "1: 395pp" - def test_process(self, score: Score): score.process() assert score.result == ScoreResult.END_CHOKE diff --git a/users/test_models.py b/users/test_models.py index d62a37e..cb2a75e 100644 --- a/users/test_models.py +++ b/users/test_models.py @@ -14,4 +14,4 @@ def score_filter_preset(self, user: User, score_filter: ScoreFilter): ) def test_magic_str(self, score_filter_preset): - assert str(score_filter_preset) == f"{score_filter_preset.user_id}: Hidden" + assert str(score_filter_preset) == f"Hidden"