2

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.

Community
  • 1
  • 1
rajivRaja
  • 527
  • 3
  • 6
  • 16

1 Answers1

2

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.

rajivRaja
  • 527
  • 3
  • 6
  • 16