From cdd5d6ec0debf5c648e43da52b53972d660e7541 Mon Sep 17 00:00:00 2001 From: alan Date: Fri, 15 Nov 2024 14:32:08 +0100 Subject: [PATCH] =?UTF-8?q?Masque=20le=20bloc=20de=20suivi=20si=20la=20fic?= =?UTF-8?q?he=20d=C3=A9tection=20est=20en=20visibit=C3=A9=20brouillon?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Côté front: - modification du template detail de la fiche détection pour masquer le bloc de suivi et les modales utilisées dans celui-ci. - ajout de tests bloc de suivi masqué si brouillon et affiché si local ou national Côté serveur: - application du mixin PreventActionIfVisibiliteBrouillonMixin dans les vues du bloc de suivi - ajout de tests pour vérifier que les requêtes HTTP sur les différentes routes du bloc de suivi soient refusées si la fiche est en brouillon Autres: - modification du nom et de l'accessibilité de la méthode _add_message_url dans le mixin WithMessageUrlsMixin car besoin dans les tests sv/tests/test_fiches_message.py - modification de sv/tests/test_fichedetection_performances.py pour que les test s'effectuent sur une fiche détection en visibilité local car BASE_NUM_QUERIES prend en compte l'affichage du bloc de suivi (14 requêtes contre 10 sans). - modification du test test_structure_list pour que la vérification se fasse directement dans le formulaire HTML et non dans le contexte du formulaire. --- core/mixins.py | 20 ++-- core/views.py | 90 +++++++++++---- sv/templates/sv/fichedetection_detail.html | 5 +- sv/tests/test_fichedetection_detail.py | 33 ++++++ sv/tests/test_fichedetection_performances.py | 68 +++++------- sv/tests/test_fiches_contact_add.py | 111 +++++++++++++++++-- sv/tests/test_fiches_documents.py | 80 ++++++++++++- sv/tests/test_fiches_message.py | 69 +++++++++++- sv/tests/test_fiches_structure_add.py | 72 +++++++++++- sv/views.py | 2 +- 10 files changed, 466 insertions(+), 84 deletions(-) diff --git a/core/mixins.py b/core/mixins.py index 5b9c83c0..747a8b7c 100644 --- a/core/mixins.py +++ b/core/mixins.py @@ -129,7 +129,7 @@ class Meta: class WithMessageUrlsMixin: - def _add_message_url(self, message_type): + def get_add_message_url(self, message_type): content_type = ContentType.objects.get_for_model(self) return reverse( "message-add", kwargs={"message_type": message_type, "obj_type_pk": content_type.pk, "obj_pk": self.pk} @@ -137,27 +137,27 @@ def _add_message_url(self, message_type): @property def add_message_url(self): - return self._add_message_url(Message.MESSAGE) + return self.get_add_message_url(Message.MESSAGE) @property def add_note_url(self): - return self._add_message_url(Message.NOTE) + return self.get_add_message_url(Message.NOTE) @property def add_point_de_suivi_url(self): - return self._add_message_url(Message.POINT_DE_SITUATION) + return self.get_add_message_url(Message.POINT_DE_SITUATION) @property def add_demande_intervention_url(self): - return self._add_message_url(Message.DEMANDE_INTERVENTION) + return self.get_add_message_url(Message.DEMANDE_INTERVENTION) @property def add_compte_rendu_url(self): - return self._add_message_url(Message.COMPTE_RENDU) + return self.get_add_message_url(Message.COMPTE_RENDU) @property def add_fin_suivi_url(self): - return self._add_message_url(Message.FIN_SUIVI) + return self.get_add_message_url(Message.FIN_SUIVI) class AllowVisibiliteMixin(models.Model): @@ -236,11 +236,11 @@ class PreventActionIfVisibiliteBrouillonMixin: Mixin pour empêcher des actions sur des objets ayant la visibilité 'brouillon'. """ - def get_object(self): - raise NotImplementedError("Vous devez implémenter la méthode `get_object` pour ce mixin.") + def get_fiche_object(self): + raise NotImplementedError("Vous devez implémenter la méthode `get_fiche_object` pour ce mixin.") def dispatch(self, request, *args, **kwargs): - obj = self.get_object() + obj = self.get_fiche_object() if obj.visibilite == Visibilite.BROUILLON: messages.error(request, "Action impossible car la fiche est en brouillon") return safe_redirect(request.POST.get("next") or obj.get_absolute_url() or "/") diff --git a/core/views.py b/core/views.py index eb2fc45b..22f82013 100644 --- a/core/views.py +++ b/core/views.py @@ -28,9 +28,14 @@ from .redirect import safe_redirect -class DocumentUploadView(FormView): +class DocumentUploadView(PreventActionIfVisibiliteBrouillonMixin, FormView): form_class = DocumentUploadForm + def get_fiche_object(self): + content_type = ContentType.objects.get(id=self.request.POST.get("content_type")) + ModelClass = content_type.model_class() + return get_object_or_404(ModelClass, pk=self.request.POST.get("object_id")) + def post(self, request, *args, **kwargs): form = DocumentUploadForm(request.POST, request.FILES) if form.is_valid(): @@ -46,21 +51,28 @@ def post(self, request, *args, **kwargs): return safe_redirect(self.request.POST.get("next") + "#tabpanel-documents-panel") -class DocumentDeleteView(View): +class DocumentDeleteView(PreventActionIfVisibiliteBrouillonMixin, View): + def get_fiche_object(self): + self.document = get_object_or_404(Document, pk=self.kwargs.get("pk")) + return self.document.content_object + def post(self, request, *args, **kwargs): - document = get_object_or_404(Document, pk=kwargs.get("pk")) - document.is_deleted = True - document.deleted_by = self.request.user.agent - document.save() + self.document.is_deleted = True + self.document.deleted_by = self.request.user.agent + self.document.save() messages.success(request, "Le document a été marqué comme supprimé.", extra_tags="core documents") return safe_redirect(request.POST.get("next") + "#tabpanel-documents-panel") -class DocumentUpdateView(UpdateView): +class DocumentUpdateView(PreventActionIfVisibiliteBrouillonMixin, UpdateView): model = Document form_class = DocumentEditForm http_method_names = ["post"] + def get_fiche_object(self): + self.document = get_object_or_404(Document, pk=self.kwargs.get("pk")) + return self.document.content_object + def get_success_url(self): return self.request.POST.get("next") + "#tabpanel-documents-panel" @@ -70,10 +82,17 @@ def form_valid(self, form): return response -class ContactAddFormView(FormView): +class ContactAddFormView(PreventActionIfVisibiliteBrouillonMixin, FormView): template_name = "core/_contact_add_form.html" form_class = ContactAddForm + def get_fiche_object(self): + content_type_id = self.request.GET.get("content_type_id") or self.request.POST.get("content_type_id") + fiche_id = self.request.GET.get("fiche_id") or self.request.POST.get("fiche_id") + content_type = ContentType.objects.get(pk=content_type_id) + ModelClass = content_type.model_class() + return get_object_or_404(ModelClass, pk=fiche_id) + def get_initial(self): initial = super().get_initial() initial["fiche_id"] = self.request.GET.get("fiche_id") @@ -94,10 +113,17 @@ def form_valid(self, form): return render(self.request, self.template_name, {"form": form, "selection_form": selection_form}) -class ContactSelectionView(FormView): +class ContactSelectionView(PreventActionIfVisibiliteBrouillonMixin, FormView): template_name = "core/_contact_add_form.html" form_class = ContactSelectionForm + def get_fiche_object(self): + content_type_id = self.request.POST.get("content_type_id") + fiche_id = self.request.POST.get("fiche_id") + content_type = ContentType.objects.get(pk=content_type_id) + ModelClass = content_type.model_class() + return get_object_or_404(ModelClass, pk=fiche_id) + def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context["next"] = self.request.GET.get("next") @@ -147,19 +173,21 @@ def form_invalid(self, form): ) -class ContactDeleteView(View): - def post(self, request, *args, **kwargs): - content_type = ContentType.objects.get(id=request.POST.get("content_type_pk")) +class ContactDeleteView(PreventActionIfVisibiliteBrouillonMixin, View): + def get_fiche_object(self): + content_type = ContentType.objects.get(id=self.request.POST.get("content_type_pk")) ModelClass = content_type.model_class() - fiche = get_object_or_404(ModelClass, pk=request.POST.get("fiche_pk")) - contact = Contact.objects.get(pk=self.request.POST.get("pk")) + self.fiche = get_object_or_404(ModelClass, pk=self.request.POST.get("fiche_pk")) + return self.fiche - fiche.contacts.remove(contact) + def post(self, request, *args, **kwargs): + contact = Contact.objects.get(pk=self.request.POST.get("pk")) + self.fiche.contacts.remove(contact) messages.success(request, "Le contact a bien été supprimé de la fiche.", extra_tags="core contacts") return safe_redirect(request.POST.get("next") + "#tabpanel-contacts-panel") -class MessageCreateView(CreateView): +class MessageCreateView(PreventActionIfVisibiliteBrouillonMixin, CreateView): model = Message form_class = MessageForm @@ -169,6 +197,9 @@ def dispatch(self, request, *args, **kwargs): self.obj = get_object_or_404(self.obj_class, pk=self.kwargs.get("obj_pk")) return super().dispatch(request, *args, **kwargs) + def get_fiche_object(self): + return self.obj + def get_form_kwargs(self): kwargs = super().get_form_kwargs() kwargs.update( @@ -243,14 +274,26 @@ def form_valid(self, form): return response -class MessageDetailsView(DetailView): +class MessageDetailsView(PreventActionIfVisibiliteBrouillonMixin, DetailView): model = Message + def get_fiche_object(self): + message = get_object_or_404(Message, pk=self.kwargs.get("pk")) + fiche = message.content_object + return fiche + -class StructureAddFormView(FormView): +class StructureAddFormView(PreventActionIfVisibiliteBrouillonMixin, FormView): template_name = "core/_structure_add_form.html" form_class = StructureAddForm + def get_fiche_object(self): + content_type_id = self.request.GET.get("content_type_id") or self.request.POST.get("content_type_id") + fiche_id = self.request.GET.get("fiche_id") or self.request.POST.get("fiche_id") + content_type = ContentType.objects.get(pk=content_type_id) + ModelClass = content_type.model_class() + return get_object_or_404(ModelClass, pk=fiche_id) + def get_initial(self): initial = super().get_initial() initial["fiche_id"] = self.request.GET.get("fiche_id") @@ -270,10 +313,17 @@ def form_valid(self, form): return render(self.request, self.template_name, {"form": form, "selection_form": selection_form}) -class StructureSelectionView(FormView): +class StructureSelectionView(PreventActionIfVisibiliteBrouillonMixin, FormView): template_name = "core/_structure_add_form.html" form_class = StructureSelectionForm + def get_fiche_object(self): + content_type_id = self.request.POST.get("content_type_id") + fiche_id = self.request.POST.get("fiche_id") + content_type = ContentType.objects.get(pk=content_type_id) + ModelClass = content_type.model_class() + return get_object_or_404(ModelClass, pk=fiche_id) + def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context["next"] = self.request.GET.get("next") @@ -343,7 +393,7 @@ def post(self, request): class ACNotificationView(PreventActionIfVisibiliteBrouillonMixin, View): - def get_object(self): + def get_fiche_object(self): content_type_id = self.request.POST.get("content_type_id") content_id = self.request.POST.get("content_id") content_type = ContentType.objects.get(pk=content_type_id).model_class() diff --git a/sv/templates/sv/fichedetection_detail.html b/sv/templates/sv/fichedetection_detail.html index 01bc631b..4a7d6fb7 100644 --- a/sv/templates/sv/fichedetection_detail.html +++ b/sv/templates/sv/fichedetection_detail.html @@ -229,8 +229,9 @@

Mesures de gestion

{% include "sv/_fichedetection_synthese.html" %} - - {% include "core/_fiche_bloc_commun.html" with fiche=fichedetection %} + {% if not fichedetection.is_draft %} + {% include "core/_fiche_bloc_commun.html" with fiche=fichedetection %} + {% endif %} {% endblock %} diff --git a/sv/tests/test_fichedetection_detail.py b/sv/tests/test_fichedetection_detail.py index 0336b010..dac9cbfd 100644 --- a/sv/tests/test_fichedetection_detail.py +++ b/sv/tests/test_fichedetection_detail.py @@ -1,5 +1,7 @@ from django.urls import reverse +import pytest + from core.models import Visibilite from sv.models import Lieu, Prelevement, FicheZoneDelimitee, ZoneInfestee, FicheDetection from model_bakery import baker @@ -205,3 +207,34 @@ def test_fiche_detection_brouillon_cannot_add_zone(live_server, page, mocked_aut # simule le fait d'effectuer la requete GET directement pour ajouter une zone page.goto(f"{live_server.url}{reverse('rattachement-fiche-zone-delimitee', args=[fiche_detection.id])}") expect(page.get_by_text("Action impossible car la fiche est en brouillon")).to_be_visible() + + +def test_fiche_detection_brouillon_does_not_have_bloc_suivi_display(live_server, page, mocked_authentification_user): + fiche_detection = baker.make( + FicheDetection, visibilite=Visibilite.BROUILLON, createur=mocked_authentification_user.agent.structure + ) + page.goto(f"{live_server.url}{fiche_detection.get_absolute_url()}") + expect(page.get_by_label("Fil de suivi")).not_to_be_visible() + expect(page.get_by_label("Contacts")).not_to_be_visible() + expect(page.get_by_label("Documents")).not_to_be_visible() + + +@pytest.mark.parametrize( + "visibilite", + [ + Visibilite.LOCAL, + Visibilite.NATIONAL, + ], +) +def test_fiche_detection_local_or_national_have_bloc_suivi_display( + live_server, page, visibilite: Visibilite, mocked_authentification_user +): + fiche_detection = baker.make( + FicheDetection, visibilite=visibilite, createur=mocked_authentification_user.agent.structure + ) + page.goto(f"{live_server.url}{fiche_detection.get_absolute_url()}") + expect(page.get_by_label("Fil de suivi")).to_be_visible() + expect(page.get_by_label("Contacts")).to_have_count(1) + expect(page.get_by_label("Contacts")).to_be_hidden() + expect(page.get_by_label("Documents")).to_have_count(1) + expect(page.get_by_label("Documents")).to_be_hidden() diff --git a/sv/tests/test_fichedetection_performances.py b/sv/tests/test_fichedetection_performances.py index 858bb314..2298beef 100644 --- a/sv/tests/test_fichedetection_performances.py +++ b/sv/tests/test_fichedetection_performances.py @@ -2,100 +2,92 @@ from model_bakery import baker from core.models import Message, Document, Structure, Contact -from sv.models import FicheDetection, Lieu, Prelevement +from sv.models import Lieu, Prelevement BASE_NUM_QUERIES = 14 # Please note a first call is made without assertion to warm up any possible cache @pytest.mark.django_db -def test_empty_fiche_detection_performances(client, django_assert_num_queries, mocked_authentification_user): - fiche = baker.make(FicheDetection, createur=mocked_authentification_user.agent.structure) - client.get(fiche.get_absolute_url()) +def test_empty_fiche_detection_performances(client, django_assert_num_queries, fiche_detection): + client.get(fiche_detection.get_absolute_url()) with django_assert_num_queries(BASE_NUM_QUERIES): - client.get(fiche.get_absolute_url()) + client.get(fiche_detection.get_absolute_url()) @pytest.mark.django_db def test_fiche_detection_performances_with_messages_from_same_user( - client, django_assert_num_queries, mocked_authentification_user + client, django_assert_num_queries, mocked_authentification_user, fiche_detection ): - fiche = baker.make(FicheDetection, createur=mocked_authentification_user.agent.structure) - client.get(fiche.get_absolute_url()) + client.get(fiche_detection.get_absolute_url()) - baker.make(Message, content_object=fiche, sender=mocked_authentification_user.agent.contact_set.get()) + baker.make(Message, content_object=fiche_detection, sender=mocked_authentification_user.agent.contact_set.get()) with django_assert_num_queries(BASE_NUM_QUERIES + 6): - client.get(fiche.get_absolute_url()) + client.get(fiche_detection.get_absolute_url()) baker.make( Message, - content_object=fiche, + content_object=fiche_detection, sender=mocked_authentification_user.agent.contact_set.get(), _quantity=3, ) with django_assert_num_queries(BASE_NUM_QUERIES + 6): - response = client.get(fiche.get_absolute_url()) + response = client.get(fiche_detection.get_absolute_url()) assert len(response.context["message_list"]) == 4 @pytest.mark.django_db -def test_fiche_detection_performances_with_lieux(client, django_assert_num_queries, mocked_authentification_user): - fiche = baker.make(FicheDetection, createur=mocked_authentification_user.agent.structure) - client.get(fiche.get_absolute_url()) +def test_fiche_detection_performances_with_lieux(client, django_assert_num_queries, fiche_detection): + client.get(fiche_detection.get_absolute_url()) with django_assert_num_queries(BASE_NUM_QUERIES): - client.get(fiche.get_absolute_url()) + client.get(fiche_detection.get_absolute_url()) - baker.make(Lieu, fiche_detection=fiche, _quantity=3, _fill_optional=True) + baker.make(Lieu, fiche_detection=fiche_detection, _quantity=3, _fill_optional=True) with django_assert_num_queries(BASE_NUM_QUERIES): - client.get(fiche.get_absolute_url()) + client.get(fiche_detection.get_absolute_url()) @pytest.mark.django_db -def test_fiche_detection_performances_with_document(client, django_assert_num_queries, mocked_authentification_user): - fiche = baker.make(FicheDetection, createur=mocked_authentification_user.agent.structure) - client.get(fiche.get_absolute_url()) +def test_fiche_detection_performances_with_document(client, django_assert_num_queries, fiche_detection): + client.get(fiche_detection.get_absolute_url()) with django_assert_num_queries(BASE_NUM_QUERIES): - client.get(fiche.get_absolute_url()) + client.get(fiche_detection.get_absolute_url()) - baker.make(Document, content_object=fiche, _quantity=3, _create_files=True) + baker.make(Document, content_object=fiche_detection, _quantity=3, _create_files=True) with django_assert_num_queries(BASE_NUM_QUERIES + 1): - client.get(fiche.get_absolute_url()) + client.get(fiche_detection.get_absolute_url()) @pytest.mark.django_db -def test_fiche_detection_performances_with_prelevement(client, django_assert_num_queries, mocked_authentification_user): - fiche = baker.make(FicheDetection, createur=mocked_authentification_user.agent.structure) - client.get(fiche.get_absolute_url()) +def test_fiche_detection_performances_with_prelevement(client, django_assert_num_queries, fiche_detection): + client.get(fiche_detection.get_absolute_url()) with django_assert_num_queries(BASE_NUM_QUERIES): - client.get(fiche.get_absolute_url()) + client.get(fiche_detection.get_absolute_url()) for _ in range(0, 3): - lieu = baker.make(Lieu, fiche_detection=fiche) + lieu = baker.make(Lieu, fiche_detection=fiche_detection) baker.make(Prelevement, lieu=lieu, _fill_optional=True) with django_assert_num_queries(BASE_NUM_QUERIES): - client.get(fiche.get_absolute_url()) + client.get(fiche_detection.get_absolute_url()) @pytest.mark.django_db -def test_fiche_detection_performances_when_adding_structure( - client, django_assert_num_queries, mocked_authentification_user -): - fiche = baker.make(FicheDetection, createur=mocked_authentification_user.agent.structure) - client.get(fiche.get_absolute_url()) +def test_fiche_detection_performances_when_adding_structure(client, django_assert_num_queries, fiche_detection): + client.get(fiche_detection.get_absolute_url()) with django_assert_num_queries(BASE_NUM_QUERIES): - client.get(fiche.get_absolute_url()) + client.get(fiche_detection.get_absolute_url()) for _ in range(0, 10): structure = baker.make(Structure) contact = baker.make(Contact, structure=structure, agent=None) - fiche.contacts.add(contact) + fiche_detection.contacts.add(contact) with django_assert_num_queries(BASE_NUM_QUERIES + 1): - client.get(fiche.get_absolute_url()) + client.get(fiche_detection.get_absolute_url()) diff --git a/sv/tests/test_fiches_contact_add.py b/sv/tests/test_fiches_contact_add.py index 48f17d72..09db45bb 100644 --- a/sv/tests/test_fiches_contact_add.py +++ b/sv/tests/test_fiches_contact_add.py @@ -1,9 +1,12 @@ import pytest +from django.contrib.contenttypes.models import ContentType +from django.utils.http import urlencode from playwright.sync_api import expect from model_bakery import baker from django.urls import reverse -from core.models import Contact, Structure, Agent +from core.models import Contact, Structure, Agent, Visibilite +from sv.models import FicheDetection @pytest.fixture @@ -48,6 +51,18 @@ def test_add_contact_form(live_server, page, fiche_variable, mocked_authentifica expect(page.get_by_role("button", name="Rechercher")).to_be_visible() +def test_cant_access_add_contact_form_if_fiche_brouillon(live_server, page, fiche_variable): + fiche = fiche_variable() + fiche.visibilite = Visibilite.BROUILLON + fiche.numero = None + fiche.save() + content_type = ContentType.objects.get_for_model(fiche) + page.goto( + f"{live_server.url}/{reverse('contact-add-form')}?fiche_id={fiche.id}&content_type_id={content_type.id}&next={fiche.get_absolute_url()}" + ) + expect(page.get_by_text("Action impossible car la fiche est en brouillon")).to_be_visible() + + def test_add_contact_form_back_to_fiche(live_server, page, fiche_variable): """Test le lien de retour vers la fiche""" fiche = fiche_variable() @@ -60,25 +75,29 @@ def test_add_contact_form_back_to_fiche(live_server, page, fiche_variable): @pytest.mark.django_db -def test_structure_list(client): +def test_structure_list(live_server, page): """Test que la liste des structures soit bien dans le contexte du formulaire d'ajout de contact""" Contact.objects.all().delete() Agent.objects.all().delete() Structure.objects.all().delete() - for _ in range(0, 3): - structure = baker.make(Structure) + for i in range(0, 3): + structure = baker.make(Structure, libelle=f"Structure {i+1}") agent = baker.make(Agent, structure=structure) user = agent.user user.is_active = True user.save() baker.make(Contact, email="foo@example.com", agent=agent) + assert Structure.objects.count() == 3 - url = reverse("contact-add-form") - response = client.get(url) - form = response.context["form"] - form_structures = form.fields["structure"].queryset - assert form_structures.count() == 3 - assert all(structure in form_structures for structure in Structure.objects.all()) + fiche = FicheDetection.objects.create(visibilite=Visibilite.LOCAL, createur=Structure.objects.first()) + url = f"{reverse('contact-add-form')}?{urlencode({'fiche_id': fiche.pk, 'content_type_id': ContentType.objects.get_for_model(fiche).id, 'next': fiche.get_absolute_url()})}" + page.goto(f"{live_server.url}{url}") + + page.query_selector(".choices").click() + page.wait_for_selector("input:focus", state="visible", timeout=2_000) + page.locator("*:focus").fill("Structure") + for i in range(0, 3): + expect(page.get_by_role("option", name=f"Structure {i+1}", exact=True)).to_be_visible() def test_add_contact_form_select_structure(live_server, page, fiche_variable, contacts, choice_js_fill): @@ -97,6 +116,30 @@ def test_add_contact_form_select_structure(live_server, page, fiche_variable, co expect(page.get_by_text(f"{contact2.agent.nom}")).to_contain_text(f"{contact2.agent.nom} {contact2.agent.prenom}") +def test_cant_add_contact_form_select_structure_if_fiche_brouillon(client, fiche_variable): + """Test que si une fiche est en visibilité brouillon, on ne peut pas afficher des contacts dans le formulaire de sélection suite à la selection d'une structure""" + fiche = fiche_variable() + fiche.visibilite = Visibilite.BROUILLON + fiche.numero = None + fiche.save() + + response = client.post( + reverse("contact-add-form"), + data={ + "fiche_id": fiche.id, + "structure": fiche.createur, + "next": fiche.get_absolute_url(), + "content_type_id": ContentType.objects.get_for_model(fiche).id, + }, + follow=True, + ) + + messages = list(response.context["messages"]) + assert len(messages) == 1 + assert messages[0].level_tag == "error" + assert str(messages[0]) == "Action impossible car la fiche est en brouillon" + + def test_add_contact_to_a_fiche(live_server, page, fiche_variable, contacts, choice_js_fill): """Test l'ajout d'un contact à une fiche de détection""" fiche = fiche_variable() @@ -190,3 +233,51 @@ def test_add_contact_form_back_to_fiche_after_error_message(live_server, page, f page.get_by_role("link", name="Retour à la fiche").click() expect(page).to_have_url(f"{live_server.url}{fiche.get_absolute_url()}") + + +def test_cant_add_contact_if_fiche_brouillon(client, fiche_variable, contact): + fiche = fiche_variable() + fiche.visibilite = Visibilite.BROUILLON + fiche.numero = None + fiche.save() + + response = client.post( + reverse("contact-add-form-select-agents"), + data={ + "structure": contact.agent.structure.id, + "contacts": [contact.id], + "content_type_id": ContentType.objects.get_for_model(fiche).id, + "fiche_id": fiche.id, + "next": fiche.get_absolute_url(), + }, + follow=True, + ) + + messages = list(response.context["messages"]) + assert len(messages) == 1 + assert messages[0].level_tag == "error" + assert str(messages[0]) == "Action impossible car la fiche est en brouillon" + + +def test_cant_delete_contact_if_fiche_brouillon(client, fiche_variable, contact): + fiche = fiche_variable() + fiche.visibilite = Visibilite.BROUILLON + fiche.numero = None + fiche.save() + fiche.contacts.set([contact]) + + response = client.post( + reverse("contact-delete"), + data={ + "content_type_pk": ContentType.objects.get_for_model(fiche).id, + "fiche_pk": fiche.id, + "pk": contact.id, + "next": fiche.get_absolute_url(), + }, + follow=True, + ) + + messages = list(response.context["messages"]) + assert len(messages) == 1 + assert messages[0].level_tag == "error" + assert str(messages[0]) == "Action impossible car la fiche est en brouillon" diff --git a/sv/tests/test_fiches_documents.py b/sv/tests/test_fiches_documents.py index 17f74a2f..00b05143 100644 --- a/sv/tests/test_fiches_documents.py +++ b/sv/tests/test_fiches_documents.py @@ -1,7 +1,10 @@ +from django.contrib.contenttypes.models import ContentType +from django.core.files.uploadedfile import SimpleUploadedFile +from django.urls import reverse from model_bakery import baker from playwright.sync_api import Page, expect -from core.models import Structure +from core.models import Structure, Document, Visibilite from django.contrib.auth import get_user_model User = get_user_model() @@ -145,3 +148,78 @@ def test_can_filter_documents_by_unit_on_fiche_detection(live_server, page: Page expect(page.get_by_text("Test document", exact=True)).to_be_visible() expect(page.get_by_text("Ma carto", exact=True)).not_to_be_visible() + + +def test_cant_add_document_if_brouillon(client, fiche_variable): + fiche = fiche_variable() + fiche.visibilite = Visibilite.BROUILLON + fiche.numero = None + fiche.save() + test_file = SimpleUploadedFile(name="test.pdf", content=b"contenu du fichier test", content_type="application/pdf") + data = { + "nom": "Un fichier test", + "document_type": Document.TypeDocument.AUTRE, + "description": "Description du fichier test", + "file": test_file, + "content_type": ContentType.objects.get_for_model(fiche).pk, + "object_id": fiche.id, + "next": fiche.get_absolute_url(), + } + + response = client.post( + reverse("document-upload"), + data=data, + format="multipart", + follow=True, + ) + + assert response.status_code == 200 + messages = list(response.context["messages"]) + assert len(messages) == 1 + assert messages[0].level_tag == "error" + assert str(messages[0]) == "Action impossible car la fiche est en brouillon" + + +def test_cant_delete_document_if_brouillon(client, fiche_variable, document_recipe): + fiche = fiche_variable() + fiche.visibilite = Visibilite.BROUILLON + fiche.numero = None + fiche.save() + document = document_recipe().make(nom="Test document", description="un document") + fiche.documents.set([document]) + + response = client.post( + reverse("document-delete", kwargs={"pk": document.pk}), + data={"next": fiche.get_absolute_url()}, + follow=True, + ) + document.refresh_from_db() + + assert document.is_deleted is False + messages = list(response.context["messages"]) + assert len(messages) == 1 + assert messages[0].level_tag == "error" + assert str(messages[0]) == "Action impossible car la fiche est en brouillon" + + +def test_cant_edit_document_if_brouillon(client, fiche_variable, document_recipe): + fiche = fiche_variable() + fiche.visibilite = Visibilite.BROUILLON + fiche.numero = None + fiche.save() + document = document_recipe().make(nom="Test document", description="un document") + fiche.documents.set([document]) + + response = client.post( + reverse("document-update", kwargs={"pk": document.pk}), + data={"next": fiche.get_absolute_url(), "nom": "Nouveau nom", "description": "Nouvelle description"}, + follow=True, + ) + document.refresh_from_db() + + assert document.nom == "Test document" + assert document.description == "un document" + messages = list(response.context["messages"]) + assert len(messages) == 1 + assert messages[0].level_tag == "error" + assert str(messages[0]) == "Action impossible car la fiche est en brouillon" diff --git a/sv/tests/test_fiches_message.py b/sv/tests/test_fiches_message.py index 60c31428..fd81b40f 100644 --- a/sv/tests/test_fiches_message.py +++ b/sv/tests/test_fiches_message.py @@ -2,7 +2,7 @@ from django.contrib.auth import get_user_model from model_bakery import baker from playwright.sync_api import Page, expect -from core.models import Message, Contact, Agent, Structure +from core.models import Message, Contact, Agent, Structure, Visibilite User = get_user_model() @@ -338,3 +338,70 @@ def test_cant_only_pick_structure_with_email( choice_js_fill(page, ".choices__input--cloned:first-of-type", "FOO", "FOO") choice_js_cant_pick(page, ".choices__input--cloned:first-of-type", "BAR", "BAR") + + +@pytest.mark.parametrize("message_type, message_label", Message.MESSAGE_TYPE_CHOICES) +def test_cant_access_add_message_form_if_fiche_brouillon(client, fiche_variable, message_type, message_label): + fiche = fiche_variable() + fiche.visibilite = Visibilite.BROUILLON + fiche.numero = None + fiche.save() + + response = client.get(fiche.get_add_message_url(message_type), follow=True) + + messages = list(response.context["messages"]) + assert len(messages) == 1 + assert messages[0].level_tag == "error" + assert str(messages[0]) == "Action impossible car la fiche est en brouillon" + + +@pytest.mark.parametrize("message_type, message_label", Message.MESSAGE_TYPE_CHOICES) +def test_cant_add_message_if_fiche_brouillon( + client, fiche_variable, mocked_authentification_user, with_active_contact, message_type, message_label +): + fiche = fiche_variable() + fiche.visibilite = Visibilite.BROUILLON + fiche.numero = None + fiche.save() + + response = client.post( + fiche.get_add_message_url(message_type), + data={ + "sender": Contact.objects.get(agent=mocked_authentification_user.agent).pk, + "recipients": [with_active_contact.pk], + "message_type": message_type, + "content": "My content \n with a line return", + }, + follow=True, + ) + + messages = list(response.context["messages"]) + assert len(messages) == 1 + assert messages[0].level_tag == "error" + assert str(messages[0]) == "Action impossible car la fiche est en brouillon" + + +@pytest.mark.parametrize("message_type, message_label", Message.MESSAGE_TYPE_CHOICES) +def test_cant_access_message_details_if_fiche_brouillon( + client, fiche_variable, mocked_authentification_user, with_active_contact, message_type, message_label +): + fiche = fiche_variable() + fiche.visibilite = Visibilite.BROUILLON + fiche.numero = None + fiche.save() + message = Message.objects.create( + message_type=message_type, + title="un titre", + content="un contenu", + sender=Contact.objects.get(agent=mocked_authentification_user.agent), + content_object=fiche, + ) + recipient_contact = Contact.objects.get(agent=with_active_contact) + message.recipients.set([recipient_contact]) + + response = client.get(message.get_absolute_url(), follow=True) + + messages = list(response.context["messages"]) + assert len(messages) == 1 + assert messages[0].level_tag == "error" + assert str(messages[0]) == "Action impossible car la fiche est en brouillon" diff --git a/sv/tests/test_fiches_structure_add.py b/sv/tests/test_fiches_structure_add.py index 114ef6fa..af64b402 100644 --- a/sv/tests/test_fiches_structure_add.py +++ b/sv/tests/test_fiches_structure_add.py @@ -1,7 +1,9 @@ import pytest +from django.contrib.contenttypes.models import ContentType +from django.urls import reverse from playwright.sync_api import expect from model_bakery import baker -from core.models import Structure, Contact +from core.models import Structure, Contact, Visibilite @pytest.fixture @@ -157,3 +159,71 @@ def test_add_structure_form_back_to_fiche_after_select_structure_niveau1( page.get_by_role("link", name="Retour à la fiche").click() expect(page).to_have_url(f"{live_server.url}{fiche.get_absolute_url()}") + + +@pytest.mark.django_db +def test_cant_access_structure_selection_add_form_if_fiche_brouillon(live_server, page, fiche_variable): + fiche = fiche_variable() + fiche.visibilite = Visibilite.BROUILLON + fiche.numero = None + fiche.save() + content_type = ContentType.objects.get_for_model(fiche) + page.goto( + f"{live_server.url}/{reverse('structure-selection-add-form')}?fiche_id={fiche.id}&content_type_id={content_type.id}&next={fiche.get_absolute_url()}" + ) + expect(page.get_by_text("Action impossible car la fiche est en brouillon")).to_be_visible() + + +@pytest.mark.django_db +def test_cant_post_structure_selection_add_form_if_fiche_brouillon( + client, fiche_variable, mocked_authentification_user +): + fiche = fiche_variable() + fiche.visibilite = Visibilite.BROUILLON + fiche.numero = None + fiche.save() + + response = client.post( + reverse("structure-selection-add-form"), + data={ + "fiche_id": fiche.id, + "next": fiche.get_absolute_url(), + "content_type_id": ContentType.objects.get_for_model(fiche).id, + "structure_niveau1": fiche.createur, + }, + follow=True, + ) + + messages = list(response.context["messages"]) + assert len(messages) == 1 + assert messages[0].level_tag == "error" + assert str(messages[0]) == "Action impossible car la fiche est en brouillon" + + +@pytest.mark.django_db +def test_cant_post_structure_add_form_if_fiche_brouillon(client, fiche_variable, mocked_authentification_user): + fiche = fiche_variable() + fiche.visibilite = Visibilite.BROUILLON + fiche.numero = None + fiche.save() + structure = Structure.objects.create( + niveau1=fiche.createur, niveau2="une autre structure", libelle="une autre structure" + ) + contact = Contact.objects.create(structure=structure) + + response = client.post( + reverse("structure-add"), + data={ + "content_type_id": ContentType.objects.get_for_model(fiche).id, + "fiche_id": fiche.id, + "next": fiche.get_absolute_url(), + "structure_selected": fiche.createur, + "contacts": [contact], + }, + follow=True, + ) + + messages = list(response.context["messages"]) + assert len(messages) == 1 + assert messages[0].level_tag == "error" + assert str(messages[0]) == "Action impossible car la fiche est en brouillon" diff --git a/sv/views.py b/sv/views.py index bac77102..c2d9adad 100644 --- a/sv/views.py +++ b/sv/views.py @@ -733,7 +733,7 @@ def form_invalid(self, form): class RattachementDetectionView(PreventActionIfVisibiliteBrouillonMixin, FormView): form_class = RattachementDetectionForm - def get_object(self): + def get_fiche_object(self): self.fiche_detection = FicheDetection.objects.get(pk=self.kwargs.get("pk")) return self.fiche_detection