Question

I'm facing an issue which is somewhat reverse of Django reversion does not save revisions made in shell

Versions being used:

Django: v1.3.1

django-reversion: v1.5.7

I wrote a class that can be used to save/discard changes made to a model:

import reversion, datetime

class Execute:
    model = None
    delete_ids = []
    def __init__(self, model):
            self.model = model
        if not reversion.is_registered(model):
            reversion.register(model)
    def update(self, n):
        id = n.pk
        if len(self.model.objects.filter(pk=id)) > 0:
            m= self.model.objects.get(pk=id)
            if len(reversion.get_for_object(m)) == 1: # Update newly inserted element
                reversion.get_for_object(m).delete() # reversion list only 1 long.
                with reversion.create_revision():
                    m=n
                    m.save()
            else: # Update existing element. reversion list will be at least 2 long
                if len(reversion.get_for_object(m)) == 0: # Add self as first revision.
                    with reversion.create_revision():
                        m.save()
                with reversion.create_revision(): # Add updates
                    m=n
                    m.save()
    def insert(self, n):
        with reversion.create_revision():
            n.pk = None
            n.save()
        return n.pk
    def delete(self, n):
        id = n.pk
        if len(self.model.objects.filter(pk=id)) > 0:
            m=self.model.objects.get(pk=id)
            with reversion.create_revision():
                m.save()
            delete_ids.append(id)
            m.delete()
    def discard(self, n):
        id = n.pk
        if len(self.model.objects.filter(pk=id)) > 0:
            m=self.model.objects.get(pk=id)
            rev=reversion.get_for_object(m)
            if len(rev) == 0:
                return
            if len(rev) == 1: #insert operation, then delete
                m.delete()
            else: #update operation, then revert
                rev[len(rev)-1].revert()
            rev.delete()
        if len(reversion.get_deleted(self.model).filter(object_id=id)) > 0:
            reversion.get_deleted(self.model).get(object_id=id).revert()
    def save(self, n):
        id = n.pk
        if len(self.model.objects.filter(pk=id)) > 0:
            m=self.model.objects.get(pk=id)
            m.save()
            reversion.get_for_object(m).delete()
        if len(reversion.get_deleted(self.model).filter(object_id=id)) > 0:
            reversion.get_deleted(self.model).get(object_id=id).delete()

I then use it as follows:

> my_execute=Execute(MyModel) m=MyModel.objects.get(pk=id)
> 
> --modify something in m-- 
> my_execute.update(tm)
> 
> --modify something else in m-- 
> my_execute.update(tm)
> 
> --modify something else in m-- 
> my_execute.update(tm)
> 
> my_execute.discard(tm)  <-- Revert m to original 
> or 
> my_execute.save(tm) <-- Save new modifications

This works perfectly when I run these via shell, but is inconsistent when running through POST requests in views.

On debugging, I found out that basically when running in shell, every time my registered model is doing a "save()", I see the list "reversion.get_for_object(m)" adding a new revision as expected. But when I run the same through Django view, the list does not get updated as expected, but seems to happen only when the end of view code is reached.

eg: When I update an existing model, I expect to see two entries like:

>>> reversion.get_for_object(tm)
[]
>>> tm_execute.update(tm)
>>> reversion.get_for_object(tm)
[<Version: QGE__Power Sequencing fix__726__lalitb>, <Version: QGE__Power Sequencing fix__726__lalitb>]
>>> 

However when I do the same using views:

from collabgrid.testmatrix.models import Testmatrix, Testcaseinfo, Product
from django.http import HttpResponse
from django.utils import simplejson
import json, pdb, datetime, reversion

class Execute:
    model = None
    delete_ids = []
    def __init__(self, model):
            self.model = model
        if not reversion.is_registered(model):
            reversion.register(model)
    def update(self, n):
        id = n.pk;
        if len(self.model.objects.filter(pk=id)) > 0:
            m= self.model.objects.get(pk=id)
            if len(reversion.get_for_object(m)) == 1: # Update newly inserted element
                reversion.get_for_object(m).delete() # reversion list only 1 long.
                with reversion.create_revision():
                    m=n
                    m.save()
            else: # Update existing element. reversion list will be at least 2 long
                if len(reversion.get_for_object(m)) == 0: # Add self as first revision.
                    with reversion.create_revision():
                        m.save()
                with reversion.create_revision(): # Add updates
                    m=n
                    m.save()
    def insert(self, n):
        with reversion.create_revision():
            n.pk = None
            n.save()
        return n.pk
    def delete(self, n):
        id = n.pk
        if len(self.model.objects.filter(pk=id)) > 0:
            m=self.model.objects.get(pk=id)
            with reversion.create_revision():
                m.save()
            delete_ids.append(id)
            m.delete()
    def discard(self, n):
        id = n.pk
        if len(self.model.objects.filter(pk=id)) > 0:
            m=self.model.objects.get(pk=id)
            rev=reversion.get_for_object(m)
            if len(rev) == 0:
                return
            if len(rev) == 1: #insert operation, then delete
                m.delete()
            else: #update operation, then revert
                rev[len(rev)-1].revert()
            rev.delete()
        if len(reversion.get_deleted(self.model).filter(object_id=id)) > 0:
            reversion.get_deleted(self.model).get(object_id=id).revert()
    def save(self, n):
        id = n.pk
        if len(self.model.objects.filter(pk=id)) > 0:
            m=self.model.objects.get(pk=id)
            m.save()
            reversion.get_for_object(m).delete()
        if len(reversion.get_deleted(self.model).filter(object_id=id)) > 0:
            reversion.get_deleted(self.model).get(object_id=id).delete()


tm_execute=Execute(Testmatrix)

def update_sub_col(request):
    print request
    if request.method == 'POST':
        ret = request.POST
        tm_data = json.loads(request.POST['tm_data'])
        for t in tm_data:
            if(len(Testmatrix.objects.filter(pk=t['id'])) > 0):
                tm = Testmatrix.objects.get(pk=t['id'])
                if tm.os != t['sub_col']:
                    tm.os = t['sub_col']
                    print "BEFORE: ", reversion.get_for_object(tm) # List returns as empty []
                    tm_execute.update(tm)
                    print "AFTER:  ",reversion.get_for_object(tm) # List returns as empty []
                                                                  # but subsequent read has one
                                                                  # revision entry.
    json_response = {
        'ret': 'success'
    }
    return HttpResponse(simplejson.dumps(json_response),mimetype='application/javascript')

I see the list to be only one long on subsequent reads:

[<Version: QGE__Power Sequencing fix__726__lalitb>] 

At this point the model commits all modifications and there is no way to revert them back. Not sure why the difference since I'm using the same code in both cases.

I even used pdb to check the revision list contents after

with reversion.create_revision():
                        m.save()

and it was empty when I walked through the update function when run via views, but was correctly showing revision entries when debugged through shell prompt.

Was it helpful?

Solution

Turns out that mixing and matching revision creation mechanisms was biting me (although the docs say otherwise).

I was using the following techniques:

1) Middleware:

  • django.middleware.transaction.TransactionMiddleware
  • reversion.middleware.RevisionMiddleware

2) Context Manager

  • reversion.create_revision() context manager

I removed the middleware settings and just resorted to Context Manager and everything is working as expected across shell as well as AJAX/views.

Edit: Turns out I'm doing the right thing. Removing revision middleware is the way to go to get finer grain control as per this thread.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top