From 8a889fccdfb70e008ded2c6742dc3af75c722159 Mon Sep 17 00:00:00 2001 From: matinmidietsoir <42717317+matinmidietsoir@users.noreply.github.com> Date: Mon, 3 Sep 2018 12:22:06 +0200 Subject: [PATCH] fixing "assignment to the reverse side of a many-to-many.." fixing the bug : "direct assignment to the reverse side of a many-to-many set is prohibited. I use what I found here https://gist.github.com/Wtower/0b181cc06f816e4feac14e7c0aa2e9d0 --- extend.py | 98 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 extend.py diff --git a/extend.py b/extend.py new file mode 100644 index 0000000..243d09d --- /dev/null +++ b/extend.py @@ -0,0 +1,98 @@ +from django import forms +from django.contrib import admin +from django.core.exceptions import ImproperlyConfigured + + +def registered_modeladmin(model_cls): + try: + model_admin_cls = type(admin.site._registry[model_cls]) + model_admin_cls._model_cls = model_cls + return model_admin_cls + except KeyError: + raise ImproperlyConfigured( + "A ModelAdmin for %s needs to already be registered" % model_cls) + + +def registered_form(model_cls): + admin_cls = registered_modeladmin(model_cls) + form_cls = admin_cls.form + form_cls._admin_cls = admin_cls + return form_cls + + +def add_bidirectional_m2m(form_cls): + + class BidirectionalM2MForm(form_cls): + + def _get_bidirectional_m2m_fields(self): + try: + return super(BidirectionalM2MForm, self)._get_bidirectional_m2m_fields() + except AttributeError: + return [] + + def __init__(self, *args, **kwargs): + super(BidirectionalM2MForm, self).__init__(*args, **kwargs) + if self.instance.pk is not None: + for m2m_field, related_manager in self._get_bidirectional_m2m_fields(): + self.fields[m2m_field].initial = getattr( + self.instance, related_manager).all() + + def save(self, commit=True): + """ + Saves this ``form``'s cleaned_data into model instance + ``self.instance``. + + If commit=True, then the changes to ``instance`` will be saved to the + database. If ``instance`` is a new object then it will get saved to + the database even if commit=False + + Returns ``instance``. + """ + instance = super(BidirectionalM2MForm, self).save(commit=False) + force_save = self.instance.pk is None + if force_save: + instance.save() + for m2m_field, related_manager in self._get_bidirectional_m2m_fields(): +# setattr(self.instance, related_manager, self.cleaned_data[m2m_field]) +# ici ça plante direct assignment to the reverse side of a many-to-many set is prohibited. Use suppliers.set() instead. + +# self.instance.suppliers.set(self.cleaned_data[m2m_field]) #là ça marche mais avec suppliers en dur +# self.instance.related_manager.set(self.cleaned_data[m2m_field]) #et là ça plante 'Food' object has no attribute 'related_manager' +# j'appends que instance.__setattr__(attribute, value) est équivalent à instance.attribute = value +# self.instance.__set__(related_manager,self.cleaned_data[m2m_field]) # mais ça marche pas non plus 'Food' object has no attribute '__set__' +# et finalement je copie la solution utilisée là https://gist.github.com/Wtower/0b181cc06f816e4feac14e7c0aa2e9d0 + recordset = getattr(self.instance, m2m_field) + records = recordset.all() + # remove records that have been removed in form + for record in records: + if record not in self.cleaned_data[m2m_field]: + recordset.remove(record) + # add records that have been added in form + for record in self.cleaned_data[m2m_field]: + if record not in records: + recordset.add(record) + + if commit: + if not force_save: + instance.save() + self.save_m2m() + return instance + + return BidirectionalM2MForm + + +def extend_registered(extended_cls): + if issubclass(extended_cls, admin.ModelAdmin): + admin_cls = extended_cls + admin.site.unregister(admin_cls._model_cls) + admin.site.register(admin_cls._model_cls, admin_cls) + return admin_cls + elif issubclass(extended_cls, (forms.Form, forms.ModelForm)): + form_cls = extended_cls + admin_cls = form_cls._admin_cls + admin.site.unregister(admin_cls._model_cls) + admin_cls.form = form_cls + admin.site.register(admin_cls._model_cls, admin_cls) + return form_cls + else: + raise ValueError("Extended class needs to be a ModelAdmin or Form")