Skip to content

Commit

Permalink
✨(dashboard) add validated consent view and reuse no-data card template
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
ssorin committed Feb 17, 2025
1 parent e7b9e6f commit 8fa3c4b
Show file tree
Hide file tree
Showing 9 changed files with 209 additions and 27 deletions.
2 changes: 2 additions & 0 deletions src/dashboard/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,32 +16,12 @@ <h3 class="fr-card__title">

{# awaiting consent - no items #}
{% if resume_awaiting and not has_awaiting_consent %}
<div class="fr-card__desc fr-highlight">
<p class="fr-card__desc">
Aucune autorisation en attente.
<br />
Si vous pensez qu'il s'agit d'une erreur, merci de contacter l'équipe QualiCharge.
<a class="fr-link fr-card__desc"
href="mailto:{{ contact_email }}?subject=[QualiCharge] Aucune autorisation en attente dans le Dashboard." target="_blank">
{{ contact_email }}
</a>
</p>
</div>
{% 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 %}
<div class="fr-card__desc fr-highlight">
<p class="fr-card__desc">
Aucune station suivie.
<br />
Si vous pensez qu'il s'agit d'une erreur, merci de contacter l'équipe QualiCharge.
<a class="fr-link fr-card__desc"
href="mailto:{{ contact_email }}?subject=[QualiCharge] Aucune station suivie dans le Dashboard." target="_blank">
{{ contact_email }}
</a>
</p>
</div>
{% include "consent/includes/_no_data_card.html" with description="Aucune station suivie." %}
{% endif %}
</div>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
</td>
<td class="fr-cell--center">
<a class="fr-link fr-icon-arrow-right-line fr-link--icon-right"
href="{% url "consent:manage" entity.slug %}"
href="{% url "consent:validated" entity.slug %}"
data-fr-js-link-actionee="true">
Consulter la liste
</a>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{% comment %}
Note: all texts of this page are intended to appear in a contract and must
therefore be in French and non-translatable.
{% endcomment %}

<div class="fr-card__desc fr-highlight">
<p class="fr-card__desc">
{{ description }}
<br />
Si vous pensez qu'il s'agit d'une erreur, merci de contacter l'équipe QualiCharge.
<a class="fr-link fr-card__desc"
href="mailto:{{ contact_email }}?subject=[QualiCharge] Aucune station suivie dans le Dashboard." target="_blank">
{{ contact_email }}
</a>
</p>
</div>
64 changes: 64 additions & 0 deletions src/dashboard/apps/consent/templates/consent/validated.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
{% extends "consent/base.html" %}

{% comment %}
Note: all texts of this page are intended to appear in a contract and must
therefore be in French and non-translatable.
{% endcomment %}

{% load i18n static %}

{% block dashboard_content %}
<h2>Stations suivies</h2>

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

{% if consents %}
<div class="fr-table fr-table--no-caption" id="table-pdl-component">
<div class="fr-table__wrapper">
<div class="fr-table__container">
<div class="fr-table__content">

<table id="table-pdl" aria-labelledby="table-pdl-caption">
<caption id="table-pdl-caption"> Points de livraison suivis </caption>

<thead>
<tr>
<th scope="col">
<div class="fr-cell__title">Nom de la station</div>
</th>
<th scope="col">
<div class="fr-cell__title">Identifiant de la station</div>
</th>
<th scope="col">
<div class="fr-cell__title">Identifiant du PDL</div>
</th>
</tr>
</thead>

<tbody>
{% for consent in consents %}
<tr id="table-pdl-row-key-{{ forloop.counter }}"
data-row-key="{{ forloop.counter }}"
aria-labelledby="row-label-{{ forloop.counter }}">

{# todo : get station_name, station_id, prm_id #}
<td> -- station name -- </td>
<td> -- station id -- </td>
<td> {{ consent.delivery_point.provider_assigned_id }} </td>
</tr>
{% endfor %}
</tbody>
</table>

</div>
</div>
</div>
</div>
{% else %}
{% include "consent/includes/_no_data_card.html" with description="Aucune station suivie." %}
{% endif %}

{% endblock dashboard_content %}
78 changes: 76 additions & 2 deletions src/dashboard/apps/consent/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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_validated_consents()


@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_validated_consents()
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")
8 changes: 7 additions & 1 deletion src/dashboard/apps/consent/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand All @@ -17,4 +17,10 @@
name="manage",
),
path("manage/<slug:slug>", ConsentFormView.as_view(), name="manage"),
path(
"validated/",
RedirectView.as_view(pattern_name="consent:index", permanent=False),
name="validated",
),
path("validated/<slug:slug>", ValidatedConsentView.as_view(), name="validated"),
]
38 changes: 37 additions & 1 deletion src/dashboard/apps/consent/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
PermissionDenied,
ValidationError,
)
from django.db.models import Q
from django.db.models import Q, QuerySet
from django.http import HttpResponse
from django.shortcuts import get_object_or_404
from django.urls import reverse_lazy as reverse
Expand Down Expand Up @@ -270,3 +270,39 @@ def _send_email(self) -> None:
# fail silently and send a sentry log
sentry_sdk.capture_exception(e)
return


class ValidatedConsentView(BreadcrumbContextMixin, TemplateView):
"""Index view of the consent app."""

template_name = "consent/validated.html"
breadcrumb_links = [
{"url": reverse("consent:index"), "title": _("Consent")},
]
breadcrumb_current = _("Manage Consents")

def get_context_data(self, **kwargs): # noqa: D102
context = super().get_context_data(**kwargs)
context["consents"] = self._get_validated_consents()

return context

def _get_validated_consents(self) -> QuerySet:
"""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()
4 changes: 4 additions & 0 deletions src/dashboard/apps/core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down

0 comments on commit 8fa3c4b

Please sign in to comment.