track m2m changes

This commit is contained in:
Laura Klünder 2017-06-13 14:21:01 +02:00
parent 66596aac63
commit ff0922fe64
3 changed files with 56 additions and 8 deletions

View file

@ -98,17 +98,26 @@ class ChangeSet(models.Model):
for name, value in kwargs.items(): for name, value in kwargs.items():
setattr(change, name, value) setattr(change, name, value)
change.save() change.save()
print(repr(change)) # print(repr(change))
return change return change
def add_create(self, obj, author=None): def add_create(self, obj, author=None):
change = self._new_change(author=author, action='create', model_class=type(obj._obj)) change = self._new_change(author=author, action='create', model_class=type(obj._obj))
obj.pk = 'c%d' % change.pk obj.pk = 'c%d' % change.pk
def add_update(self, obj, name, value, author=None): def _add_value(self, action, obj, name, value, author=None):
return self._new_change(author=author, action='update', obj=obj, return self._new_change(author=author, action=action, obj=obj,
field_name=name, field_value=json.dumps(value, ensure_ascii=False)) 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): def add_delete(self, obj, author=None):
return self._new_change(author=author, action='delete', obj=obj) 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.') raise TypeError('existing_model_pk or created_object have to be set.')
@obj.setter @obj.setter
def obj(self, value: models.Model): def obj(self, value):
if isinstance(value, ModelInstanceWrapper) and isinstance(value.pk, str): if not isinstance(value, ModelInstanceWrapper):
value = self.changeset.wrap(value)
if isinstance(value.pk, str):
if value._changeset.id != self.changeset.pk: if value._changeset.id != self.changeset.pk:
raise ValueError('value is a Change instance but belongs to a different changeset.') raise ValueError('value is a Change instance but belongs to a different changeset.')
self.model_class = type(value._obj) 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 result += 'Update object '+repr(self.obj)+': '+self.field_name+'='+self.field_value
elif self.action == 'delete': elif self.action == 'delete':
result += 'Delete object '+repr(self.obj) 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 += '>' result += '>'
return result return result

View file

@ -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.on_top_of = on_top_of
obj.save() obj.save()
# form.save_m2m() form.save_m2m()
# request.changeset.changes.all().delete() # request.changeset.changes.all().delete()
return redirect(ctx['back_url']) return redirect(ctx['back_url'])

View file

@ -19,6 +19,8 @@ class BaseWrapper:
return ModelInstanceWrapper(self._changeset, instance, self._author) return ModelInstanceWrapper(self._changeset, instance, self._author)
def _wrap_manager(self, manager): def _wrap_manager(self, manager):
if hasattr(manager, 'through'):
return ManyRelatedManagerWrapper(self._changeset, manager, self._author)
return ManagerWrapper(self._changeset, manager, self._author) return ManagerWrapper(self._changeset, manager, self._author)
def _wrap_queryset(self, queryset): def _wrap_queryset(self, queryset):
@ -37,8 +39,6 @@ class BaseWrapper:
elif callable(value) and name not in self._allowed_callables: elif callable(value) and name not in self._allowed_callables:
if not isinstance(self, ModelInstanceWrapper) or hasattr(models.Model, name): if not isinstance(self, ModelInstanceWrapper) or hasattr(models.Model, name):
raise TypeError('Can not call %s.%s wrapped!' % (self._obj, name)) raise TypeError('Can not call %s.%s wrapped!' % (self._obj, name))
# print(self._obj, name, type(value), value)
return value return value
def __setattr__(self, name, value): def __setattr__(self, name, value):
@ -212,5 +212,37 @@ class ManagerWrapper(BaseQueryWrapper):
pass 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): class QuerySetWrapper(BaseQueryWrapper):
pass pass