user984003
user984003

Reputation: 29597

Django 1.10: Commit data to log table after fail / rollback of main transaction

I am moving to Django 1.10 from Django 1.5 and discovering that database transactions are quite different with little commit and rollback control.

What I want:

Perform series of queries in view: my_function(request)
If all queries succeed:
    commit
if any query fails:
   rollback
   insert into log table and commit the insertion

In Django 1.5, I handle this with rollback and commit in the Exception Middleware:

class ExceptionMiddleware(object):
    def process_exception(self, request, exception): 
        # do rollback
        # insert into log table
        # commit

How can I achieve this in Django 1.10 where there doesn't seem to be a way to do rollback?

My 1.10 settings:

AUTOCOMMIT = True
ATOMIC_REQUESTS = True

This puts all queries into a single transaction that, correctly, only commits upon completion.

I would have more rollback/commit control with "AUTOCOMMIT = False," but Django recommends against this: "this is best used in situations where you want to run your own transaction-controlling middleware or do something really strange."

Upvotes: 0

Views: 456

Answers (2)

user984003
user984003

Reputation: 29597

The following worked. When there is an exception in my_view(), its insertion is rolled back, but the insertion in the exception middleware is committed.

settings.py

Database ATOMIC_REQUESTS = True

MIDDLEWARE = [
    ...
    'myproject.middleware.ExceptionMiddleware', # put it last
]

views.py

def my_view(request):
    do table insertion insertion..
    x = 1/0 # cause exception

middleware.py

from django.utils.deprecation import MiddlewareMixin

class ExceptionMiddleware(MiddlewareMixin):
    def process_exception(self, request, exception): 
        # can be put inside "with transaction.atomic():" for more control of rollback/commit within this function
        log_error() # do table insertion
        # can return some response

Upvotes: 1

knbk
knbk

Reputation: 53699

Django 1.10 also introduced a new style of middleware, which makes this quite easy:

from django.db import transaction

class LoggingMiddleware(object):
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        try:
            with transaction.atomic():
                response = self.get_response(request)
        except:
            log_failure()
            raise
        return response

When an exception is propagated through the __exit__ method of transaction.atomic(), the transaction is automatically rolled back. You can then catch the exception and log the failure outside of the main transaction that encapsulates the view.

Upvotes: 2

Related Questions