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