diff --git a/aidants_connect/common/forms.py b/aidants_connect/common/forms.py index af3012b73..a8b6adc19 100644 --- a/aidants_connect/common/forms.py +++ b/aidants_connect/common/forms.py @@ -1,4 +1,4 @@ -from django.forms import ModelForm +from django.forms import Form, ModelForm from django.forms.utils import ErrorList from django.utils.html import format_html @@ -19,3 +19,10 @@ def __init__(self, **kwargs): kwargs.setdefault("label_suffix", "") kwargs.setdefault("error_class", PatchedErrorList) super().__init__(**kwargs) + + +class PatchedForm(Form): + def __init__(self, **kwargs): + kwargs.setdefault("error_class", PatchedErrorList) + kwargs.setdefault("label_suffix", "") + super().__init__(**kwargs) diff --git a/aidants_connect_habilitation/forms.py b/aidants_connect_habilitation/forms.py index 7491de2de..f13af2622 100644 --- a/aidants_connect_habilitation/forms.py +++ b/aidants_connect_habilitation/forms.py @@ -1,18 +1,24 @@ from django.conf import settings from django.core.exceptions import ValidationError from django.forms import ( - Form, formset_factory, ChoiceField, CharField, BaseFormSet, + BooleanField, FileField, ) +from django.urls import reverse +from django.utils.html import format_html from phonenumber_field.formfields import PhoneNumberField from phonenumber_field.widgets import PhoneNumberInternationalFallbackWidget from aidants_connect.common.constants import RequestOriginConstants -from aidants_connect.common.forms import PatchedErrorListForm, PatchedErrorList +from aidants_connect.common.forms import ( + PatchedErrorListForm, + PatchedErrorList, + PatchedForm, +) from aidants_connect_habilitation import models from aidants_connect_habilitation.models import ( AidantRequest, @@ -36,6 +42,7 @@ class IssuerForm(PatchedErrorListForm): ) def __init__(self, render_non_editable=False, **kwargs): + kwargs.setdefault("label_suffix", "") super().__init__(**kwargs) self.render_non_editable = render_non_editable if self.render_non_editable: @@ -168,19 +175,9 @@ class Meta: class BaseAidantRequestFormSet(BaseFormSet): - def __init__( - self, - data=None, - files=None, - auto_id="id_%s", - prefix=None, - initial=None, - error_class=PatchedErrorList, - form_kwargs=None, - ): - super().__init__( - data, files, auto_id, prefix, initial, error_class, form_kwargs - ) + def __init__(self, **kwags): + kwags.setdefault("error_class", PatchedErrorList) + super().__init__(**kwags) def save(self, organisation: OrganisationRequest, commit=True): result = [] @@ -226,5 +223,31 @@ def save(self, organisation: OrganisationRequest, commit=True): ) -class ValidationForm(Form): - pass +class ValidationForm(PatchedForm): + cgu = BooleanField( + required=True, + label='J’ai pris connaissance des ' + "conditions générales d’utilisation et je les valide.", + ) + dpo = BooleanField( + required=True, + label="Je confirme que le délégué à la protection des données " + "de mon organisation est informé de ma demande.", + ) + professionals_only = BooleanField( + required=True, + label="Je confirme que la liste des aidants à habiliter contient " + "exclusivement des aidants professionnels. Elle ne contient " + "donc ni service civique, ni bénévole, ni apprenti, ni stagiaire.", + ) + without_elected = BooleanField( + required=True, + label="Je confirme qu’aucun élu n’est impliqué dans l’habilitation " + "Aidants Connect. Le responsable Aidants Connect ainsi que les aidants " + "à habiliter ne sont pas des élus.", + ) + + def __init__(self, **kwargs): + super().__init__(**kwargs) + cgu = self["cgu"] + cgu.label = format_html(cgu.label, url=reverse("cgu")) diff --git a/aidants_connect_habilitation/models.py b/aidants_connect_habilitation/models.py index ebf9fa331..b551df074 100644 --- a/aidants_connect_habilitation/models.py +++ b/aidants_connect_habilitation/models.py @@ -35,6 +35,9 @@ class Person(models.Model): def __str__(self): return f"{self.first_name} {self.last_name}" + def get_full_name(self): + return str(self) + class Meta: abstract = True diff --git a/aidants_connect_habilitation/templates/validation_form.html b/aidants_connect_habilitation/templates/validation_form.html index e69de29bb..5ef67082d 100644 --- a/aidants_connect_habilitation/templates/validation_form.html +++ b/aidants_connect_habilitation/templates/validation_form.html @@ -0,0 +1,111 @@ +{% extends 'layouts/main-habilitation.html' %} +{% load static form_extras %} + +{% block title %} + Aidants Connect — Récapitulatif +{% endblock %} + +{% block content %} +

