brandonbanks
brandonbanks

Reputation: 1295

How to rollback transactions in a loop django

I'm trying to rollback a group of transactions if there are exceptions thrown in the loop. But I don't want to break out of the loop or throw an exception without catching it.

I don't want the business logic to be saved if any of the children in the loop throw an exception. So that means I can't put the transaction inside the loop because if any of them fails it would only rollback the transaction for the specific child.

parent = Parent.objects.get(pk='something')
exceptions = []
with transaction.atomic():
    for child in parent.children.all():
        try:
            # business logic which also saves other models
            # I don't want this saved if there is an exception for any object in the loop
        except Exception as e:
            exceptions.append({
                'id': child.id,
                'error': str(e),
            })
if len(exceptions) > 0:
    transaction.set_rollback(True)
    for exception in exceptions:
        Child.objects.filter(pk=exception['id']) \
            .update(error=exception['error']
    # more business logic and raise exception
    parent.is_blocked = True
    parent.save()
    # I don't want this exception to rollback all transactions
    raise Exception('Parent {} is blocked'.format(parent.id))

I'm getting an error with the above code. The message is pretty straight forward. I'm trying to rollback the transaction outside of the block.

django.db.transaction.TransactionManagementError: The rollback flag doesn't work outside of an 'atomic' block.

Has anyone found a way to handle something like this. I hope I'm just missing something simple. Please let me know if you need more information.

Upvotes: 3

Views: 2221

Answers (2)

Fogmoon
Fogmoon

Reputation: 569

Avoid catching exceptions inside atomic!

Follow the document, and with your special case, your code should be like this:

parent = Parent.objects.get(pk='something')
exceptions = []
try:
    with transaction.atomic():
        for child in parent.children.all():
            try:
                # business logic which also saves other models
                # I don't want this saved if there is an exception for any object in the loop
            except Exception as e:
                exceptions.append({
                    'id': child.id,
                    'error': str(e),
                })
        # raise exception handly to trigger rollback
        if len(exceptions) > 0:
            raise("raise for rollback")
except Exception as e:
    pass

if len(exceptions) > 0:
    for exception in exceptions:
        Child.objects.filter(pk=exception['id']) \
            .update(error=exception['error']
    # more business logic and raise exception
    parent.is_blocked = True
    parent.save()
    # I don't want this exception to rollback all transactions
    raise Exception('Parent {} is blocked'.format(parent.id))

Upvotes: 1

Samuel Omole
Samuel Omole

Reputation: 185

You can try a generator function:

def function():
    for child in parent.children.all():
        try:
            yield result
        except Exception as e:
            yield exception

You can check out this answer for clarity: How to handle error thrown in a generator function

Upvotes: 0

Related Questions