Why this inner atomic block not rolled back in a viewflow custom view code
I created this custom create process view to add in logged in user's email to the saved object, I also added a transaction block so that when the next step fails (inside self.activation_done()
statement) it will roll back the db changes inside form_valid()
and then display the error in the start step, so no new process instance is saved.
from django.core.exceptions import ValidationError
from django.db import transaction
from django.http import HttpResponseRedirect
from viewflow.flow.views import CreateProcessView, StartFlowMixin
class StarterEmailCreateProcessView(CreateProcessView):
"""
Sets the user email to the process model
"""
def form_valid(self, form, *args, **kwargs):
"""If the form is valid, save the associated model and finish the task."""
try:
with transaction.atomic():
super(StartFlowMixin, self).form_valid(form, *args, **kwargs)
# begin of setting requester email
# https://github.com/viewflow/viewflow/issues/314
self.object.requester_email = self.request.user.email
self.object.save()
# end of setting requester email
self.activation_done(form, *args, **kwargs)
except Exception as ex:
form.add_error(None, ValidationError(str(ex)))
return self.form_invalid(form, *args, **kwargs)
return HttpResponseRedirect(self.get_success_url())
Based on Django documentation, exception raised inside the transaction is rolled back
However, upon form displays the error message, new process instances are still saved, so I suspect the transaction is not rolled back and I don't know why.
The self.lock.__enter__()
looked suspicious without exiting, so I changed it to become the following and it rolled back as expected
with transaction.atomic(savepoint=True):
signals.task_started.send(sender=self.flow_class, process=self.process, task=self.task)
self.process.save()
lock_impl = self.flow_class.lock_impl(self.flow_class.instance)
self.lock = lock_impl(self.flow_class, self.process.pk)
# self.lock.__enter__()
with self.lock:
self.task.process = self.process
self.task.finished = now()
self.task.save()
signals.task_finished.send(sender=self.flow_class, process=self.process, task=self.task)
signals.flow_started.send(sender=self.flow_class, process=self.process, task=self.task)
self.activate_next()
@kmmbvnr can you please verify? Is there going to be any unintended consequences after this change?