Yash Narwal
Yash Narwal

Reputation: 111

atomic transaction is not working django rest

I am using DRF, API View Class based view, post method. Parameters are : file Logic : Do some validation on file and save file progressively (different type of objects) I am trying to rollback the transaction if exception happens while saving the rest of the file. I set 'ATOMIC_REQUESTS': True

class SaveXMlFile(APIView):
   authentication_classes = [TokenAuthentication]
   permission_classes = [IsAuthenticated]
   parser_classes = [FormParser, MultiPartParser]

   def post(self, request):
      """
          Save xml file
          ---
          # inputs
          parameters:
              - name: game_log_file
                description: Game log file
                type: file
                required: true
                paramType: post
                allowMultiple: false
      """
      try:
          # import pdb; pdb.set_trace()
          game_log_file = request.data['game_log_file']
          file_store = FileStore.objects.create(uploaded_file=game_log_file)
          xml_file_processing = ProcessXmlFile(file_store)
          already_saved = xml_file_processing.was_file_saved()
          player_exists = xml_file_processing.player_exists()
          if already_saved:
              file_store.delete()
              return Response({"info": "File was saved previously, no action taken place this time."}, status=200)
          if not player_exists:
              file_store.delete()
              return Response({"info":  "No player exists in the database, ask your administrator to create some."}, status=200)
          xml_file_processing.save()
          file_store.delete()
          return Response({"success": "File has been saved."}, status=status.HTTP_201_CREATED)
      except Exception as err:
          error = "{0}".format(str(err))
          return JsonResponse({'exception': error}, status=500)

I am deliberately throwing exceptions when half of the file has been saved but committed transactions don't rollback even exception is raised in the process.

Any thoughts would be appreciated.

Upvotes: 2

Views: 5001

Answers (4)

Kartik Setia
Kartik Setia

Reputation: 43

One thing that has worked for me is to roll back the transaction in the except block. Consider this example inspired by your code snippet -

from django.db import transaction #--> import this

class SaveXMlFile(APIView):
  authentication_classes = [TokenAuthentication]
  permission_classes = [IsAuthenticated]
  parser_classes = [FormParser, MultiPartParser]
 
  @transaction.atomic # --> add this
  def post(self, request):
    try:
      # import pdb; pdb.set_trace()
      game_log_file = request.data['game_log_file']
      file_store = FileStore.objects.create(uploaded_file=game_log_file)
      xml_file_processing = ProcessXmlFile(file_store)
      already_saved = xml_file_processing.was_file_saved()
      player_exists = xml_file_processing.player_exists()
      if already_saved:
        file_store.delete()
        return Response({"info": "File was saved previously, no action taken place this time."}, status=200)
      if not player_exists:
        file_store.delete()
        return Response({"info":  "No player exists in the database, ask your administrator to create some."}, status=200)
      xml_file_processing.save()
      file_store.delete()
      return Response({"success": "File has been saved."}, status=status.HTTP_201_CREATED)
    except Exception as err:
     transaction.set_rollback(True) # --> add this
     error = "{0}".format(str(err))
     return JsonResponse({'exception': error}, status=500)

The logic behind this is that once you do try and except even though you have marked it atomic, the code thinks you might have handled the error and doesn't rollback automatically. So, you have to do it explicitly hence marking set_rollback to true.

Upvotes: 0

emanuel sanga
emanuel sanga

Reputation: 945

For visitors from the future(Assuming time travels)

As a rule of thumb...

Never use try...catch when doing transactions!! Because, with transactions, you are trying to solve the problem of inconsistencies.... But, with try...catch, you are hiding inconsistencies such that a transaction fails to roll back.

Upvotes: 3

toudi
toudi

Reputation: 844

There are two independent things here. First of all, DRF will raise a 500 response by itself, when an error occurs (so there is no need to catch exception and raise a 500 explicitely). Second of all, you may also consider applying method_decorator like so:

from django.utils.decorators import method_decorator
from django.db.transaction import atomic

@method_decorator(atomic, name='dispatch')
class MyView(APIView):
  ....

The main rationale behind this is that using ATOMIC_REQUESTS set to True may not always be desirable due to performance penalty, but you still may want to wrap some particular views in atomic block. Of course in this case you could have used

with atomic():
  ... your code

because you have access to the method itself, but that is a bit of a digress :)

hope this helps

Upvotes: 3

Linovia
Linovia

Reputation: 20986

You should read a bit more about how transactions work with Django. Since you are catching the exception, Django will see that everything went fine and will commit the transaction, no matter what's your response code. Taken from https://docs.djangoproject.com/en/1.10/topics/db/transactions/#tying-transactions-to-http-requests:

It works like this. Before calling a view function, Django starts a transaction. If the response is produced without problems, Django commits the transaction. If the view produces an exception, Django rolls back the transaction.

So since you're catching the exception and returning an response, Django sees no reason to perform a rollback.

Upvotes: 6

Related Questions