M2M form mixin
This commit is contained in:
parent
e92d50593c
commit
6f5fa19fc3
1 changed files with 50 additions and 0 deletions
50
shared/forms.py
Normal file
50
shared/forms.py
Normal 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
|
Loading…
Reference in a new issue