Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Règles plus restrictives pour la suppression de certain objets afin de ne pas créer d'incohérences de données #3150

Merged
merged 1 commit into from
Aug 12, 2024

Conversation

rsebille
Copy link
Contributor

@rsebille rsebille commented Oct 5, 2023

Note

Je met tout le monde en relecteur mais il n'y a pas forcément besoin de tout relire, vous pouvez seulement vous concentrer sur un ou des domaines en particuliers.

Warning

Ces changements étant particulièrement lié aux aspects métiers, donc diffus dans l'équipe et assez impactant, n'hésitez pas à regarder les parties modifiées ET non-modifiées !

Il y a au final assez peu de modification évidente et qui, je pense, ferons consensus [1]. Le but est donc de lancer les réflexions à partir d'une première proposition.

tl;dr : C'est le moment de sortir vos meilleures pinaillage 😁.

Pourquoi ?

Cela fait maintenant plusieurs fois que des problèmes de données liés à des ForeignKey sont détectés [1], faisons donc l'inventaire de celles-ci ainsi que de leur clause on_delete.

On utilise quasiment que CASCADE et SET_NULL :

  • Certain CASCADE me semblant être un tropisme "la suppression n'intervient que dans l'admin et il y a un message de validation"
  • Les SET_NULL sont souvent là par simplicité au prix de l'auditabilité, a voir où on veux mettre le curseur au global et/ou en fonction des objets

[1] Je pense au CASCADE pour les MembershipAbstract.updated_by

Copy link
Contributor

@xavfernandez xavfernandez left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Super initiative.
Personnellement je suis plutôt dans l'équipe RESTRICT par défaut avec un commentaire dans le cas contraire 😅

