24

I have a pretty long data migration that I'm doing to correct an earlier bad migration where some rows were created incorrectly. I'm trying to assign values to a new column based upon old ones, however, sometimes this leads to integrity errors. When this happens, I want to throw away the one that's causing the integrity error

Here is a code snippet:

def load_data(apps, schema_editor):
    MyClass = apps.get_model('my_app', 'MyClass')

    new_col_mapping = {old_val1: new_val1, ....}

    for inst in MyClass.objects.filter(old_col=c):

        try:
            inst.new_col = new_col_mapping[c]
            inst.save()

        except IntegrityError:
            inst.delete()

Then in the operations of my Migration class I do

operations = [
    migrations.RunPython(load_data)
]

I get the following error when running the migration

django.db.transaction.TransactionManagementError: An error occurred in the current transaction. You can't execute queries until the end of the 'atomic' block

I get the feeling that doing

with transaction.atomic():

somewhere is my solution but I'm not exactly sure where the right place is. More importantly I'd like to understand WHY this is necessary

sedavidw
  • 11,116
  • 13
  • 61
  • 95

4 Answers4

44

This is similar to the example in the docs.

First, add the required import if you don't have it already.

from django.db import transaction

Then wrap the code that might raise an integrity error in an atomic block.

try:
    with transaction.atomic():
        inst.new_col = new_col_mapping[c]
        inst.save()
except IntegrityError:
    inst.delete()

The reason for the error is explained in the warning block 'Avoid catching exceptions inside atomic!' in the docs. Once Django encounters a database error, it will roll back the atomic block. Attempting any more database queries will cause a TransactionManagementError, which you are seeing. By wrapping the code in an atomic block, only that code will be rolled back, and you can execute queries outside of the block.

Leistungsabfall
  • 6,368
  • 7
  • 33
  • 41
Alasdair
  • 298,606
  • 55
  • 578
  • 516
4

Each migration is wrapped around one transaction, so when something fails during migration, all operations will be cancelled. Because of that, each transaction in which something failed, can't take new queries (they will be cancelled anyway).

Wrapping some operations with with transaction.atomic(): is not good solution, because you won't be able to cancel that operation when something will fail. Instead of that, avoid integrity errors by doing some more checks before saving data.

GwynBleidD
  • 20,081
  • 5
  • 46
  • 77
  • 1
    I'm not sure why using `transaction.atomic()` is a "bad" solution if that's what I'm going to be doing anyway. These rows were made incorrectly and are in fact bad, and I want to get rid of them – sedavidw Aug 25 '15 at 17:54
2

It seems that the same exception can have a variety of causes. In my case it was caused by an invalid model field name: I used a greek letter delta in my field name.

It seemed to work fine, all app worked well (perhaps I just didn't try any more complex use case). The tests, however, raised TransactionManagementError.

I solved the problem by removing from the field name and from all the migration files.

Eerik Sven Puudist
  • 2,098
  • 2
  • 23
  • 42
0

I faced on same issue, but I resolved it by using django.test.TransactionTestCase instead of django.test.TestCase.

Venus713
  • 1,441
  • 12
  • 24