Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨(dashboard) add validated consent view #403

Merged
merged 1 commit into from
Feb 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,11 @@
<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] Dashboard - {{ description }}." 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" %}

{% load dsfr_tags %}

{% 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>

{% dsfr_pagination page_obj %}

{% 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_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")
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"),
]
35 changes: 34 additions & 1 deletion src/dashboard/apps/consent/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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()
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
Loading