From 6f5fa19fc34027f0b445000b53c2ceb2c65cf129 Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Mon, 3 Aug 2020 19:06:02 +0200 Subject: [PATCH] M2M form mixin --- shared/forms.py | 50 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 shared/forms.py diff --git a/shared/forms.py b/shared/forms.py new file mode 100644 index 00000000..97094e29 --- /dev/null +++ b/shared/forms.py @@ -0,0 +1,50 @@ +from django.forms.models import ModelForm + + +class ProtectedModelForm(ModelForm): + """ + Extension de `ModelForm` + + Quand on save un champ `ManyToMany` dans un `ModelForm`, la méthode appelée + est .set(), qui écrase l'intégralité du contenu. + Le problème survient quand le `field` a un queryset restreint, et qu'on ne + veut pas toucher aux choix qui ne sont pas dans ce queryset... + C'est le but de ce mixin. + + Attributs : + - `protected_fields` : champs qu'on souhaite protéger. + """ + + protected_fields = [] + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + for field_name in self.protected_fields: + if field_name not in self.fields: + raise ValueError("Le champ %s n'existe pas !" % field_name) + + def _get_protected_elts(self, field_name): + """ + Renvoie tous les éléments de `instance.` qui ne sont pas + dans `self..queryset` (et sont donc à conserver). + + NB : on "désordonne" tous les querysets via `.order_by()` car Django + ne peut pas effectuer une union de QS ordonnés. + """ + if self.instance.pk: + previous = getattr(self.instance, field_name).order_by() + selectable = self.fields[field_name].queryset.order_by() + return previous.difference(selectable) + else: + # Nouvelle instance, rien à protéger. + return self.fields[field_name].queryset.none() + + def clean(self): + cleaned_data = super().clean() + for field_name in self.protected_fields: + selected_elts = cleaned_data[field_name].order_by() + protected_elts = self._get_protected_elts(field_name) + cleaned_data[field_name] = selected_elts.union(protected_elts) + + return cleaned_data