diff --git a/sv/factories.py b/sv/factories.py index a626222b..d81f347a 100644 --- a/sv/factories.py +++ b/sv/factories.py @@ -17,6 +17,7 @@ SITES_INSPECTION, DEPARTEMENTS, REGIONS, + POSITION_CHAINE_DISTRIBUTION, ) from .models import ( Prelevement, @@ -36,6 +37,7 @@ Contexte, SiteInspection, Region, + PositionChaineDistribution, ) fake = Faker() @@ -187,6 +189,26 @@ def build_with_some_related_objects_saved(cls, *args, **kwargs): espece = EspeceEchantillonFactory() return cls.build(matrice_prelevee=matrice_prelevee, espece_echantillon=espece, *args, **kwargs) + @classmethod + def create_minimal(cls, **kwargs): + return cls.create( + numero_echantillon="", + date_prelevement=None, + matrice_prelevee=None, + espece_echantillon=None, + laboratoire=None, + numero_rapport_inspection="", + **kwargs, + ) + + +class PositionChaineDistributionFactory(DjangoModelFactory): + class Meta: + model = PositionChaineDistribution + django_get_or_create = ("libelle",) + + libelle = factory.lazy_attribute(lambda _: random.choice(POSITION_CHAINE_DISTRIBUTION)) + class LieuFactory(DjangoModelFactory): class Meta: @@ -210,6 +232,29 @@ class Meta: siret_etablissement = factory.Faker("numerify", text="##############") code_inupp_etablissement = factory.Faker("numerify", text="#######") site_inspection = factory.SubFactory("sv.factories.SiteInspectionFactory") + position_chaine_distribution_etablissement = factory.SubFactory("sv.factories.PositionChaineDistributionFactory") + + @classmethod + def create_minimal(cls, **kwargs): + return cls.create( + wgs84_longitude=None, + wgs84_latitude=None, + adresse_lieu_dit="", + commune="", + code_insee="", + departement=None, + is_etablissement=False, + nom_etablissement="", + activite_etablissement="", + pays_etablissement="", + raison_sociale_etablissement="", + adresse_etablissement="", + siret_etablissement="", + code_inupp_etablissement="", + site_inspection=None, + position_chaine_distribution_etablissement=None, + **kwargs, + ) class FicheDetectionFactory(DjangoModelFactory): diff --git a/sv/tests/test_evenement_details.py b/sv/tests/test_evenement_details.py index 46beb9b5..c72c2df0 100644 --- a/sv/tests/test_evenement_details.py +++ b/sv/tests/test_evenement_details.py @@ -335,3 +335,33 @@ def test_show_details_synthese_switch(live_server, page: Page, etat: Etat): page.goto(f"{live_server.url}{evenement.get_absolute_url()}") expect(page.get_by_text("Détail")).to_be_visible() expect(page.get_by_text("Synthèse")).to_be_visible() + + +def test_first_detection_by_number_is_selected_by_default(live_server, page: Page): + """Test que la première fiche de détection (par numéro et non par id) est sélectionnée par défaut.""" + evenement = EvenementFactory() + + FicheDetectionFactory(evenement=evenement, numero_detection=f"{evenement.numero}.3") + FicheDetectionFactory(evenement=evenement, numero_detection=f"{evenement.numero}.2") + detection_3 = FicheDetectionFactory(evenement=evenement, numero_detection=f"{evenement.numero}.1") + + page.goto(f"{live_server.url}{evenement.get_absolute_url()}") + + expect(page.get_by_role("tab", name=detection_3.numero_detection)).to_have_class( + re.compile(r"(^|\s)selected($|\s)") + ) + + +def test_detections_are_order_by_detection_number_not_by_id(live_server, page: Page): + """Test que les fiches détections sont triées par numéro et non par id.""" + evenement = EvenementFactory() + detection_10 = FicheDetectionFactory(evenement=evenement, numero_detection=f"{evenement.numero}.10") + detection_3 = FicheDetectionFactory(evenement=evenement, numero_detection=f"{evenement.numero}.3") + detection_2 = FicheDetectionFactory(evenement=evenement, numero_detection=f"{evenement.numero}.2") + detection_1 = FicheDetectionFactory(evenement=evenement, numero_detection=f"{evenement.numero}.1") + + page.goto(f"{live_server.url}{evenement.get_absolute_url()}") + + expect(page.locator("#tabpanel-detection-panel ul > li")).to_contain_text( + [detection_1.numero, detection_2.numero, detection_3.numero, detection_10.numero] + ) diff --git a/sv/tests/test_evenement_performance_details.py b/sv/tests/test_evenement_performance_details.py index 9a0ba38f..ea9600c7 100644 --- a/sv/tests/test_evenement_performance_details.py +++ b/sv/tests/test_evenement_performance_details.py @@ -110,7 +110,7 @@ def test_evenement_performances_with_prelevement(client, django_assert_num_queri PrelevementFactory.create_batch(3, lieu__fiche_detection=fiche_detection) - with django_assert_num_queries(BASE_NUM_QUERIES + 12): + with django_assert_num_queries(BASE_NUM_QUERIES + 13): client.get(evenement.get_absolute_url()) diff --git a/sv/tests/test_fichedetection_detail.py b/sv/tests/test_fichedetection_detail.py index 9e2519ce..0d66d528 100644 --- a/sv/tests/test_fichedetection_detail.py +++ b/sv/tests/test_fichedetection_detail.py @@ -1,17 +1,13 @@ -from model_bakery import baker from playwright.sync_api import expect from sv.factories import LieuFactory, EvenementFactory, FicheDetectionFactory, PrelevementFactory -from sv.models import Lieu, Prelevement +from sv.models import Prelevement -def test_lieu_details(live_server, page, fiche_detection): +def test_lieu_details(live_server, page): "Test que les détails d'un lieu s'affichent correctement dans la modale" - lieu = baker.make(Lieu, fiche_detection=fiche_detection, _fill_optional=True, is_etablissement=True) - evenement = fiche_detection.evenement - evenement.createur = fiche_detection.createur - evenement.save() - page.goto(f"{live_server.url}{fiche_detection.get_absolute_url()}") + lieu = LieuFactory(is_etablissement=True) + page.goto(f"{live_server.url}{lieu.fiche_detection.get_absolute_url()}") page.get_by_role("button", name=f"Consulter le détail du lieu {lieu.nom}").click() expect(page.get_by_role("heading", name=lieu.nom)).to_be_visible() expect(page.locator(f"#fr-modal-lieu-{lieu.pk}").get_by_text("Adresse ou lieu-dit")).to_be_visible() @@ -48,14 +44,10 @@ def test_lieu_details(live_server, page, fiche_detection): ) -def test_lieu_details_second_lieu(live_server, page, fiche_detection): +def test_lieu_details_second_lieu(live_server, page): "Test que si je clique sur le bouton 'Consulter le détail du lieu' d'un deuxième lieu, les détails de ce lieu s'affichent correctement dans la modale" - baker.make(Lieu, fiche_detection=fiche_detection, _fill_optional=True) - evenement = fiche_detection.evenement - evenement.createur = fiche_detection.createur - evenement.save() - lieu2 = baker.make(Lieu, fiche_detection=fiche_detection, _fill_optional=True) - page.goto(f"{live_server.url}{fiche_detection.get_absolute_url()}") + _, lieu2 = LieuFactory.create_batch(2) + page.goto(f"{live_server.url}{lieu2.fiche_detection.get_absolute_url()}") page.get_by_role("button", name=f"Consulter le détail du lieu {lieu2.nom}").click() expect(page.get_by_role("heading", name=lieu2.nom)).to_be_visible() expect(page.get_by_test_id(f"lieu-{lieu2.pk}-adresse")).to_contain_text(lieu2.adresse_lieu_dit) @@ -93,13 +85,10 @@ def test_lieu_details_of_second_detection_when_first_detection_has_lieu(live_ser expect(page.locator(".fr-modal--opened")).to_contain_text(str(lieu.wgs84_longitude).replace(".", ",")) -def test_lieu_details_with_no_data(live_server, page, fiche_detection): +def test_lieu_details_with_no_data(live_server, page): "Test que les détails d'un lieu s'affichent correctement dans la modale lorsqu'il n'y a pas de données (sauf pour les champs obligatoires)" - lieu = baker.make(Lieu, fiche_detection=fiche_detection) - evenement = fiche_detection.evenement - evenement.createur = fiche_detection.createur - evenement.save() - page.goto(f"{live_server.url}{fiche_detection.get_absolute_url()}") + lieu = LieuFactory.create_minimal() + page.goto(f"{live_server.url}{lieu.fiche_detection.get_absolute_url()}") page.get_by_role("button", name=f"Consulter le détail du lieu {lieu.nom}").click() expect(page.get_by_test_id(f"lieu-{lieu.pk}-adresse")).to_contain_text("nc.") expect(page.get_by_test_id(f"lieu-{lieu.pk}-commune")).to_contain_text(lieu.commune) @@ -121,14 +110,11 @@ def test_prelevement_card(live_server, page): expect(page.locator(".prelevement").get_by_text("DÉTECTÉ")).to_be_visible() -def test_prelevement_non_officiel_details_with_no_data(live_server, page, fiche_detection): +def test_prelevement_non_officiel_details_with_no_data(live_server, page): "Test que les détails d'un prélèvement non officiel s'affichent correctement dans la modale lorsqu'il n'y a pas de données (sauf pour les champs obligatoires)" - lieu = baker.make(Lieu, fiche_detection=fiche_detection) - evenement = fiche_detection.evenement - evenement.createur = fiche_detection.createur - evenement.save() - prelevement = baker.make(Prelevement, lieu=lieu) - page.goto(f"{live_server.url}{fiche_detection.get_absolute_url()}") + prelevement = PrelevementFactory.create_minimal(is_officiel=False) + + page.goto(f"{live_server.url}{prelevement.lieu.fiche_detection.get_absolute_url()}") page.get_by_role("button", name=f"Consulter le détail du prélèvement {prelevement.numero_echantillon}").click() expect(page.get_by_test_id(f"prelevement-{prelevement.pk}-is-officiel")).to_contain_text( "oui" if prelevement.is_officiel else "non" diff --git a/sv/views.py b/sv/views.py index 61fa93ea..55b19b40 100644 --- a/sv/views.py +++ b/sv/views.py @@ -4,7 +4,7 @@ from django.contrib.messages.views import SuccessMessageMixin from django.core.exceptions import PermissionDenied, ValidationError from django.db import transaction -from django.db.models import Prefetch, Min +from django.db.models import Prefetch from django.http import HttpResponseBadRequest, HttpResponseRedirect, HttpResponse, Http404 from django.shortcuts import redirect from django.urls import reverse @@ -111,10 +111,14 @@ class EvenementDetailView( def get_queryset(self): return ( Evenement.objects.all() - .annotate(first_detection_id=Min("detections__id")) .select_related("createur", "organisme_nuisible", "statut_reglementaire") .prefetch_related( - "detections", + Prefetch( + "detections", + queryset=FicheDetection.objects.all() + .with_numero_detection_only() + .order_by("numero_detection_only"), + ), "detections__createur", Prefetch( "detections__lieux__prelevements", @@ -179,7 +183,7 @@ def get_context_data(self, **kwargs): context["active_detection"] = ( int(self.request.GET.get("detection")) if self.request.GET.get("detection") - else self.object.first_detection_id # first_detection_id sera None s'il n'y a pas de détection + else getattr(self.object.detections.first(), "id", None) ) context["max_upload_size_mb"] = MAX_UPLOAD_SIZE_MEGABYTES return context