notbad.jpeg notbad.jpeg - 2 months ago 20
Python Question

django: Proper way to recover from IntegrityError

What's the proper way to recover from an
IntegrityError
, or any other errors that could leave my transactions screwed up without using manual transaction control?



In my application, I'm running into problems with
IntegrityError
s that I want to recover from, that screw up later database activity, leaving me with:

DatabaseError: current transaction is aborted, commands ignored until end of transaction block`


for all database activity after ignoring
IntegrityErrors
.

This block of code should reproduce the error I'm seeing

from django.db import transaction

try:
MyModel.save() # Do a bad save that will raise IntegrityError
except IntegrityError:
pass

MyModel.objects.all() # raises DatabaseError: current transaction is aborted, commands ignored until end of transaction block


According to the docs, the solution to recover from an
IntegrityError
is by rolling back the transaction. But the following code results in a
TransactionManagementError
.

from django.db import transaction

try:
MyModel.save()
except IntegrityError:
transaction.rollback() # raises TransactionManagementError: This code isn't under transaction management

MyModel.objects.all() # Should work





EDIT: I'm confused by the message from the
TransactionManagementError
, because if in my
except
I do a:

connection._cursor().connection.rollback()


instead of the django
transaction.rollback()
, the
MyModel.objects.all()
succeeds, which doesn't make sense if my code "isn't under transaction management". It also doesn't make sense that code that isn't under transaction management (which I assume means it's using autocommit), can have transactions that span multiple queries.

EDIT #2: I'm aware of using manual transaction control to be able to recover from these errors, but shouldn't I be able to recover without manual transaction control? My understanding is that if I'm using autocommit, there should only be one write per transaction, so it should not affect later database activity.

EDIT #3: This is a couple years later, but in django 1.4 (not sure about later versions), another issue here was that
Model.objects.bulk_create()
doesn't honor autocommit behavior.

Versions:




  • Django: 1.4 (
    TransactionMiddleWare
    is not enabled)

  • Python: 2.7

  • Postgres: 9.1


Answer

Django's default commit mode is AutoCommit. In order to do rollback, you need to wrap the code doing the work in a transaction. [docs]

with transaction.commit_on_success():
    # Your code here. Errors will auto-rollback.

To get database level autocommit, you will require the following option in your DATABASES settings dictionary.

'OPTIONS': {'autocommit': True,}

Alternately, you can use explicit savepoints to roll back to. [docs]

@transaction.commit_manually
def viewfunc(request):

  a.save()
  # open transaction now contains a.save()
  sid = transaction.savepoint()

  b.save()
  # open transaction now contains a.save() and b.save()

  if want_to_keep_b:
      transaction.savepoint_commit(sid)
      # open transaction still contains a.save() and b.save()
  else:
      transaction.savepoint_rollback(sid)
      # open transaction now contains only a.save()

  transaction.commit()
Comments