diff --git a/src/c3nav/editor/models.py b/src/c3nav/editor/models.py index 8848cb17..a6758905 100644 --- a/src/c3nav/editor/models.py +++ b/src/c3nav/editor/models.py @@ -98,17 +98,26 @@ class ChangeSet(models.Model): for name, value in kwargs.items(): setattr(change, name, value) change.save() - print(repr(change)) + # print(repr(change)) return change def add_create(self, obj, author=None): change = self._new_change(author=author, action='create', model_class=type(obj._obj)) obj.pk = 'c%d' % change.pk - def add_update(self, obj, name, value, author=None): - return self._new_change(author=author, action='update', obj=obj, + def _add_value(self, action, obj, name, value, author=None): + return self._new_change(author=author, action=action, obj=obj, field_name=name, field_value=json.dumps(value, ensure_ascii=False)) + def add_update(self, obj, name, value, author=None): + return self._add_value('update', obj, name, value, author) + + def add_m2m_add(self, obj, name, value, author=None): + return self._add_value('m2m_add', obj, name, value, author) + + def add_m2m_remove(self, obj, name, value, author=None): + return self._add_value('m2m_remove', obj, name, value, author) + def add_delete(self, obj, author=None): return self._new_change(author=author, action='delete', obj=obj) @@ -183,8 +192,11 @@ class Change(models.Model): raise TypeError('existing_model_pk or created_object have to be set.') @obj.setter - def obj(self, value: models.Model): - if isinstance(value, ModelInstanceWrapper) and isinstance(value.pk, str): + def obj(self, value): + if not isinstance(value, ModelInstanceWrapper): + value = self.changeset.wrap(value) + + if isinstance(value.pk, str): if value._changeset.id != self.changeset.pk: raise ValueError('value is a Change instance but belongs to a different changeset.') self.model_class = type(value._obj) @@ -252,5 +264,9 @@ class Change(models.Model): result += 'Update object '+repr(self.obj)+': '+self.field_name+'='+self.field_value elif self.action == 'delete': result += 'Delete object '+repr(self.obj) + elif self.action == 'm2m_add': + result += 'Update (m2m) object '+repr(self.obj)+': '+self.field_name+'.add('+self.field_value+')' + elif self.action == 'm2m_remove': + result += 'Update (m2m) object '+repr(self.obj)+': '+self.field_name+'.remove('+self.field_value+')' result += '>' return result diff --git a/src/c3nav/editor/views.py b/src/c3nav/editor/views.py index 782446a9..643d19ec 100644 --- a/src/c3nav/editor/views.py +++ b/src/c3nav/editor/views.py @@ -226,7 +226,7 @@ def edit(request, pk=None, model=None, level=None, space=None, on_top_of=None, e obj.on_top_of = on_top_of obj.save() - # form.save_m2m() + form.save_m2m() # request.changeset.changes.all().delete() return redirect(ctx['back_url']) diff --git a/src/c3nav/editor/wrappers.py b/src/c3nav/editor/wrappers.py index 34af7156..587fe38d 100644 --- a/src/c3nav/editor/wrappers.py +++ b/src/c3nav/editor/wrappers.py @@ -19,6 +19,8 @@ class BaseWrapper: return ModelInstanceWrapper(self._changeset, instance, self._author) def _wrap_manager(self, manager): + if hasattr(manager, 'through'): + return ManyRelatedManagerWrapper(self._changeset, manager, self._author) return ManagerWrapper(self._changeset, manager, self._author) def _wrap_queryset(self, queryset): @@ -37,8 +39,6 @@ class BaseWrapper: elif callable(value) and name not in self._allowed_callables: if not isinstance(self, ModelInstanceWrapper) or hasattr(models.Model, name): raise TypeError('Can not call %s.%s wrapped!' % (self._obj, name)) - - # print(self._obj, name, type(value), value) return value def __setattr__(self, name, value): @@ -212,5 +212,37 @@ class ManagerWrapper(BaseQueryWrapper): pass +class ManyRelatedManagerWrapper(ManagerWrapper): + def _check_through(self): + if not self._obj.through._meta.auto_created: + raise AttributeError('Cannot do this an a ManyToManyField which specifies an intermediary model.') + + def set(self, objs, author=None): + if author is None: + author = self._author + + old_ids = set(self.values_list('pk', flat=True)) + new_ids = set(obj.pk for obj in objs) + + self.remove(*(old_ids - new_ids)) + self.add(*(new_ids - old_ids)) + + def add(self, *objs, author=None): + if author is None: + author = self._author + + for obj in objs: + pk = (obj.pk if isinstance(obj, self._obj.model) else obj) + self._changeset.add_m2m_add(self._obj.instance, name=self.prefetch_cache_name, value=pk, author=author) + + def remove(self, *objs, author=None): + if author is None: + author = self._author + + for obj in objs: + pk = (obj.pk if isinstance(obj, self._obj.model) else obj) + self._changeset.add_m2m_remove(self._obj.instance, name=self.prefetch_cache_name, value=pk, author=author) + + class QuerySetWrapper(BaseQueryWrapper): pass