diff --git a/src/c3nav/editor/models/changedobject.py b/src/c3nav/editor/models/changedobject.py index be2135c5..c82443d4 100644 --- a/src/c3nav/editor/models/changedobject.py +++ b/src/c3nav/editor/models/changedobject.py @@ -33,8 +33,8 @@ class ChangedObject(models.Model): model_class = kwargs.pop('model_class', None) super().__init__(*args, **kwargs) self._set_object = None - self._m2m_added_cache = {name: set(values) for name, values in self.m2m_added} - self._m2m_removed_cache = {name: set(values) for name, values in self.m2m_added} + self._m2m_added_cache = {name: set(values) for name, values in self.m2m_added.items()} + self._m2m_removed_cache = {name: set(values) for name, values in self.m2m_removed.items()} if model_class is not None: self.model_class = model_class @@ -114,8 +114,8 @@ class ChangedObject(models.Model): self.changeset.deleted_existing.setdefault(model, set()).add(pk) if not self.deleted: - self.changeset.m2m_added.get(model, {})[pk] = self._m2m_added_cache - self.changeset.m2m_removed.get(model, {})[pk] = self._m2m_removed_cache + self.changeset.m2m_added.setdefault(model, {})[pk] = self._m2m_added_cache + self.changeset.m2m_removed.setdefault(model, {})[pk] = self._m2m_removed_cache else: self.changeset.m2m_added.get(model, {}).pop(pk, None) self.changeset.m2m_removed.get(model, {}).pop(pk, None) @@ -189,8 +189,6 @@ class ChangedObject(models.Model): else: value = None if value is None else value.pk self.updated_fields[field.name] = value - else: - raise NotImplementedError self.clean_updated_fields() self.save() @@ -201,6 +199,46 @@ class ChangedObject(models.Model): self.deleted = True self.save() + def m2m_set(self, name, set_pks=None): + if self.is_created: + self._m2m_removed_cache.get(name, None) + return + + field = self.model_class._meta.get_field(name) + rel_name = field.rel.related_name + pks = set(field.related_model.objects.filter(**{rel_name+'__pk': self.obj_pk}).values_list('pk', flat=True)) + + m2m_added_before = self._m2m_added_cache.get(name, set()) + m2m_removed_before = self._m2m_removed_cache.get(name, set()) + + if set_pks is None: + self._m2m_added_cache.get(name, set()).difference_update(pks) + self._m2m_removed_cache.get(name, set()).intersection_update(pks) + else: + self._m2m_added_cache[name] = set_pks - pks + self._m2m_removed_cache[name] = pks - set_pks + + if not self._m2m_added_cache.get(name, set()): + self._m2m_added_cache.pop(name, None) + if not self._m2m_removed_cache.get(name, set()): + self._m2m_removed_cache.pop(name, None) + + if (m2m_added_before != self._m2m_added_cache.get(name, set()) or + m2m_removed_before != self._m2m_removed_cache.get(name, set())): + self.save() + return True + return False + + def m2m_add(self, name, pks: set): + self._m2m_added_cache.setdefault(name, set()).update(pks) + self._m2m_removed_cache.setdefault(name, set()).difference_update(pks) + self.m2m_set(name) + + def m2m_remove(self, name, pks: set): + self._m2m_removed_cache.setdefault(name, set()).update(pks) + self._m2m_added_cache.setdefault(name, set()).difference_update(pks) + self.m2m_set(name) + @property def does_something(self): return (self.updated_fields or self._m2m_added_cache or self._m2m_removed_cache or self.is_created or @@ -213,8 +251,11 @@ class ChangedObject(models.Model): if self.pk is not None: self.delete() return False - self.m2m_added = {name: tuple(values) for name, values in self._m2m_added_cache} - self.m2m_removed = {name: tuple(values) for name, values in self._m2m_removed_cache} + if self.changeset.pk is None: + self.changeset.save() + self.changeset = self.changeset + self.m2m_added = {name: tuple(values) for name, values in self._m2m_added_cache.items()} + self.m2m_removed = {name: tuple(values) for name, values in self._m2m_removed_cache.items()} super().save(*args, **kwargs) if not self.changeset.fill_changes_cache(): self.update_changeset_cache() diff --git a/src/c3nav/editor/models/changeset.py b/src/c3nav/editor/models/changeset.py index 889bcbd3..f6871a12 100644 --- a/src/c3nav/editor/models/changeset.py +++ b/src/c3nav/editor/models/changeset.py @@ -131,7 +131,11 @@ class ChangeSet(models.Model): :param include_deleted_created: Fetch created objects that were deleted. :rtype: True if the method was executed, else False """ - if self.pk is None or self.changed_objects is not None: + if self.changed_objects is not None: + return False + + if self.pk is None: + self.changed_objects = {} return False if include_deleted_created: diff --git a/src/c3nav/editor/views/changes.py b/src/c3nav/editor/views/changes.py index 920e5b4c..bc0d3823 100644 --- a/src/c3nav/editor/views/changes.py +++ b/src/c3nav/editor/views/changes.py @@ -142,10 +142,10 @@ def changeset_detail(request, pk): for m2m_mode in ('m2m_added', 'm2m_removed'): m2m_list = getattr(changed_object, m2m_mode).items() - for name, values in sorted(m2m_list, key=lambda name, value: form_fields.index(name)): + for name, values in sorted(m2m_list, key=lambda nv: form_fields.index(nv[0])): field = model._meta.get_field(name) for value in values: - change_data.update({ + changes.append({ 'icon': 'chevron-right' if m2m_mode == 'm2m_added' else 'chevron-left', 'class': 'info', 'title': field.verbose_name, diff --git a/src/c3nav/editor/wrappers.py b/src/c3nav/editor/wrappers.py index c701fc3e..64054bf3 100644 --- a/src/c3nav/editor/wrappers.py +++ b/src/c3nav/editor/wrappers.py @@ -739,21 +739,22 @@ class ManyRelatedManagerWrapper(RelatedManagerWrapper): return self._obj.prefetch_cache_name def set(self, objs): - 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)) + if self._obj.reverse: + raise NotImplementedError + pks = set(obj.pk for obj in objs) + self._changeset.get_changed_object(self._obj.instance).m2m_set(self._get_cache_name(), pks) def add(self, *objs): - 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._get_cache_name(), value=pk) + if self._obj.reverse: + raise NotImplementedError + pks = set((obj.pk if isinstance(obj, self._obj.model) else obj) for obj in objs) + self._changeset.get_changed_object(self._obj.instance).m2m_add(self._get_cache_name(), pks) def remove(self, *objs): - 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._get_cache_name(), value=pk) + if self._obj.reverse: + raise NotImplementedError + pks = set((obj.pk if isinstance(obj, self._obj.model) else obj) for obj in objs) + self._changeset.get_changed_object(self._obj.instance).m2m_remove(self._get_cache_name(), pks) def all(self): try: