From 0e83afc0c866b4a38391c35decb8e00fee17dffd Mon Sep 17 00:00:00 2001 From: ssorin Date: Thu, 13 Feb 2025 15:39:31 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8(dashboard)=20add=20validated=20consen?= =?UTF-8?q?t=20view=20and=20reuse=20no-data=20card=20template?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implemented a view to display validated consents with appropriate permissions. Modularized the no-data card template to reduce duplication across consent summary templates. Added tests to ensure proper behavior of the validated consent view and permissions. --- src/dashboard/CHANGELOG.md | 2 + .../includes/_consent_summary_card.html | 24 +----- .../includes/_consent_summary_validated.html | 2 +- .../consent/includes/_no_data_card.html | 11 +++ .../consent/templates/consent/validated.html | 64 +++++++++++++++ .../apps/consent/tests/test_views.py | 78 ++++++++++++++++++- src/dashboard/apps/consent/urls.py | 8 +- src/dashboard/apps/consent/views.py | 35 ++++++++- src/dashboard/apps/core/models.py | 4 + 9 files changed, 201 insertions(+), 27 deletions(-) create mode 100644 src/dashboard/apps/consent/templates/consent/includes/_no_data_card.html create mode 100644 src/dashboard/apps/consent/templates/consent/validated.html diff --git a/src/dashboard/CHANGELOG.md b/src/dashboard/CHANGELOG.md index 7b1b0b90..babef6e2 100644 --- a/src/dashboard/CHANGELOG.md +++ b/src/dashboard/CHANGELOG.md @@ -17,6 +17,8 @@ and this project adheres to - add ProConnect authentication system - add dashboard homepage - add consent form to manage consents of one or many entities +- added a validated consent page allowing consultation of validated consent for the + current period. - add an email notification to users (via Brevo) after they have validated their consents. - add admin integration for Entity, DeliveryPoint and Consent - add mass admin action (make revoked) for consents diff --git a/src/dashboard/apps/consent/templates/consent/includes/_consent_summary_card.html b/src/dashboard/apps/consent/templates/consent/includes/_consent_summary_card.html index 5a0acb24..030e1f31 100644 --- a/src/dashboard/apps/consent/templates/consent/includes/_consent_summary_card.html +++ b/src/dashboard/apps/consent/templates/consent/includes/_consent_summary_card.html @@ -16,32 +16,12 @@

