I'm trying to implement 'undo' feature in django project with django-reversion in a case when user can accidentally modify multiple objects. Using admin panel won't work as one has to revert objects one-by-one.
My problem is that I am unable to create revision that holds data about more than one object.
Even when I do
with reversion.create_revision():
Res.object.all().delete()
then I cannot access revision that groups this change. It's splitted in a "one object one revision.models.Version" manner.
In [103]: reversion.models.Version.objects.all()
Out[103]: [<Version: #00001>, <Version: #00002>]
I've tried also through
reversion.models.Revision.objects.all().order_by('-date_created')[0].version_set.all()
but it also returned only one Version for one deleted Res object.
Seems like I'm missing something.
tl;dr - A revision isn't something you 'undo'. It's something you restore. Thus, undoing is a case of finding the most recent revision that occurred before the thing you want to undo, and restore it by calling revert().
The data model for django-reversion is based around Revisions and Versions. A Revision consists of one or more Versions, and a Version represents the serialized state of one Django model.
django-reversion allows you to roll back to the state of any previous Version or Revision. This is similar to, but not identical to an undo functionality.
Consider the following workflow:
# Create a Revision containing two Versions.
with reversion.create_revision():
a = SomeModel.objects.create(name="A version 1")
b = SomeModel.objects.create(name="B version 1")
# Create a Revision containing to Versions.
with reversion.create_revision():
a.name = "A version 2"
a.save()
b.name = "B version 2"
b.save()
At this point, you can 'undo' the second edit by reverting to the last revision.
# Revert just 'a' to version 1. This is the last but one revision.
reversion.get_for_object(a)[1].revert()
# Or, revert 'a' and 'b' to version 1.
reversion.get_for_object(b)[1].revision.revert()
You can also delete and recover like so:
# Store the pk of 'a', then delete it.
a_pk = a.pk
a.delete()
# Recover 'a' via it's primary key.
reversion.get_deleted(SomeModel).filter(object_id=a_pk).revert()
So you can revert to a previous state of a single model, or a group of models saved together. However, there is no way to say 'undo what I just did'. Instead, you have to tell reversion 'become like you were at this time'.
In your case, if you wanted to undo a bulk delete, you could do it like this:
# Save a revision containing all SomeModel instances.
with reversion.create_revision():
for obj in SomeModel.objects.all():
obj.save()
# Delete them all.
SomeModel.objects.delete()
# Revert back to the previous revision, where nothing was deleted.
Revision.objects.filter(version__content_type=ContentType.objects.get_for_model(SomeModel)).order_by("-date_created")[0].revert()
In this case, however, you'll get a very silly race condition, since other revisions for SomeModel could be created at any time.
I finally got it.
Issue 1: Store changes related to multiple objects in a single Revision
Solution: add TransactionMiddleware to MIDDLEWARE_CLASSES
Issue 2: Undo feature / reverting changes
Well, this is confusing. You cannot REVERT CHANGES you can RESTORE OBJECT to previously known state. And using reversion.create_revision() in your object-altering views stores data about that object after changing it. If you have view that modify multiple object then you have to create revision before making actual changes.
This is also visible in admin panel. If you change your object than a new log entry will appear. But if you want to revert that change than you cannot select newest version -- it is identical with the current state of the object -- instead you have to choose the previous one.