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