{# awaiting consent - no items #} {% if resume_awaiting and not has_awaiting_consent %} -
-

- Aucune autorisation en attente. -
- Si vous pensez qu'il s'agit d'une erreur, merci de contacter l'équipe QualiCharge. - - {{ contact_email }} - -

-
+ {% include "consent/includes/_no_data_card.html" with description="Aucune autorisation en attente." %} {% endif %} {# validated consent - no items #} {% if resume_validated and not has_validated_consent %} -
-

- Aucune station suivie. -
- Si vous pensez qu'il s'agit d'une erreur, merci de contacter l'équipe QualiCharge. - - {{ contact_email }} - -

-
+ {% include "consent/includes/_no_data_card.html" with description="Aucune station suivie." %} {% endif %} diff --git a/src/dashboard/apps/consent/templates/consent/includes/_consent_summary_validated.html b/src/dashboard/apps/consent/templates/consent/includes/_consent_summary_validated.html index abcabda4..9bc2c973 100644 --- a/src/dashboard/apps/consent/templates/consent/includes/_consent_summary_validated.html +++ b/src/dashboard/apps/consent/templates/consent/includes/_consent_summary_validated.html @@ -44,7 +44,7 @@ Consulter la liste diff --git a/src/dashboard/apps/consent/templates/consent/includes/_no_data_card.html b/src/dashboard/apps/consent/templates/consent/includes/_no_data_card.html new file mode 100644 index 00000000..5174bb6b --- /dev/null +++ b/src/dashboard/apps/consent/templates/consent/includes/_no_data_card.html @@ -0,0 +1,11 @@ +
+

+ {{ description }} +
+ Si vous pensez qu'il s'agit d'une erreur, merci de contacter l'équipe QualiCharge. + + {{ contact_email }} + +

+
diff --git a/src/dashboard/apps/consent/templates/consent/validated.html b/src/dashboard/apps/consent/templates/consent/validated.html new file mode 100644 index 00000000..c1761772 --- /dev/null +++ b/src/dashboard/apps/consent/templates/consent/validated.html @@ -0,0 +1,64 @@ +{% extends "consent/base.html" %} + +{% load dsfr_tags %} + +{% load i18n static %} + +{% block dashboard_content %} +

Stations suivies

+ +

+ Retrouver ici la liste des stations pour lesquelles la DGEC consulte le gestionnaire + de réseau et émet des certificats. +

+ + {% if consents %} +
+
+
+
+ + + + + + + + + + + + + + {% for consent in consents %} + + + {# todo : get station_name, station_id, prm_id #} + + + + + {% endfor %} + +
Points de livraison suivis
+
Nom de la station
+
+
Identifiant de la station
+
+
Identifiant du PDL
+
-- station name -- -- station id -- {{ consent.delivery_point.provider_assigned_id }}
+ +
+
+
+
+ + {% dsfr_pagination page_obj %} + + {% else %} + {% include "consent/includes/_no_data_card.html" with description="Aucune station suivie." %} + {% endif %} + +{% endblock dashboard_content %} diff --git a/src/dashboard/apps/consent/tests/test_views.py b/src/dashboard/apps/consent/tests/test_views.py index b7ed036e..f5576f65 100644 --- a/src/dashboard/apps/consent/tests/test_views.py +++ b/src/dashboard/apps/consent/tests/test_views.py @@ -7,13 +7,14 @@ import pytest from django.conf import settings +from django.core.exceptions import PermissionDenied from django.urls import reverse from apps.auth.factories import UserFactory from apps.consent import AWAITING, REVOKED, VALIDATED from apps.consent.factories import ConsentFactory from apps.consent.models import Consent -from apps.consent.views import ConsentFormView +from apps.consent.views import ConsentFormView, ValidatedConsentView from apps.core.factories import DeliveryPointFactory, EntityFactory FORM_CLEANED_DATA = { @@ -385,7 +386,7 @@ def test_form_post_empty(rf): @pytest.mark.django_db -def test_manage_url_redirect(client): +def test_manage_url_without_slug_is_redirected(client): """Test direct access to manage page is redirected to consent index page.""" # create and connect user user = UserFactory() @@ -425,3 +426,76 @@ def test_send_email_notification_populated(rf): }, ) email_send_mock.assert_called_once() + + +@pytest.mark.django_db +def test_get_validated_consents_raises_permission_denied(rf): + """Test PermissionDenied is raised when the user does not have permission.""" + user = UserFactory() + + # create a consent without the declared user + entity = EntityFactory() + DeliveryPointFactory(entity=entity) + + request = rf.get(reverse("consent:validated", kwargs={"slug": entity.slug})) + request.user = user + + view = ValidatedConsentView() + view.request = request + view.kwargs = {"slug": entity.slug} + + # the user has no perms to this consent + with pytest.raises(PermissionDenied): + view.get_queryset() + + +@pytest.mark.django_db +def test_get_validated_consents_return_queryset(rf): + """Test _get_validated_consents returns the correct QuerySet.""" + user = UserFactory() + + # create and get 2 validated consents for the user + entity = EntityFactory(users=(user,)) + dl1 = DeliveryPointFactory(entity=entity) + dl2 = DeliveryPointFactory(entity=entity) + consent1 = Consent.objects.get(delivery_point=dl1) + consent2 = Consent.objects.get(delivery_point=dl2) + for consent in Consent.objects.all(): + consent.status = VALIDATED + consent.save() + + # and and get an awaiting consent + dl3 = DeliveryPointFactory(entity=entity) + consent3 = Consent.objects.get(delivery_point=dl3) + + # check the number of validated consents + expected_validated_consent = 2 + assert ( + Consent.objects.filter(status=VALIDATED).count() == expected_validated_consent + ) + + request = rf.get(reverse("consent:validated", kwargs={"slug": entity.slug})) + request.user = user + view = ValidatedConsentView() + view.request = request + view.kwargs = {"slug": entity.slug} + + # we expected to retrieve only the 2 VALIDATED consents in the result + result = view.get_queryset() + assert len(result) == expected_validated_consent + assert consent1 in result + assert consent2 in result + assert consent3 not in result + + +@pytest.mark.django_db +def test_validated_url_without_slug_is_redirected(client): + """Test direct access to validated url is redirected to consent index page.""" + # create and connect user + user = UserFactory() + client.force_login(user) + + # Get response object + response = client.get(reverse("consent:validated")) + assert response.status_code == HTTPStatus.FOUND + assert response.url == reverse("consent:index") diff --git a/src/dashboard/apps/consent/urls.py b/src/dashboard/apps/consent/urls.py index 5eb64cce..25465bb2 100644 --- a/src/dashboard/apps/consent/urls.py +++ b/src/dashboard/apps/consent/urls.py @@ -3,7 +3,7 @@ from django.urls import path from django.views.generic.base import RedirectView -from .views import ConsentFormView, IndexView +from .views import ConsentFormView, IndexView, ValidatedConsentView app_name = "consent" @@ -17,4 +17,10 @@ name="manage", ), path("manage/", ConsentFormView.as_view(), name="manage"), + path( + "validated/", + RedirectView.as_view(pattern_name="consent:index", permanent=False), + name="validated", + ), + path("validated/", ValidatedConsentView.as_view(), name="validated"), ] diff --git a/src/dashboard/apps/consent/views.py b/src/dashboard/apps/consent/views.py index 22a58724..13fc2947 100644 --- a/src/dashboard/apps/consent/views.py +++ b/src/dashboard/apps/consent/views.py @@ -15,7 +15,7 @@ from django.urls import reverse_lazy as reverse from django.utils import timezone from django.utils.translation import gettext_lazy as _ -from django.views.generic import FormView, TemplateView +from django.views.generic import FormView, ListView, TemplateView from apps.core.models import Entity @@ -270,3 +270,36 @@ def _send_email(self) -> None: # fail silently and send a sentry log sentry_sdk.capture_exception(e) return + + +class ValidatedConsentView(BreadcrumbContextMixin, ListView): + """Index view of the consent app.""" + + context_object_name = "consents" + template_name = "consent/validated.html" + paginate_by = 50 + + breadcrumb_links = [ + {"url": reverse("consent:index"), "title": _("Consent")}, + ] + breadcrumb_current = _("Manage Consents") + + def get_queryset(self): + """Filter queryset to only return validated consents for the current user. + + Returns: + QuerySet: A QuerySet of validated consents if the user has permission, + and the slug is valid. + + Raises: + PermissionDenied: If the user does not have permission to validate the + entity. + """ + slug: str | None = self.kwargs.get("slug", None) + user: DashboardUser = self.request.user # type: ignore + + entity: Entity = get_object_or_404(Entity, slug=slug) + if not user.can_validate_entity(entity): + raise PermissionDenied + + return entity.get_validated_consents() diff --git a/src/dashboard/apps/core/models.py b/src/dashboard/apps/core/models.py index cc277e7d..dab2fae8 100644 --- a/src/dashboard/apps/core/models.py +++ b/src/dashboard/apps/core/models.py @@ -134,6 +134,10 @@ def get_awaiting_consents(self) -> QuerySet: """Get all awaiting consents for this entity.""" return self.get_consents(AWAITING) + def get_validated_consents(self) -> QuerySet: + """Get all awaiting consents for this entity.""" + return self.get_consents(VALIDATED) + class DeliveryPoint(DashboardBase): """Represents a delivery point for electric vehicles.