M2M form mixin

This commit is contained in:
Ludovic Stephan 2020-08-03 19:06:02 +02:00
parent e92d50593c
commit 6f5fa19fc3

50
shared/forms.py Normal file
View file

@ -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 <field>.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.<field_name>` qui ne sont pas
dans `self.<field_name>.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