itou/eligibility/models/geiq.py Outdated Show resolved Hide resolved
itou/approvals/models.py Outdated Show resolved Hide resolved
itou/approvals/models.py Show resolved Hide resolved
@@ -822,7 +822,7 @@ def displayed_choices_for_siae(siae):
verbose_name="mis à jour par",
null=True,
blank=True,
on_delete=models.SET_NULL,
on_delete=models.RESTRICT, # For traceability and accountability, the dates can be edited
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Si on veut aller à fond là-dedans (et tous les autres "traceability & accountability du même genre) je dirais:

  • ni nullable, ni blankable (pour tous ces created_by / updated_by ...)
  • RESTRICT ou CASCADE suivant le cas
  • créer ou réutiliser un utilisateur singleton (genre robot <machine@internal.com>) pour tous les cas où on fait des updates par script.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Vu que tout le monde semblait être plutôt d'avis d'être restrictif ( 🥁 💇 ) j'ai modifié les on_delete dans ce sens.
Pour les null/blank vu qu'il faut rester compatible avec les NULL existant faudra gérer ça au cas par cas.

related_name="%(class)ss_declared",
)
declared_by_siae = models.ForeignKey(
"siaes.Siae",
verbose_name="SIAE du déclarant",
null=True,
on_delete=models.SET_NULL,
on_delete=models.RESTRICT, # For traceability and accountability, people's organization can change
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 mais ça me donne envie de lancer un chantier "désactivation des SIAEs" plutot que de les supprimer, pour garder la traçabilité dans le temps. Non ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@@ -1080,7 +1080,7 @@ class CommonProlongation(models.Model):
verbose_name="prescripteur habilité qui a autorisé cette prolongation",
null=True,
blank=True,
on_delete=models.SET_NULL,
on_delete=models.RESTRICT, # For traceability and accountability
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pareil pour la désactivation des prescripteurs, je sens que retirer ces SET_NULL va faire péter les scripts de syncho, l'admin, etc.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Le support est prévenu. Mieux vaut une erreur que perdre de la données sur ces modèles critiques.

itou/common_apps/organizations/models.py Outdated Show resolved Hide resolved
itou/job_applications/models.py Outdated Show resolved Hide resolved
@rsebille rsebille force-pushed the rsebille/fk_on_delete branch from 894c94c to 2dad337 Compare February 22, 2024 13:59
@rsebille rsebille requested review from vjousse and leo-naeka and removed request for ikarius and tonial February 22, 2024 14:01
@rsebille rsebille marked this pull request as ready for review February 22, 2024 14:04
@rsebille rsebille force-pushed the rsebille/fk_on_delete branch from 2dad337 to 1ffb0f1 Compare February 26, 2024 18:06
Copy link
Contributor

@francoisfreitag francoisfreitag left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💯 👍 🥇

Mais pourquoi avons-nous attendu si longtemps pour merger cette PR ?

On ne veut pas vivre dans un monde aussi dangereux. Merci d’avoir ramené quelques gardes fous (et le petit coup de pression pour les intégrer 😁) 🙇

related_name="%(class)ss_declared",
)
declared_by_siae = models.ForeignKey(
"siaes.Siae",
verbose_name="SIAE du déclarant",
null=True,
on_delete=models.SET_NULL,
on_delete=models.RESTRICT, # For traceability and accountability, people's organization can change
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@@ -1080,7 +1080,7 @@ class CommonProlongation(models.Model):
verbose_name="prescripteur habilité qui a autorisé cette prolongation",
null=True,
blank=True,
on_delete=models.SET_NULL,
on_delete=models.RESTRICT, # For traceability and accountability
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Le support est prévenu. Mieux vaut une erreur que perdre de la données sur ces modèles critiques.

@@ -235,7 +235,7 @@ class GEIQAdministrativeCriteria(AbstractAdministrativeCriteria):
verbose_name="critère parent",
blank=True,
null=True,
on_delete=models.SET_NULL,
on_delete=models.RESTRICT, # Prevent promoting a criteria form child to parent
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
on_delete=models.RESTRICT, # Prevent promoting a criteria form child to parent
on_delete=models.RESTRICT, # Prevent promoting a criteria from child to parent

@@ -505,7 +505,7 @@ class JobApplication(xwf_models.WorkflowEnabled, models.Model):
sender = models.ForeignKey(
settings.AUTH_USER_MODEL,
verbose_name="utilisateur émetteur",
on_delete=models.SET_NULL,
on_delete=models.SET_NULL, # FIXME: Do we need traceability and accountability?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Le code a été adapté pour gérer un prescripteur qui a été supprimé dans l’admin, sans cris ni larmes. (avant, on avait une 500 car on récupérait un None)

Je pense qu’on est OK tant qu’il reste le diagnostique et l’organisation prescriptrice ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hésitation résolue par le fait de passer en mode RESTRICT-first.

@@ -529,7 +529,7 @@ class JobApplication(xwf_models.WorkflowEnabled, models.Model):
verbose_name="organisation du prescripteur émettrice",
null=True,
blank=True,
on_delete=models.SET_NULL,
on_delete=models.SET_NULL, # FIXME: Do we need traceability and accountability?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Peut-être plus intéressant de garder, et on n’a sûrement moins de raisons (e.g. RGPD) de supprimer (sans transfert) une orga prescriptrice ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hésitation résolue par le fait de passer en mode RESTRICT-first.

@@ -635,14 +635,14 @@ class EvaluatedJobApplication(models.Model):
job_application = models.ForeignKey(
"job_applications.JobApplication",
verbose_name="candidature",
on_delete=models.CASCADE,
on_delete=models.CASCADE, # FIXME: RESTRICT because CaP is an auditability tool?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep.

related_name="evaluated_job_applications",
)

evaluated_siae = models.ForeignKey(
EvaluatedSiae,
verbose_name="SIAE évaluée",
on_delete=models.CASCADE,
on_delete=models.CASCADE, # FIXME: RESTRICT because CaP is an auditability tool?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Il arrive qu'on souhaite retirer une SIAE d'une campagne (par exemple, parce qu'elle n'est plus accessible suite à une convention expirée) donc le CASCADE pourrait être pratique.
Après, les cas sont assez rares donc on pourra tout à fait le faire auto-prescription par auto-prescription avant de supprimer l'EvaluatedSiae

@@ -759,18 +759,20 @@ class EvaluatedAdministrativeCriteria(models.Model):
administrative_criteria = models.ForeignKey(
"eligibility.AdministrativeCriteria",
verbose_name="critère administratif",
on_delete=models.CASCADE,
on_delete=models.CASCADE, # FIXME: RESTRICT because CaP is an auditability tool?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep. Au pire, on passera gérer ces cas manuellement si un jour on supprime un critère d’éligibilité.

proof = models.ForeignKey("files.File", on_delete=models.CASCADE, blank=True, null=True)
proof = models.ForeignKey(
"files.File", on_delete=models.CASCADE, blank=True, null=True
) # FIXME: RESTRICT because CaP is an auditability tool?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ça, tu peux être sûr que RESTRICT ne cassera rien. Donc pas d’hésitation.

related_name="evaluated_administrative_criteria",
)

evaluated_job_application = models.ForeignKey(
EvaluatedJobApplication,
verbose_name="candidature évaluée",
on_delete=models.CASCADE,
on_delete=models.CASCADE, # FIXME: RESTRICT because CaP is an auditability tool?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

J’hésite pour celui-ci. Les critères sélectionnés pour justifier une candidature ne sont pas si important, c’est déclaratif pour l’employeur et si on supprime la candidature à évaluer, les critères sélectionnés peuvent suivre. Je laisserais bien CASCADE.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Je resterais également sur CASCADE.

@@ -181,7 +181,7 @@ class EvaluationCampaign(models.Model):

institution = models.ForeignKey(
"institutions.Institution",
on_delete=models.CASCADE,
on_delete=models.CASCADE, # FIXME: Seems dangerous to allow campaign deletion on institution deletion
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RESTRICT également

related_name="evaluated_administrative_criteria",
)

evaluated_job_application = models.ForeignKey(
EvaluatedJobApplication,
verbose_name="candidature évaluée",
on_delete=models.CASCADE,
on_delete=models.CASCADE, # FIXME: RESTRICT because CaP is an auditability tool?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Je resterais également sur CASCADE.

related_name="evaluated_job_applications",
)

evaluated_siae = models.ForeignKey(
EvaluatedSiae,
verbose_name="SIAE évaluée",
on_delete=models.CASCADE,
on_delete=models.CASCADE, # FIXME: RESTRICT because CaP is an auditability tool?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Il arrive qu'on souhaite retirer une SIAE d'une campagne (par exemple, parce qu'elle n'est plus accessible suite à une convention expirée) donc le CASCADE pourrait être pratique.
Après, les cas sont assez rares donc on pourra tout à fait le faire auto-prescription par auto-prescription avant de supprimer l'EvaluatedSiae

Copy link
Collaborator

@celine-m-s celine-m-s left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Merci pour cette remise à plat ! 🎉
Je suis d'accord avec tous les changements suggérés par les relecteurs (donc j'approuve en anticipant les changements à venir).

tonial added a commit that referenced this pull request Jun 4, 2024
This may happen when the siae was removed by the import_siae script.
This removal should soon be blocked (see #3150)
tonial added a commit that referenced this pull request Jun 4, 2024
This may happen when the siae was removed by the import_siae script.
This removal should soon be blocked (see #3150)
tonial added a commit that referenced this pull request Jun 4, 2024
This may happen when the siae was removed by the import_siae script.
This removal should soon be blocked (see #3150)
tonial added a commit that referenced this pull request Jun 4, 2024
This may happen when the siae was removed by the import_siae script.
This removal should soon be blocked (see #3150)
github-merge-queue bot pushed a commit that referenced this pull request Jun 4, 2024
This may happen when the siae was removed by the import_siae script.
This removal should soon be blocked (see #3150)
tonial added a commit that referenced this pull request Jun 4, 2024
This may happen when the siae was removed by the import_siae script.
This removal should soon be blocked (see #3150)
@rsebille rsebille force-pushed the rsebille/fk_on_delete branch from 1ffb0f1 to d8d0ab9 Compare July 29, 2024 13:26
@rsebille rsebille force-pushed the rsebille/fk_on_delete branch from 1156086 to dac1b2c Compare August 12, 2024 12:22
@rsebille rsebille changed the title Inventaire des ForeignKey.on_delete Règles plus restrictives pour la suppression de certain objets afin de ne pas créer d'incohérences de données Aug 12, 2024
@rsebille rsebille added this pull request to the merge queue Aug 12, 2024
Merged via the queue into master with commit 189e0a0 Aug 12, 2024
11 checks passed
@rsebille rsebille deleted the rsebille/fk_on_delete branch August 12, 2024 14:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants