From 55b76124adc0961936a357814ca526622308e87a Mon Sep 17 00:00:00 2001 From: ssorin Date: Fri, 20 Dec 2024 17:32:29 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8(dashboard)=20add=20admin=20actions=20?= =?UTF-8?q?for=20updating=20consent=20statuses?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduce `make_revoked` and `make_awaiting` actions to simplify updating consent status directly from the admin interface. Updates include relevant timestamps and user feedback via success messages. --- src/dashboard/CHANGELOG.md | 2 + src/dashboard/apps/consent/admin.py | 27 ++++++++- src/dashboard/apps/consent/tests/conftest.py | 32 ++++++++++ .../apps/consent/tests/test_admin.py | 60 +++++++++++++++++++ 4 files changed, 120 insertions(+), 1 deletion(-) diff --git a/src/dashboard/CHANGELOG.md b/src/dashboard/CHANGELOG.md index c69c51bf..6b070c3c 100644 --- a/src/dashboard/CHANGELOG.md +++ b/src/dashboard/CHANGELOG.md @@ -18,6 +18,8 @@ and this project adheres to - add internationalization and language switcher - add dashboard homepage - add consent form to manage consents of one or many entities +- add admin integration for Entity, DeliveryPoint and Consent +- add mass admin action (make revoked and make awaiting) for consents - integration of custom 403, 404 and 500 pages - sentry integration - added a signal on the creation of a delivery point. This signal allows the creation diff --git a/src/dashboard/apps/consent/admin.py b/src/dashboard/apps/consent/admin.py index 15812fc5..f1a42686 100644 --- a/src/dashboard/apps/consent/admin.py +++ b/src/dashboard/apps/consent/admin.py @@ -1,7 +1,10 @@ """Dashboard consent admin.""" -from django.contrib import admin +from django.contrib import admin, messages +from django.utils import timezone +from django.utils.translation import gettext_lazy as _ +from . import AWAITING, REVOKED from .models import Consent @@ -23,3 +26,25 @@ class ConsentAdmin(admin.ModelAdmin): ] list_filter = ["status"] date_hierarchy = "start" + actions = ["make_revoked", "make_awaiting"] + + @admin.action(description=_("Mark selected consents as revoked")) + def make_revoked(self, request, queryset): + """Mark selected consents as revoked.""" + now = timezone.now() + queryset.update(status=REVOKED, revoked_at=now, updated_at=now) + self.message_user( + request, + _("Selected consents have been marked as revoked."), + messages.SUCCESS, + ) + + @admin.action(description=_("Mark selected consents as awaiting")) + def make_awaiting(self, request, queryset): + """Mark selected consents as awaiting.""" + queryset.update(status=AWAITING, updated_at=timezone.now(), revoked_at=None) + self.message_user( + request, + _("Selected consents have been marked as awaiting."), + messages.SUCCESS, + ) diff --git a/src/dashboard/apps/consent/tests/conftest.py b/src/dashboard/apps/consent/tests/conftest.py index 91ebab3e..cde687b0 100644 --- a/src/dashboard/apps/consent/tests/conftest.py +++ b/src/dashboard/apps/consent/tests/conftest.py @@ -3,6 +3,10 @@ import datetime import pytest +from django.contrib.messages.middleware import MessageMiddleware +from django.contrib.sessions.middleware import SessionMiddleware +from django.test import RequestFactory +from django.utils import timezone FAKE_TIME = datetime.datetime(2025, 1, 6, 17, 5, 55, 0, tzinfo=datetime.timezone.utc) @@ -17,3 +21,31 @@ def now(cls, tz=datetime.timezone.utc): return FAKE_TIME monkeypatch.setattr(datetime, "datetime", FakeDatetime) + + +@pytest.fixture +def patch_timezone_now(monkeypatch): + """Monkeypatch timezone.now to return a frozen date (`FAKE_TIME`).""" + + def mock_now(): + return FAKE_TIME + + monkeypatch.setattr(timezone, "now", mock_now) + + +@pytest.fixture +def request_with_message_middleware(): + """Fixture to create a factice request with a message middleware.""" + factory = RequestFactory() + request = factory.get("/") + + # add session middleware + session_middleware = SessionMiddleware(lambda req: None) + session_middleware.process_request(request) + request.session.save() + + # add messages middleware + message_middleware = MessageMiddleware(lambda req: None) + message_middleware.process_request(request) + + return request diff --git a/src/dashboard/apps/consent/tests/test_admin.py b/src/dashboard/apps/consent/tests/test_admin.py index 19da282b..61fc844f 100644 --- a/src/dashboard/apps/consent/tests/test_admin.py +++ b/src/dashboard/apps/consent/tests/test_admin.py @@ -4,8 +4,12 @@ from django.contrib.admin.sites import AdminSite from django.urls import reverse +from apps.auth.factories import AdminUserFactory +from apps.consent import AWAITING, REVOKED from apps.consent.admin import ConsentAdmin from apps.consent.models import Consent +from apps.consent.tests.conftest import FAKE_TIME +from apps.core.factories import DeliveryPointFactory @pytest.mark.django_db @@ -29,3 +33,59 @@ def test_admin_manager_name(rf): # The manager name must be the default Django manager: `objects`. manager_name = queryset.model._default_manager.name assert manager_name == "objects" + + +@pytest.mark.django_db +def test_make_revoked_action(client, patch_timezone_now): + """Tests the 'make_revoked' action for ConsentAdmin.""" + # Initialize admin user + admin_user = AdminUserFactory() + + # create a consent + assert Consent.objects.count() == 0 + DeliveryPointFactory() + assert Consent.objects.count() == 1 + + # Post action with selected consent + consent = Consent.objects.first() + data = { + "action": "make_revoked", + "_selected_action": [ + consent.id, + ], + } + client.force_login(admin_user) + client.post(reverse("admin:qcd_consent_consent_changelist"), data) + + consent.refresh_from_db() + assert consent.status == REVOKED + assert consent.revoked_at == FAKE_TIME + assert consent.updated_at == FAKE_TIME + + +@pytest.mark.django_db +def test_make_awaiting_action(client, patch_timezone_now): + """Tests the 'make_awaiting' action for ConsentAdmin.""" + # Initialize admin user + admin_user = AdminUserFactory() + + # create a consent + assert Consent.objects.count() == 0 + DeliveryPointFactory() + assert Consent.objects.count() == 1 + + # Post action with selected consent + consent = Consent.objects.first() + data = { + "action": "make_awaiting", + "_selected_action": [ + consent.id, + ], + } + client.force_login(admin_user) + client.post(reverse("admin:qcd_consent_consent_changelist"), data) + + consent.refresh_from_db() + assert consent.status == AWAITING + assert consent.revoked_at is None + assert consent.updated_at == FAKE_TIME