Skip to content

Commit

Permalink
Ajouter un suivi des versions pour la fiche détection
Browse files Browse the repository at this point in the history
Ce commit permet d'utiliser django-revision afin de suivre les
modifications réalisées sur une fiche détection et les objets
sous-jacents (Lieu, Prélèvement) afin de pouvoir afficher la date de
dernière modification ainsi que l'utilisateur / l'utilisatrice à
l'origine de cette modification.

Je n'ai pas utilisé un simple champ date et une FK vers le User car nous
savons que nous voulons être en mesure d'afficher un diff plus complet
avec les différentes dates de modification.

Autres modifications:
- Changement de format de date pour utiliser une vrai date plutôt qu'une
  chaine de caractères (tests en erreurs après l'ajout de Django
revision)
- Changement dans l'admin afin que l'on puisse charger l'admin des
  fiches
- Changement d'un test pour ne plus utiliser la factory qui a été
  améliorée entre temps et rendre le test OK
  • Loading branch information
Anto59290 committed Dec 31, 2024
1 parent 7ddad47 commit 297c664
Show file tree
Hide file tree
Showing 16 changed files with 242 additions and 16 deletions.
1 change: 1 addition & 0 deletions core/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ class StructureAdmin(admin.ModelAdmin):
class ContactAdmin(admin.ModelAdmin):
list_display = ("id", "email", "structure", "agent")
list_filter = ("structure", "agent")
search_fields = ("email", "agent__nom", "agent__prenom")


admin.site.register(Contact, ContactAdmin)
Expand Down
4 changes: 4 additions & 0 deletions core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ def __str__(self):
def is_in_structure(self, structure):
return self.structure == structure

@property
def agent_with_structure(self):
return f"{self.nom} {self.prenom} ({self.structure})"


class Structure(models.Model):
class Meta:
Expand Down
1 change: 1 addition & 0 deletions requirements.in
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@ faker
django-debug-toolbar
django-post_office
factory-boy
django-reversion
3 changes: 3 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ django==5.1.4
# django-debug-toolbar
# django-filter
# django-post-office
# django-reversion
# django-storages
# model-bakery
# mozilla-django-oidc
Expand All @@ -51,6 +52,8 @@ django-filter==24.3
# via -r requirements.in
django-post-office==3.9.1
# via -r requirements.in
django-reversion==5.1.0
# via -r requirements.in
django-storages[s3]==1.14.4
# via -r requirements.in
djhtml==3.0.7
Expand Down
2 changes: 2 additions & 0 deletions seves/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"account.apps.AccountConfig",
"django_filters",
"post_office",
"reversion",
]

MIDDLEWARE = [
Expand All @@ -69,6 +70,7 @@
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
"seves.middlewares.LoginRequiredMiddleware",
"reversion.middleware.RevisionMiddleware",
]

ROOT_URLCONF = "seves.urls"
Expand Down
9 changes: 8 additions & 1 deletion sv/admin.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from django.contrib import admin
from reversion.admin import VersionAdmin

from .models import (
OrganismeNuisible,
StatutReglementaire,
Expand All @@ -24,6 +26,12 @@

admin.site.site_header = "Administration de Sèves"


@admin.register(FicheDetection)
class FicheDetectionAdmin(VersionAdmin):
autocomplete_fields = ["contacts"]


admin.site.register(OrganismeNuisible)
admin.site.register(StatutReglementaire)
admin.site.register(Contexte)
Expand All @@ -39,7 +47,6 @@
admin.site.register(Laboratoire)
admin.site.register(Prelevement)
admin.site.register(StatutEvenement)
admin.site.register(FicheDetection)
admin.site.register(NumeroFiche)
admin.site.register(Etat)
admin.site.register(ZoneInfestee)
Expand Down
3 changes: 3 additions & 0 deletions sv/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@
class SvConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "sv"

def ready(self):
import sv.signals # noqa: F401
18 changes: 15 additions & 3 deletions sv/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from factory.django import DjangoModelFactory
from factory.fuzzy import FuzzyChoice
from core.models import Visibilite, Structure
from .constants import STATUTS_REGLEMENTAIRES, STRUCTURES_PRELEVEUSES
from .constants import STATUTS_REGLEMENTAIRES, STRUCTURES_PRELEVEUSES, STRUCTURE_EXPLOITANT
from .models import (
Prelevement,
Lieu,
Expand All @@ -17,6 +17,7 @@
StructurePreleveuse,
ZoneInfestee,
)
from datetime import datetime


class NumeroFicheFactory(DjangoModelFactory):
Expand Down Expand Up @@ -70,7 +71,9 @@ class Meta:
model = StructurePreleveuse
django_get_or_create = ("nom",)

nom = FuzzyChoice(STRUCTURES_PRELEVEUSES)
nom = factory.LazyAttribute(
lambda _: random.choice([s for s in STRUCTURES_PRELEVEUSES if s != STRUCTURE_EXPLOITANT])
)


class PrelevementFactory(DjangoModelFactory):
Expand All @@ -86,6 +89,12 @@ class Meta:
resultat = FuzzyChoice([choice[0] for choice in Prelevement.Resultat.choices])
numero_rapport_inspection = factory.Faker("numerify", text="#####")

@classmethod
def _create(cls, model_class, *args, **kwargs):
if kwargs["is_officiel"] is False:
kwargs["numero_rapport_inspection"] = ""
return super()._create(model_class, *args, **kwargs)


class LieuFactory(DjangoModelFactory):
class Meta:
Expand Down Expand Up @@ -141,7 +150,10 @@ def etat(self, create, extracted, **kwargs):
@factory.post_generation
def date_creation(self, create, extracted, **kwargs): # noqa: F811
if extracted and create:
self.date_creation = extracted
if isinstance(extracted, str):
self.date_creation = datetime.strptime(extracted, "%Y-%m-%d").date()
else:
self.date_creation = extracted
self.save()

@classmethod
Expand Down
42 changes: 38 additions & 4 deletions sv/models.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import re

from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ValidationError
from django.db import models, transaction
from django.core.validators import RegexValidator, MinValueValidator
from django.db.models import TextChoices, Q
from django.contrib.contenttypes.fields import GenericRelation
import datetime
import reversion

from django.urls import reverse
from reversion.models import Version

from core.mixins import (
AllowsSoftDeleteMixin,
Expand Down Expand Up @@ -146,6 +149,7 @@ def validate_wgs84_latitude(value):
raise ValidationError("La latitude doit être comprise entre -90° et +90°")


@reversion.register()
class Lieu(models.Model):
class Meta:
verbose_name = "Lieu"
Expand Down Expand Up @@ -225,6 +229,10 @@ class Meta:
def __str__(self):
return str(self.nom)

def save(self, *args, **kwargs):
with reversion.create_revision():
super().save(*args, **kwargs)


class StatutEtablissement(models.Model):
class Meta:
Expand Down Expand Up @@ -330,6 +338,7 @@ def validate_numero_rapport_inspection(value):
)


@reversion.register()
class Prelevement(models.Model):
class Resultat(models.TextChoices):
DETECTE = "detecte", "Détecté"
Expand Down Expand Up @@ -417,7 +426,8 @@ def clean(self):

def save(self, *args, **kwargs):
self.clean()
super().save(*args, **kwargs)
with reversion.create_revision():
super().save(*args, **kwargs)


class StatutEvenement(models.Model):
Expand Down Expand Up @@ -459,6 +469,7 @@ def __str__(self):
return self.libelle


@reversion.register()
class FicheDetection(
AllowsSoftDeleteMixin,
AllowACNotificationMixin,
Expand Down Expand Up @@ -548,9 +559,10 @@ class Meta:
objects = FicheDetectionManager()

def save(self, *args, **kwargs):
if not self.numero and self.visibilite == Visibilite.LOCAL:
self.numero = NumeroFiche.get_next_numero()
super().save(*args, **kwargs)
with reversion.create_revision():
if not self.numero and self.visibilite == Visibilite.LOCAL:
self.numero = NumeroFiche.get_next_numero()
super().save(*args, **kwargs)

def get_absolute_url(self):
return reverse("fiche-detection-vue-detaillee", kwargs={"pk": self.pk})
Expand All @@ -577,6 +589,28 @@ def get_fiche_zone_delimitee(self) -> "FicheZoneDelimitee | None":
if self.zone_infestee and self.zone_infestee.fiche_zone_delimitee:
return self.zone_infestee.fiche_zone_delimitee

@property
def latest_version(self):
lieux_ids = list(self.lieux.all().values_list("id", flat=True))
content_type = ContentType.objects.get_for_model(Lieu)
lieu_versions = Version.objects.select_related("revision").filter(
content_type=content_type, object_id__in=lieux_ids
)

prelevements = Prelevement.objects.filter(lieu__fiche_detection__pk=self.pk).values_list("id", flat=True)
content_type = ContentType.objects.get_for_model(Prelevement)
prelevement_versions = Version.objects.select_related("revision").filter(
content_type=content_type, object_id__in=list(prelevements)
)

instance_version = Version.objects.get_for_object(self).select_related("revision").first()

versions = list(lieu_versions) + list(prelevement_versions) + [instance_version]
versions = [v for v in versions if v]
if not versions:
return None
return max(versions, key=lambda obj: obj.revision.date_created)


class ZoneInfestee(models.Model):
class UnitesSurfaceInfesteeTotale(TextChoices):
Expand Down
54 changes: 54 additions & 0 deletions sv/signals.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
from django.db.models.signals import pre_delete, post_save
from django.dispatch import receiver
from django.db import transaction
import reversion

from sv.models import Lieu, Prelevement


@receiver(pre_delete, sender=Lieu)
def create_fiche_detection_version_on_lieu_delete(sender, instance: Lieu, **kwargs):
fiche = instance.fiche_detection

with transaction.atomic():
with reversion.create_revision():
reversion.set_comment(f"Le lieu '{instance.nom}' a été supprimé de la fiche")
reversion.add_to_revision(fiche)


@receiver(post_save, sender=Lieu)
def create_fiche_detection_version_on_lieu_add(sender, instance: Lieu, created, **kwargs):
if not created:
return
fiche = instance.fiche_detection

with transaction.atomic():
with reversion.create_revision():
reversion.set_comment(f"Le lieu '{instance.nom}' a été ajouté à la fiche")
reversion.add_to_revision(fiche)


@receiver(post_save, sender=Prelevement)
def create_fiche_detection_version_on_prelevement_add(sender, instance: Prelevement, created, **kwargs):
if not created:
return
fiche = instance.lieu.fiche_detection

with transaction.atomic():
with reversion.create_revision():
reversion.set_comment(
f"Le prélèvement pour le lieu '{instance.lieu.nom}' et la structure '{instance.structure_preleveuse}' a été ajouté à la fiche"
)
reversion.add_to_revision(fiche)


@receiver(pre_delete, sender=Prelevement)
def create_fiche_detection_version_on_prelevement_delete(sender, instance: Prelevement, **kwargs):
fiche = instance.lieu.fiche_detection

with transaction.atomic():
with reversion.create_revision():
reversion.set_comment(
f"Le prélèvement pour le lieu '{instance.lieu.nom}' et la structure '{instance.structure_preleveuse}' a été supprimé de la fiche"
)
reversion.add_to_revision(fiche)
8 changes: 8 additions & 0 deletions sv/templates/sv/fichedetection_detail.html
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@ <h1 class="fr-mb-0-5v">Fiche détection {% if fichedetection.numero %}n° {{ fi
{% if fiche_zone_delimitee %}
<a href="{{ fiche_zone_delimitee.get_absolute_url }}" class="fr-badge fr-badge--info fr-badge--no-icon">zone {{ fiche_zone_delimitee.numero }}</a>
{% endif %}
{% if latest_version %}
<div class="fr-mt-2v">
Dernière mise à jour le {{ latest_version.revision.date_created }}
{% if latest_version.revision.user %}
par {{ latest_version.revision.user.agent.agent_with_structure }}
{% endif %}
</div>
{% endif %}
</div>

<p>
Expand Down
4 changes: 2 additions & 2 deletions sv/tests/test_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def _create_fiche_with_lieu_and_prelevement(numero=123, fill_optional=False):
numero=numero,
numero_europhyt="EUROPHYT",
numero_rasff="RASFF",
date_premier_signalement="2024-01-01",
date_premier_signalement=datetime.date(2024, 1, 1),
commentaire="Mon commentaire",
mesures_conservatoires_immediates="MCI",
mesures_consignation="MC",
Expand All @@ -47,7 +47,7 @@ def _create_fiche_with_lieu_and_prelevement(numero=123, fill_optional=False):
Prelevement,
lieu=lieu,
numero_echantillon="Echantillon 3",
date_prelevement="2023-12-12",
date_prelevement=datetime.date(2023, 12, 12),
is_officiel=True,
resultat="detecte",
structure_preleveuse=structure,
Expand Down
6 changes: 3 additions & 3 deletions sv/tests/test_fichedetection_performances.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from core.models import Message, Document, Structure, Contact
from sv.models import Lieu, Prelevement

BASE_NUM_QUERIES = 15 # Please note a first call is made without assertion to warm up any possible cache
BASE_NUM_QUERIES = 18 # Please note a first call is made without assertion to warm up any possible cache


@pytest.mark.django_db
Expand Down Expand Up @@ -46,7 +46,7 @@ def test_fiche_detection_performances_with_lieux(client, django_assert_num_queri
client.get(fiche_detection.get_absolute_url())

baker.make(Lieu, fiche_detection=fiche_detection, _quantity=3, _fill_optional=True)
with django_assert_num_queries(BASE_NUM_QUERIES):
with django_assert_num_queries(BASE_NUM_QUERIES + 1):
client.get(fiche_detection.get_absolute_url())


Expand Down Expand Up @@ -80,7 +80,7 @@ def test_fiche_detection_performances_with_prelevement(client, django_assert_num
_fill_optional=True,
)

with django_assert_num_queries(BASE_NUM_QUERIES):
with django_assert_num_queries(BASE_NUM_QUERIES + 2):
client.get(fiche_detection.get_absolute_url())


Expand Down
Loading

0 comments on commit 297c664

Please sign in to comment.