diff --git a/polylan_submitter/games/admin.py b/polylan_submitter/games/admin.py index 2dfca54..bb906d6 100644 --- a/polylan_submitter/games/admin.py +++ b/polylan_submitter/games/admin.py @@ -1,4 +1,9 @@ from django.contrib import admin, messages +from django_object_actions import ( + DjangoObjectActions, + action, + takes_instance_or_queryset, +) from submissions.tasks import sync_event_game_scopes_task @@ -19,17 +24,22 @@ class GameAdmin(admin.ModelAdmin): @admin.register(EventGame) -class EventGameAdmin(admin.ModelAdmin): +class EventGameAdmin(DjangoObjectActions, admin.ModelAdmin): list_display = ["game", "event", "enabled", "has_apibot_token"] list_filter = ["event", "enabled"] search_fields = ["game__name", "event__name"] actions = ["sync_scopes"] + change_actions = ["sync_scopes"] @admin.display(description="AAndT token", boolean=True) def has_apibot_token(self, obj): return bool(obj.apibot_token) - @admin.action(description="Sync scopes from AAndT for selected EventGames") + @action( + label="Sync scopes", + description="Sync scopes from AAndT for selected EventGames", + ) + @takes_instance_or_queryset def sync_scopes(self, request, queryset): tokened = queryset.exclude(apibot_token__isnull=True).exclude(apibot_token="") skipped = queryset.count() - tokened.count() diff --git a/polylan_submitter/market/admin.py b/polylan_submitter/market/admin.py index b67fca8..ce4c97c 100644 --- a/polylan_submitter/market/admin.py +++ b/polylan_submitter/market/admin.py @@ -1,4 +1,9 @@ from django.contrib import admin +from django_object_actions import ( + DjangoObjectActions, + action, + takes_instance_or_queryset, +) from market.models import Market, MarketOption, UserBet, UserPointChange @@ -9,7 +14,7 @@ class MarketOptionInline(admin.TabularInline): @admin.register(Market) -class MarketAdmin(admin.ModelAdmin): +class MarketAdmin(DjangoObjectActions, admin.ModelAdmin): list_display = ["title", "status", "end_date", "created_by", "created_at"] list_filter = ["status", "created_at"] search_fields = ["uuid", "title"] @@ -39,22 +44,25 @@ class MarketAdmin(admin.ModelAdmin): obj.created_by = request.user super().save_model(request, obj, form, change) - @admin.action(description="Publish selected draft markets") + actions = ["publish_markets", "close_markets"] + change_actions = ["publish_markets", "close_markets"] + + @action(label="Publish", description="Publish selected draft markets") + @takes_instance_or_queryset def publish_markets(self, request, queryset): updated = queryset.filter(status=Market.Status.DRAFT).update( status=Market.Status.OPEN ) self.message_user(request, f"Published {updated} market(s).") - @admin.action(description="Close selected markets") + @action(label="Close", description="Close selected markets") + @takes_instance_or_queryset def close_markets(self, request, queryset): updated = queryset.filter(status=Market.Status.OPEN).update( status=Market.Status.CLOSED ) self.message_user(request, f"Closed {updated} market(s).") - actions = ["publish_markets", "close_markets"] - @admin.register(MarketOption) class MarketOptionAdmin(admin.ModelAdmin): diff --git a/polylan_submitter/noita/admin.py b/polylan_submitter/noita/admin.py index 7c759ec..21c06bc 100644 --- a/polylan_submitter/noita/admin.py +++ b/polylan_submitter/noita/admin.py @@ -1,11 +1,16 @@ from django.contrib import admin +from django_object_actions import ( + DjangoObjectActions, + action, + takes_instance_or_queryset, +) from noita.services.objectives import parse_objectives_and_store from .models import LogfileSubmission, Objectiv, ObjectivPoint, DeathCounter @admin.register(LogfileSubmission) -class LogfileSubmissionAdmin(admin.ModelAdmin): +class LogfileSubmissionAdmin(DjangoObjectActions, admin.ModelAdmin): list_display = ( "id", "user", @@ -26,7 +31,10 @@ class LogfileSubmissionAdmin(admin.ModelAdmin): ) actions = ["validate_submission"] + change_actions = ["validate_submission"] + @action(label="Validate", description="Validate selected submissions") + @takes_instance_or_queryset def validate_submission(self, request, queryset): for logfile in queryset: parse_objectives_and_store(logfile) diff --git a/polylan_submitter/opus_magnum/admin.py b/polylan_submitter/opus_magnum/admin.py index f547ab5..bdcf2fc 100644 --- a/polylan_submitter/opus_magnum/admin.py +++ b/polylan_submitter/opus_magnum/admin.py @@ -1,6 +1,11 @@ from django.contrib import admin from django.utils.html import format_html from django.utils import timezone +from django_object_actions import ( + DjangoObjectActions, + action, + takes_instance_or_queryset, +) from opus_magnum.models import ( SteamAPIKey, SteamCollection, @@ -185,7 +190,7 @@ class PuzzleResponseInline(admin.TabularInline): @admin.register(Submission) -class SubmissionAdmin(admin.ModelAdmin): +class SubmissionAdmin(DjangoObjectActions, admin.ModelAdmin): list_display = [ "id", "user", @@ -238,9 +243,11 @@ class SubmissionAdmin(admin.ModelAdmin): ) actions = ["mark_as_validated"] + change_actions = ["mark_as_validated"] + @action(label="Validate", description="Mark selected submissions as validated") + @takes_instance_or_queryset def mark_as_validated(self, request, queryset): - """Mark selected submissions as validated""" updated = 0 for submission in queryset: if not submission.is_validated: @@ -254,11 +261,9 @@ class SubmissionAdmin(admin.ModelAdmin): self.message_user(request, f"{updated} submissions marked as validated.") - mark_as_validated.short_description = "Mark selected submissions as validated" - @admin.register(PuzzleResponse) -class PuzzleResponseAdmin(admin.ModelAdmin): +class PuzzleResponseAdmin(DjangoObjectActions, admin.ModelAdmin): list_display = [ "puzzle_name", "submission", @@ -314,22 +319,22 @@ class PuzzleResponseAdmin(admin.ModelAdmin): ) actions = ["mark_for_validation", "clear_validation_flag"] + change_actions = ["mark_for_validation", "clear_validation_flag"] + @action(label="Mark for validation", description="Mark as needing validation") + @takes_instance_or_queryset def mark_for_validation(self, request, queryset): - """Mark selected responses as needing validation""" updated = queryset.update(needs_manual_validation=True) self.message_user(request, f"{updated} responses marked for validation.") + @action(label="Clear validation flag", description="Clear validation flag") + @takes_instance_or_queryset def clear_validation_flag(self, request, queryset): - """Clear validation flag for selected responses""" updated = queryset.update(needs_manual_validation=False) self.message_user( request, f"{updated} responses cleared from validation queue." ) - mark_for_validation.short_description = "Mark as needing validation" - clear_validation_flag.short_description = "Clear validation flag" - @admin.register(SubmissionFile) class SubmissionFileAdmin(admin.ModelAdmin): diff --git a/polylan_submitter/polylan_submitter/settings.py b/polylan_submitter/polylan_submitter/settings.py index 8861d84..465b2a1 100644 --- a/polylan_submitter/polylan_submitter/settings.py +++ b/polylan_submitter/polylan_submitter/settings.py @@ -45,6 +45,7 @@ INSTALLED_APPS = [ "django_vite", "django_rq", "django_tasks_rq", + "django_object_actions", "accounts", "events", "animations", diff --git a/pyproject.toml b/pyproject.toml index ab9d1d5..543e060 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,6 +25,7 @@ dependencies = [ "django-rq", "django-tasks-rq", "httpx>=0.28.1", + "django-object-actions>=5.1.2", ] [dependency-groups] diff --git a/uv.lock b/uv.lock index 7ff0d35..ebe1926 100644 --- a/uv.lock +++ b/uv.lock @@ -677,6 +677,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/64/96/d967ca440d6a8e3861120f51985d8e5aec79b9a8bdda16041206adfe7adc/django_extensions-4.1-py3-none-any.whl", hash = "sha256:0699a7af28f2523bf8db309a80278519362cd4b6e1fd0a8cd4bf063e1e023336", size = 232980, upload-time = "2025-04-11T01:15:37.701Z" }, ] +[[package]] +name = "django-object-actions" +version = "5.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c4/2f/99e8fa3d117becebfbe46d54d0b85d81475b6375b85507907a7aaf1d5d2b/django_object_actions-5.1.2.tar.gz", hash = "sha256:a11ac544e5beb6b309a50e184a2086e1e18f6469704d9609bbe083a8972a2245", size = 19048, upload-time = "2026-05-28T05:48:29.88Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/37/23/c5743780fc68433f244fba86cd64b513b9d6a9491714ac700a4654b5e00f/django_object_actions-5.1.2-py3-none-any.whl", hash = "sha256:366dcd5ccfb3ef6a16ce4c147be8df2a35b52d637eb36fb9c558e28419ae4038", size = 16900, upload-time = "2026-05-28T05:48:28.56Z" }, +] + [[package]] name = "django-redis" version = "6.0.0" @@ -1626,6 +1635,7 @@ dependencies = [ { name = "django", version = "5.2.14", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.12'" }, { name = "django", version = "6.0.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.12'" }, { name = "django-channels" }, + { name = "django-object-actions" }, { name = "django-redis" }, { name = "django-rq" }, { name = "django-shinobi" }, @@ -1666,6 +1676,7 @@ requires-dist = [ { name = "daphne" }, { name = "django" }, { name = "django-channels" }, + { name = "django-object-actions", specifier = ">=5.1.2" }, { name = "django-redis" }, { name = "django-rq" }, { name = "django-shinobi" },