Skip to content

Commit

Permalink
Limiter les résultats de l'export pour l'utilisateur
Browse files Browse the repository at this point in the history
Le but de ce commit est de limiter les fiches exportables par un
utilisateur/utilisatrice donné à ce qui est effectivement visible dans
l'outil. Ainsi la prise en compte des règles de visibilité empéche un
agent de voir dans l'export des fiches qu'il ne verrait pas dans
l'outil.

Au cours de mes tests je me suis rendu compte que l'export était
relativement lent même pour une base relativement petite (~800 lignes
dans le fichier CSV). J'ai optimisé et rendu les tests plus réalistes
avec l'utilisation de _fill_optional.
  • Loading branch information
Anto59290 committed Oct 28, 2024
1 parent c5655e4 commit e88b72e
Show file tree
Hide file tree
Showing 4 changed files with 45 additions and 24 deletions.
20 changes: 15 additions & 5 deletions sv/export.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,19 @@ class FicheDetectionExport:
def _clean_field_name(self, field):
return field.replace("_", " ").title()

def get_queryset(self):
queryset = FicheDetection.objects.select_related("etat", "numero")
queryset = queryset.prefetch_related("lieux", "lieux__prelevements", "lieux__prelevements__structure_preleveur")
def get_queryset(self, user):
queryset = FicheDetection.objects.all().get_fiches_user_can_view(user).optimized_for_details()
queryset = queryset.prefetch_related(
"lieux",
"lieux__prelevements",
"lieux__departement",
"lieux__prelevements__structure_preleveur",
"lieux__prelevements__espece_echantillon",
"lieux__prelevements__matrice_prelevee",
"lieux__prelevements__site_inspection",
"lieux__prelevements__laboratoire_agree",
"lieux__prelevements__laboratoire_confirmation_officielle",
)
return queryset

def get_fieldnames(self):
Expand Down Expand Up @@ -86,8 +96,8 @@ def get_lines_from_instance(self, fiche_detection):
else:
yield self.get_fiche_data(fiche_detection)

def export(self, stream):
queryset = self.get_queryset()
def export(self, stream, user):
queryset = self.get_queryset(user)
writer = csv.DictWriter(stream, fieldnames=self.get_fieldnames())
writer.writeheader()

Expand Down
5 changes: 5 additions & 0 deletions sv/managers.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,5 +55,10 @@ def with_first_region_name(self):
def optimized_for_list(self):
return self.select_related("etat", "numero", "organisme_nuisible", "createur")

def optimized_for_details(self):
return self.select_related(
"statut_reglementaire", "etat", "numero", "contexte", "createur", "statut_evenement", "organisme_nuisible"
)

def get_all_not_in_fiche_zone_delimitee(self):
return self.filter(zone_infestee__isnull=True, hors_zone_infestee__isnull=True).order_by("numero")
38 changes: 23 additions & 15 deletions sv/tests/test_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@
import pytest
from model_bakery import baker
from unittest import mock

from core.models import Visibilite
from sv.export import FicheDetectionExport
from sv.models import Etat, FicheDetection, NumeroFiche, Lieu, Prelevement, StructurePreleveur
import datetime


def _create_fiche_with_lieu_and_prelevement(numero=123):
def _create_fiche_with_lieu_and_prelevement(numero=123, fill_optional=False):
etat = Etat.objects.get(id=Etat.get_etat_initial())
numero = NumeroFiche.objects.create(annee=2024, numero=numero)
mocked = datetime.datetime(2024, 8, 1, 0, 0, 0, tzinfo=datetime.timezone.utc)
Expand All @@ -24,6 +26,10 @@ def _create_fiche_with_lieu_and_prelevement(numero=123):
mesures_consignation="MC",
mesures_phytosanitaires="MP",
mesures_surveillance_specifique="MSP",
visibilite=Visibilite.NATIONAL,
hors_zone_infestee=None,
zone_infestee=None,
_fill_optional=fill_optional,
)
lieu = baker.make(
Lieu,
Expand All @@ -36,6 +42,7 @@ def _create_fiche_with_lieu_and_prelevement(numero=123):
adresse_lieu_dit="L'angle",
commune="Saint-Pierre",
code_insee="12345",
_fill_optional=fill_optional,
)
structure = StructurePreleveur.objects.create(nom="My structure")
baker.make(
Expand All @@ -47,14 +54,15 @@ def _create_fiche_with_lieu_and_prelevement(numero=123):
numero_phytopass="Phyto123",
resultat="detecte",
structure_preleveur=structure,
_fill_optional=fill_optional,
)


@pytest.mark.django_db
def test_export_fiche_detection_content():
def test_export_fiche_detection_content(mocked_authentification_user):
stream = StringIO()
_create_fiche_with_lieu_and_prelevement()
FicheDetectionExport().export(stream=stream)
FicheDetectionExport().export(stream=stream, user=mocked_authentification_user)

stream.seek(0)
lines = stream.readlines()
Expand Down Expand Up @@ -104,37 +112,37 @@ def test_export_fiche_detection_content():


@pytest.mark.django_db
def test_export_fiche_detection_performance(django_assert_num_queries):
def test_export_fiche_detection_performance(django_assert_num_queries, mocked_authentification_user):
stream = StringIO()
_create_fiche_with_lieu_and_prelevement()
_create_fiche_with_lieu_and_prelevement(fill_optional=True)

with django_assert_num_queries(4):
FicheDetectionExport().export(stream=stream)
with django_assert_num_queries(10):
FicheDetectionExport().export(stream=stream, user=mocked_authentification_user)

stream.seek(0)
lines = stream.readlines()
assert len(lines) == 2

stream = StringIO()
_create_fiche_with_lieu_and_prelevement(numero=4)
_create_fiche_with_lieu_and_prelevement(numero=5)
_create_fiche_with_lieu_and_prelevement(numero=6)
with django_assert_num_queries(4):
FicheDetectionExport().export(stream=stream)
_create_fiche_with_lieu_and_prelevement(numero=4, fill_optional=True)
_create_fiche_with_lieu_and_prelevement(numero=5, fill_optional=True)
_create_fiche_with_lieu_and_prelevement(numero=6, fill_optional=True)
with django_assert_num_queries(10):
FicheDetectionExport().export(stream=stream, user=mocked_authentification_user)

stream.seek(0)
lines = stream.readlines()
assert len(lines) == 5


@pytest.mark.django_db
def test_export_fiche_detection_numbers_of_lines(django_assert_num_queries):
def test_export_fiche_detection_numbers_of_lines(django_assert_num_queries, mocked_authentification_user):
stream = StringIO()
etat = Etat.objects.get(id=Etat.get_etat_initial())
numero = NumeroFiche.objects.create(annee=2024, numero=123)
mocked = datetime.datetime(2024, 8, 1, 0, 0, 0, tzinfo=datetime.timezone.utc)
with mock.patch("django.utils.timezone.now", mock.Mock(return_value=mocked)):
fiche = baker.make(FicheDetection, etat=etat, numero=numero)
fiche = baker.make(FicheDetection, etat=etat, numero=numero, visibilite=Visibilite.NATIONAL)
_lieu_without_prelevement = baker.make(Lieu, fiche_detection=fiche)
_lieu_without_prelevement_2 = baker.make(Lieu, fiche_detection=fiche)
lieu = baker.make(
Expand All @@ -152,7 +160,7 @@ def test_export_fiche_detection_numbers_of_lines(django_assert_num_queries):
baker.make(Prelevement, lieu=lieu)
baker.make(Prelevement, lieu=lieu)

FicheDetectionExport().export(stream=stream)
FicheDetectionExport().export(stream=stream, user=mocked_authentification_user)

stream.seek(0)
lines = stream.readlines()
Expand Down
6 changes: 2 additions & 4 deletions sv/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,7 @@ class FicheDetectionDetailView(
DetailView,
):
model = FicheDetection
queryset = FicheDetection.objects.select_related(
"statut_reglementaire", "etat", "numero", "contexte", "createur", "statut_evenement", "organisme_nuisible"
)
queryset = FicheDetection.objects.all().optimized_for_details()

def get_object(self, queryset=None):
if hasattr(self, "object"):
Expand Down Expand Up @@ -580,7 +578,7 @@ class FicheDetectionExportView(View):

def post(self, request):
response = HttpResponse(content_type="text/csv")
FicheDetectionExport().export(stream=response)
FicheDetectionExport().export(stream=response, user=request.user)
response["Content-Disposition"] = "attachment; filename=export_fiche_detection.csv"
return response

Expand Down

0 comments on commit e88b72e

Please sign in to comment.