Récapitulatif de la demande

+

+ Vous trouverez ici les informations principales saisies dans le formulaire. + Il est possible de les éditer si besoin. +

+ +

Informations générales

+ +
+

Vous êtes :

+ + {{ issuer.get_full_name }} +

{{ issuer.profession }}

+ + {{ issuer.email }} + {{ issuer.phone }} + + Éditer +
+ +
+

Vous faites cette demande pour :

+ +
+ {{ organisation.address }}
+ {{ organisation.zipcode }}
+ {{ organisation.city }}
+
+ +

SIRET : {{ organisation.siret }}

+ + + Éditer + +
+ +
+

Responsable de structure

+ + {{ organisation.manager.get_full_name }} +

{{ organisation.manager.profession }}

+ + {{ organisation.manager.email }} + {{ organisation.manager.phone }} + +
+ {{ organisation.manager.address }}
+ {{ organisation.manager.zipcode }}
+ {{ organisation.manager.city }}
+
+ +

Ce responsable est aussi aidant : {{ organisation.manager.is_aidant|yesno:"Oui,Non" }}

+ + Éditer +
+ + {% if organisation.data_privacy_officer %} +
+

Délégué à la protection des données

+ + {{ organisation.data_privacy_officer.get_full_name }} +

{{ organisation.data_privacy_officer.profession }}

+ + {{ organisation.data_privacy_officer.email }} + {{ organisation.data_privacy_officer.phone }} + + + Éditer + +
+ {% endif %} + + {% for aidant_request in organisation.aidant_requests.all %} +
+

Aidant

+ + {{ aidant_request.get_full_name }} +

{{ aidant_request.profession }}

