I have a view that saves multiple forms on three related models. One of the models that has to be saved later can fail due IntegrityErrors. I want to make sure that that if those later models fail, the entire transaction is rolled back. Should I do this with a try: ... except: transaction.rollback() or with transaction.atomic():. I’ve read several post but it’s not clear what should I do to make sure that the entire transaction succeeds or fails, without dangling operations.
class DocumentCreationView(View):
def post(self, request):
transaction_form = DocumentCreationForm(request.POST)
bins_formset = BinsFormset(
request.POST,
prefix='bins_set')
if transaction_form.is_valid():
transaction_instance = transaction_form.save(commit=False)
transaction_instance.sending_warehouse = self.creating_warehouse
# Change for user
transaction_instance.started_by = transaction_instance.sending_contact
transaction_instance.save()
if bins_formset.is_valid():
bins = bins_formset.save(commit=False)
for bin_instance in bins:
bin_instance.parent_transaction = transaction_instance
bin_instance.located_at = transaction_instance.recieving_warehouse
# This can fail if the serial_range already exist for the series and type.
bin_instance.save()
Right now the results is that the transaction model is saved, while the bin model fails.
Also that would indeed allow me to prevent the user from getting the integrity error message, but won’t allow me to inform the user what’s wrong and how they can fix it. The returned IntegrityError doesn’t have any structured info that would allow me to tell them “fix these fields, they overlap”.
My reason is that you moved the logic for performing the updates out to a different function. That tends to make it more awkward to coordinate the efforts between the two. You could do this in that function and return the details, but then you have to check the return values you get back to figure out what that function has returned to you. (Or, you could update values in self and check those.)
I don’t actually know to what level of detail that sort of information is even available. There are many different reasons why the transaction may fail, and not all of them are necessarily under the user’s control. (e.g. a dropped database connection)
If you need that degree of “fine-grained” control, you might end up needing to control the transaction yourself. Create your own transaction, and catch any exception at each SQL statement. If an error occurs, save the details of the error, rollback the transaction, and return.
I’ve been reading about personalizing the violation_error_message of ExclusionConstraint which would be more desirable since I would only need to handle that one. Maybe I should expand it to include something I could read from my view. I’m just trying to figure out how to get the original names/values used in the default message and add some decorators using those. Found this but it isn’t straight forward for me to manage it.