Reputation: 620
We are trying to migrate from commit_manually
to atomic
so we can upgrade Django to at least 1.8 in a legacy project. In most of cases we need to do something like that:
with transaction.atomic():
obj = Entity.objects.select_for_update().get(pk=pk)
try:
obj.do_something()
obj.set_some_status()
obj.save()
except SomeException:
obj.set_failed_flag()
obj.save()
raise
becuase the callee needs this exception information to continue with the certain flow. But in this case the transaction/savepoint will be rolled back and that's not what we want since we want obj.set_failed_flag()
to be committed. Also it seems logical to set it inside the same atomic block since we already have a locked row for this object.
Any ideas/patterns? Thanks in advance!
P.S. It was so easy with old manual transaction management!
P.P.S. We use exceptions also for "early-exit" and moving to some flags etc. would bring a log of mess and I personally would love to avoid it.
Upvotes: 4
Views: 5927
Reputation: 3968
Besides the answer already posted, in case of having nested methods where you start the atomic block somewhere higher in the chain, it helps to use the following:
transaction.on_commit(lambda: method_that_raises_exception())
This way the exception is raised after the transaction has been committed.
Upvotes: 3
Reputation: 48982
Assuming that SomeException
isn't a database exception, you can just save it and raise it outside the atomic block:
with transaction.atomic():
obj = Entity.objects.select_for_update().get(pk=pk)
try:
obj.do_something()
obj.set_some_status()
except SomeException as e:
obj.set_failed_flag()
exception = e
else:
exception = None
obj.save()
if exception:
raise exception
If you find this too verbose and need to do it frequently, you might be able to write a context manager that acts as a proxy for transaction.atomic()
but doesn't trigger a rollback in certain cases.
Finally, note that Django still has manual transaction management.
Upvotes: 5