+ + {{ aidant_request.email }} + {{ aidant_request.phone }} + + + Éditer + +
+ {% endfor %} + +
+ {% csrf_token %} + {{ form.non_field_errors }} + + {% field_as_p form.cgu %} + {% field_as_p form.dpo %} + {% field_as_p form.professionals_only %} + {% field_as_p form.without_elected %} + + + Revenir à l’étape précédente + + +
+{% endblock %} diff --git a/aidants_connect_habilitation/tests/test_views.py b/aidants_connect_habilitation/tests/test_views.py index cee8bf439..fec3cad23 100644 --- a/aidants_connect_habilitation/tests/test_views.py +++ b/aidants_connect_habilitation/tests/test_views.py @@ -43,10 +43,11 @@ class NewIssuerFormViewTests(TestCase): def setUpTestData(cls): cls.client = Client() cls.pattern_name = "habilitation_new_issuer" + cls.template_name = "issuer_form.html" def test_template(self): response = self.client.get(reverse(self.pattern_name)) - self.assertTemplateUsed(response, "issuer_form.html") + self.assertTemplateUsed(response, self.template_name) def test_redirect_valid_post_to_new_organisation(self): temp_uuid = "d6cc0622-b525-41ee-a8dc-c65de9536dba" @@ -73,6 +74,8 @@ class ModifyIssuerFormViewTests(TestCase): def setUpTestData(cls): cls.client = Client() cls.pattern_name = "habilitation_modify_issuer" + cls.template_name = "issuer_form.html" + cls.issuer: Issuer = IssuerFactory() def test_404_on_bad_issuer_id(self): response = self.client.get( @@ -87,18 +90,26 @@ def test_404_on_bad_issuer_id(self): ) self.assertEqual(response.status_code, 404) + def test_template(self): + response = self.client.get( + reverse( + self.pattern_name, + kwargs={"issuer_id": self.issuer.issuer_id}, + ) + ) + self.assertTemplateUsed(response, self.template_name) + def test_on_correct_issuer_id_post_updates_model(self): - model: Issuer = IssuerFactory() new_name = Faker("first_name").evaluate(None, None, {"locale": DEFAULT_LOCALE}) - form = IssuerForm(data={**model_to_dict(model), "first_name": new_name}) + form = IssuerForm(data={**model_to_dict(self.issuer), "first_name": new_name}) if not form.is_valid(): raise ValueError(str(form.errors)) - self.assertNotEqual(model.first_name, new_name) + self.assertNotEqual(self.issuer.first_name, new_name) response = self.client.post( - reverse(self.pattern_name, kwargs={"issuer_id": model.issuer_id}), + reverse(self.pattern_name, kwargs={"issuer_id": self.issuer.issuer_id}), form.clean(), ) @@ -106,13 +117,15 @@ def test_on_correct_issuer_id_post_updates_model(self): response, reverse( "habilitation_new_organisation", - kwargs={"issuer_id": model.issuer_id}, + kwargs={"issuer_id": self.issuer.issuer_id}, ), ) - model.refresh_from_db() + self.issuer.refresh_from_db() self.assertEqual( - model.first_name, new_name, "The model's first_name field wasn't modified" + self.issuer.first_name, + new_name, + "The model's first_name field wasn't modified", ) @@ -122,6 +135,8 @@ class NewOrganisationRequestFormViewTests(TestCase): def setUpTestData(cls): cls.client = Client() cls.pattern_name = "habilitation_new_organisation" + cls.template_name = "organisation_form.html" + cls.issuer: Issuer = IssuerFactory() def test_404_on_bad_issuer_id(self): uuid = uuid4() @@ -138,9 +153,13 @@ def test_404_on_bad_issuer_id(self): ) self.assertEqual(response.status_code, 404) - def test_redirect_valid_post_to_new_aidants(self): - issuer: Issuer = IssuerFactory() + def test_template(self): + response = self.client.get( + reverse(self.pattern_name, kwargs={"issuer_id": self.issuer.issuer_id}) + ) + self.assertTemplateUsed(response, self.template_name) + def test_redirect_valid_post_to_new_aidants(self): cleaned_data = utils.get_form(OrganisationRequestForm).clean() cleaned_data["public_service_delegation_attestation"] = "" cleaned_data["type"] = cleaned_data["type"].id @@ -148,7 +167,7 @@ def test_redirect_valid_post_to_new_aidants(self): response = self.client.post( reverse( self.pattern_name, - kwargs={"issuer_id": issuer.issuer_id}, + kwargs={"issuer_id": self.issuer.issuer_id}, ), cleaned_data, ) @@ -158,8 +177,8 @@ def test_redirect_valid_post_to_new_aidants(self): reverse( "habilitation_new_aidants", kwargs={ - "issuer_id": issuer.issuer_id, - "draft_id": issuer.organisation_requests.first().draft_id, + "issuer_id": self.issuer.issuer_id, + "draft_id": self.issuer.organisation_requests.first().draft_id, }, ), ) @@ -171,7 +190,38 @@ class ModifyOrganisationRequestFormViewTests(TestCase): def setUpTestData(cls): cls.client = Client() cls.pattern_name = "habilitation_modify_organisation" + cls.template_name = "organisation_form.html" cls.issuer: Issuer = IssuerFactory() + cls.organisation: OrganisationRequest = OrganisationRequestFactory( + draft_id=uuid4() + ) + + def test_404_on_bad_issuer_id(self): + issuer_id = uuid4() + + response: HttpResponse = self.client.get( + reverse( + self.pattern_name, + kwargs={"issuer_id": issuer_id, "draft_id": self.organisation.draft_id}, + ) + ) + self.assertEqual(response.status_code, 404) + + draft_id = uuid4() + + cleaned_data = utils.get_form(OrganisationRequestForm).clean() + cleaned_data["public_service_delegation_attestation"] = "" + response = self.client.post( + reverse( + self.pattern_name, + kwargs={ + "issuer_id": self.organisation.issuer.issuer_id, + "draft_id": draft_id, + }, + ), + cleaned_data, + ) + self.assertEqual(response.status_code, 404) def test_404_on_bad_draft_id(self): response = self.client.get( @@ -195,6 +245,18 @@ def test_404_on_bad_draft_id(self): ) self.assertEqual(response.status_code, 404) + def test_template(self): + response = self.client.get( + reverse( + self.pattern_name, + kwargs={ + "issuer_id": self.organisation.issuer.issuer_id, + "draft_id": self.organisation.draft_id, + }, + ) + ) + self.assertTemplateUsed(response, self.template_name) + def test_on_correct_issuer_id_post_updates_model(self): model: OrganisationRequest = OrganisationRequestFactory( issuer=self.issuer, draft_id=uuid4() @@ -243,16 +305,18 @@ class AidantsRequestFormViewTests(TestCase): def setUpTestData(cls): cls.client = Client() cls.pattern_name = "habilitation_new_aidants" + cls.template_name = "personnel_form.html" + cls.organisation: OrganisationRequest = OrganisationRequestFactory( + draft_id=uuid4() + ) def test_404_on_bad_issuer_id(self): issuer_id = uuid4() - organisation: OrganisationRequest = OrganisationRequestFactory(draft_id=uuid4()) - response: HttpResponse = self.client.get( reverse( self.pattern_name, - kwargs={"issuer_id": issuer_id, "draft_id": organisation.draft_id}, + kwargs={"issuer_id": issuer_id, "draft_id": self.organisation.draft_id}, ) ) self.assertEqual(response.status_code, 404) @@ -265,7 +329,7 @@ def test_404_on_bad_issuer_id(self): reverse( self.pattern_name, kwargs={ - "issuer_id": organisation.issuer.issuer_id, + "issuer_id": self.organisation.issuer.issuer_id, "draft_id": draft_id, }, ), @@ -273,7 +337,43 @@ def test_404_on_bad_issuer_id(self): ) self.assertEqual(response.status_code, 404) - def test_redirect_valid_post_to_new_issuer(self): + def test_404_on_bad_draft_id(self): + issuer: Issuer = IssuerFactory() + + response = self.client.get( + reverse( + self.pattern_name, + kwargs={"issuer_id": issuer.issuer_id, "draft_id": uuid4()}, + ) + ) + self.assertEqual(response.status_code, 404) + + cleaned_data = utils.get_form(OrganisationRequestForm).clean() + cleaned_data["public_service_delegation_attestation"] = "" + cleaned_data["type"] = cleaned_data["type"].id + + response = self.client.post( + reverse( + self.pattern_name, + kwargs={"issuer_id": issuer.issuer_id, "draft_id": uuid4()}, + ), + cleaned_data, + ) + self.assertEqual(response.status_code, 404) + + def test_template(self): + response = self.client.get( + reverse( + self.pattern_name, + kwargs={ + "issuer_id": self.organisation.issuer.issuer_id, + "draft_id": self.organisation.draft_id, + }, + ) + ) + self.assertTemplateUsed(response, self.template_name) + + def test_redirect_valid_post_to_validation(self): organisation: OrganisationRequest = OrganisationRequestFactory(draft_id=uuid4()) manager_data = utils.get_form(ManagerForm).data @@ -309,3 +409,135 @@ def test_redirect_valid_post_to_new_issuer(self): }, ), ) + + +@tag("habilitation") +class ValidationRequestFormViewTests(TestCase): + @classmethod + def setUpTestData(cls): + cls.client = Client() + cls.pattern_name = "habilitation_validation" + cls.template_name = "validation_form.html" + cls.organisation: OrganisationRequest = OrganisationRequestFactory( + draft_id=uuid4() + ) + + def test_404_on_bad_issuer_id(self): + issuer_id = uuid4() + + organisation: OrganisationRequest = OrganisationRequestFactory(draft_id=uuid4()) + + response: HttpResponse = self.client.get( + reverse( + self.pattern_name, + kwargs={"issuer_id": issuer_id, "draft_id": organisation.draft_id}, + ) + ) + self.assertEqual(response.status_code, 404) + + draft_id = uuid4() + + cleaned_data = utils.get_form(OrganisationRequestForm).clean() + cleaned_data["public_service_delegation_attestation"] = "" + response = self.client.post( + reverse( + self.pattern_name, + kwargs={ + "issuer_id": organisation.issuer.issuer_id, + "draft_id": draft_id, + }, + ), + cleaned_data, + ) + self.assertEqual(response.status_code, 404) + + def test_404_on_bad_draft_id(self): + response = self.client.get( + reverse( + self.pattern_name, + kwargs={ + "issuer_id": self.organisation.issuer.issuer_id, + "draft_id": uuid4(), + }, + ) + ) + self.assertEqual(response.status_code, 404) + + cleaned_data = utils.get_form(OrganisationRequestForm).clean() + cleaned_data["public_service_delegation_attestation"] = "" + cleaned_data["type"] = cleaned_data["type"].id + + response = self.client.post( + reverse( + self.pattern_name, + kwargs={ + "issuer_id": self.organisation.issuer.issuer_id, + "draft_id": uuid4(), + }, + ), + cleaned_data, + ) + self.assertEqual(response.status_code, 404) + + def test_template(self): + response = self.client.get( + reverse( + self.pattern_name, + kwargs={ + "issuer_id": self.organisation.issuer.issuer_id, + "draft_id": self.organisation.draft_id, + }, + ) + ) + self.assertTemplateUsed(response, self.template_name) + + def test_redirect_valid_post_to_new_issuer(self): + cleaned_data = { + "cgu": True, + "dpo": True, + "professionals_only": True, + "without_elected": True, + } + + response = self.client.post( + reverse( + self.pattern_name, + kwargs={ + "issuer_id": self.organisation.issuer.issuer_id, + "draft_id": self.organisation.draft_id, + }, + ), + cleaned_data, + ) + + self.assertRedirects(response, reverse("habilitation_new_issuer")) + + def test_post_invalid_data(self): + valid_data = { + "cgu": True, + "dpo": True, + "professionals_only": True, + "without_elected": True, + } + + for item in valid_data.keys(): + invalid_data = valid_data.copy() + invalid_data[item] = False + + response = self.client.post( + reverse( + self.pattern_name, + kwargs={ + "issuer_id": self.organisation.issuer.issuer_id, + "draft_id": self.organisation.draft_id, + }, + ), + invalid_data, + ) + + self.assertEqual(response.status_code, 200) + self.assertTemplateUsed(response, self.template_name) + self.assertIn( + "Ce champ est obligatoire", + str(response.context_data["form"].errors[item]), + ) diff --git a/aidants_connect_habilitation/tests/utils.py b/aidants_connect_habilitation/tests/utils.py index 4ade3dd0a..6a10d453e 100644 --- a/aidants_connect_habilitation/tests/utils.py +++ b/aidants_connect_habilitation/tests/utils.py @@ -49,7 +49,7 @@ def _(form_cls: Type[BaseFormSet], **kwargs) -> BaseFormSet: subdata = {f"form-{i}-{k}": v for (k, v) in subform.clean().items()} data.update(subdata) - form = formset_cls(data) + form = formset_cls(data=data) if not form.is_valid(): raise ValueError(str(form.errors)) diff --git a/aidants_connect_habilitation/views.py b/aidants_connect_habilitation/views.py index 2fc0619d4..822acb406 100644 --- a/aidants_connect_habilitation/views.py +++ b/aidants_connect_habilitation/views.py @@ -8,7 +8,47 @@ PersonnelForm, ValidationForm, ) -from aidants_connect_habilitation.models import Issuer, OrganisationRequest +from aidants_connect_habilitation.models import ( + Issuer, + OrganisationRequest, +) + + +__all__ = [ + "NewHabilitationView", + "NewIssuerFormView", + "ModifyIssuerFormView", + "NewOrganisationRequestFormView", + "ModifyOrganisationRequestFormView", + "PersonnelRequestFormView", + "ValidationRequestFormView", +] + + +"""Mixins""" + + +class RequestDraftView(FormView): + def setup(self, request, *args, **kwargs): + super().setup(request, *args, **kwargs) + self.issuer = get_object_or_404(Issuer, issuer_id=kwargs.get("issuer_id")) + + def get_context_data(self, **kwargs): + return { + **super().get_context_data(**kwargs), + "issuer": self.issuer, + } + + +class LateStageRequestDraftView(RequestDraftView): + def setup(self, request, *args, **kwargs): + super().setup(request, *args, **kwargs) + self.organisation = get_object_or_404( + OrganisationRequest, draft_id=kwargs.get("draft_id") + ) + + +"""Real views""" class NewHabilitationView(RedirectView): @@ -42,18 +82,6 @@ def get_form_kwargs(self): return {**super().get_form_kwargs(), "instance": self.initial_issuer} -class RequestDraftView(FormView): - def setup(self, request, *args, **kwargs): - super().setup(request, *args, **kwargs) - self.issuer = get_object_or_404(Issuer, issuer_id=kwargs.get("issuer_id")) - - def get_context_data(self, **kwargs): - return { - **super().get_context_data(**kwargs), - "issuer": self.issuer, - } - - class NewOrganisationRequestFormView(RequestDraftView): template_name = "organisation_form.html" form_class = OrganisationRequestForm @@ -73,27 +101,17 @@ def get_success_url(self): ) -class ModifyOrganisationRequestFormView(NewOrganisationRequestFormView): - def setup(self, request, *args, **kwargs): - super().setup(request, *args, **kwargs) - self.initial_org_request = get_object_or_404( - OrganisationRequest, draft_id=self.kwargs.get("draft_id") - ) - +class ModifyOrganisationRequestFormView( + LateStageRequestDraftView, NewOrganisationRequestFormView +): def get_form_kwargs(self): - return {**super().get_form_kwargs(), "instance": self.initial_org_request} + return {**super().get_form_kwargs(), "instance": self.organisation} -class PersonnelRequestFormView(RequestDraftView): +class PersonnelRequestFormView(LateStageRequestDraftView): template_name = "personnel_form.html" form_class = PersonnelForm - def setup(self, request, *args, **kwargs): - super().setup(request, *args, **kwargs) - self.organisation: OrganisationRequest = get_object_or_404( - OrganisationRequest, draft_id=self.kwargs.get("draft_id") - ) - def form_valid(self, form): manager, data_privacy_officer, _ = form.save(self.organisation) self.organisation.manager = manager @@ -117,9 +135,17 @@ def get_success_url(self): ) -class ValidationRequestFormView(RequestDraftView): +class ValidationRequestFormView(LateStageRequestDraftView): template_name = "validation_form.html" form_class = ValidationForm + def get_context_data(self, **kwargs): + return { + **super().get_context_data(**kwargs), + "issuer": self.issuer, + "organisation": self.organisation, + "aidants": self.organisation.aidant_requests, + } + def get_success_url(self): return reverse("habilitation_new_issuer")