50 lines
1.8 KiB
Python
50 lines
1.8 KiB
Python
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
|