Reputation: 1295
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
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
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