ChangedObject m2m implementation

This commit is contained in:
Laura Klünder 2017-06-27 16:10:28 +02:00
parent 15e0b3cad2
commit eb326a3694
4 changed files with 68 additions and 22 deletions

View file

@ -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()

View file

@ -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:

View file

@ -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,

View file

@ -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: