Deepstop
Deepstop

Reputation: 3817

Replacing django decorator commit_manually with non_atomic_requests

I have a Django view which imports an Excel file, and if exceptions occur I would like to catch them and report them all, and roll back any saves. I'm getting a TransactionManagementError even though I have used the non_atomic_requests decorator.

  1. As I'm also using the login_required decorator I thought they might be interfering with each other. First I reversed the order then I removed the login required. No change.

  2. I've tried disabling automatic transactions globally. Maybe I didn't do it right but it's not the solution I want anyway.

  3. I removed the offending line of code (see below) but the same error occurred when I tried to rollback

It is running on Python 3.7.3 with the latest Django and using SQLlite. I'm running it as a unit test right now, although perhaps I'm abusing the term. Suffice to say it's running as a Django TestCase.

@transaction.non_atomic_requests
@login_required(login_url='/accounts/login/?next=/finance/gl_upload/')
def gl_upload(request):
    transaction.set_autocommit(False)
    if upriv(request.user, ['admin', 'finance']) == 'admin':
        if request.method == 'POST':

... file processing here ...

                except Exception as e:
                    errs.append(format('Exception "{1}" at row {0}\n'.format(p['rownum'], e)))
                if errs:
                    transaction.rollback()
                    rows_deleted = 0
                    rows_inserted = 0
                    print(''.join('Error: {0}\n'.format(e) for e in errs))
                else:
                    transaction.commit()
                    rows_deleted = Gldata.objects.filter(item='Actual', period_gte=older, period_lte=newest).delete()
                    rows_inserted = Gldata.objects.filter(item=temp_item).update(item='Actual')
                transaction.set_autocommit(True)
                print('Deleted: {0}, inserted: {1}'.format(rows_delete, rows_inserted))
                return render(request, 'gl_upload.html', {'inserted': rows_inserted, 'removed': rows_deleted, 'errors': errs})
            else:
                return render(request, 'gl_upload.html', {'form': form})
        else:
            form = uploadForm()
            return render(request, 'gl_upload.html', {'form': form})

I receive a TransactionManagementError on the set_autocommit indicating that an atomic block is active even though I understood that the decorator would disable it. A few years back I used the old commit_manually decorator which worked just fine.

File "C:\Users\csullivan\responsive\finance\views.py", line 25, in gl_upload transaction.set_autocommit(False) File "C:\Users\csullivan\responsive\env\lib\site-packages\django\db\transaction.py", line 30, in set_autocommit return get_connection(using).set_autocommit(autocommit) File "C:\Users\csullivan\responsive\env\lib\site-packages\django\db\backends\base\base.py", line 394, in set_autocommit self.validate_no_atomic_block() File "C:\Users\csullivan\responsive\env\lib\site-packages\django\db\backends\base\base.py", line 433, in validate_no_atomic_block "This is forbidden when an 'atomic' block is active.") django.db.transaction.TransactionManagementError: This is forbidden when an 'atomic' block is active.

Upvotes: 2

Views: 688

Answers (1)

Tomasz Zieliński
Tomasz Zieliński

Reputation: 16367

I think that the atomic() decorator/context manager provides exactly what you need --it commits the transaction if the underlying code succeeds and rolls back when there's an exception.

So in your case I would just do:

with atomic():
  process_xls_files()

You shouldn't need to call transaction.set_autocommit() and similar low-level methods manually, unless you have very specific needs and the "normal" transaction handling is not enough.

Upvotes: 1

Related Questions