Search code examples
pythonmysqldjangoatomic

Control Atomic Transactions in Django


I have a simple library application. In order to force 3 actions to commit as one action, and rollback if any of the actions fail, I made the following code changes:

In settings.py:

AUTOCOMMIT=False

In forms.py

from django.db import IntegrityError, transaction
    class CreateLoan(forms.Form):
        #Fields...
        def save(self):
            id_book = form.cleaned_data.get('id_book', None)
            id_customer = form.cleaned_data.get('id_customer', None)
            start_date = form.cleaned_data.get('start_date', None)
            book = Book.objects.get(id=id_book)
            customer = Customer.objects.get(id=id_customer)
            new_return = Return(
                book=book
                start_date=start_date)
            txn=Loan_Txn(
                customer=customer,
                book=book,
                start_date=start_date
            )

            try
                with transaction.atomic():
                    book.update(status="ON_LOAN")
                    new_return.save(force_insert=True)
                    txn.save(force_insert=True)
            except IntegrityError:
                raise forms.ValidationError("Something occured. Please try again")

Am I still missing anything with regards to this? I'm using Django 1.9 with Python 3.4.3 and the database is MySQL.


Solution

  • You're using transaction.atomic() correctly (including putting the try ... except outside the transaction) but you should definitely not be setting AUTOCOMMIT = False.

    As the documentation states, you set that system-wide setting to False when you want to "disable Django’s transaction management"—but that's clearly not what you want to do, since you're using transaction.atomic()! More from the documentation:

    If you do this, Django won’t enable autocommit, and won’t perform any commits. You’ll get the regular behavior of the underlying database library. This requires you to commit explicitly every transaction, even those started by Django or by third-party libraries. Thus, this is best used in situations where you want to run your own transaction-controlling middleware or do something really strange.

    So just don't do that. Django will of course disable autocommit for that atomic block and re-enable it when the block finishes.