How to persist model changes for specific models on transaction rollback in django atomic request

I have a Django project with ATOMIC_REQUESTS=True.

When authentication fails I need to store critical data for the login attempt, here is example code:

class LoginSerializer(serializers.Serializer):
  ...
  
  def validate(self, data):
    ...
    user = authenticate(email=email, password=password)
    if user is None:
      failed_login_attempt(email)
      raise serializers.ValidationError("Invalid credentials")


def failed_login_attempt(email):
  user = User.objects.get(email=email)
  user.lock_status = "LOCKED"
  user.save()

  Device.objects.create(user=user, ip="127.0.0.1", fingerprint="some-fingerprint")

  Activity.objects.create(user=user, type="failed_login_attempt")

Constraints:

Raising the ValidationError causes all changes (including updates to user.lock_status, Device and Activity) to be rolled back. This behavior is expected because of the global transaction but I need these changes to persist.

I cannot remove ATOMIC_REQUESTS=True as it's crucial for other parts of the application.

Goal:

I want to ensure that the failed_login_attempt function persists changes even if the ValidationError rolls back the rest of the transaction. What’s the best way to persist those critical changes?

What I've tried:

  • Wrapping failed_login_attempt in transaction.atomic() This doesn’t work because they are still part of the outer transaction.

  • Separate database configuration with ATOMIC_REQUESTS=False and custom Database Router as seen in this asnwer, however:

    This required broad permissions for other models (User, Device), which would have allowed writes outside of the global transaction. I couldn’t limit this approach to only the specific function (failed_login_attempt), leading to overly broad control.

Bypassing the ORM and using raw SQL connections.cursor() feels like a hack so I would prefer to avoid it.

It seems like you could solve this by using the django.db.transaction.non_atomic_requests decorator on each view where you want to persist changes.

This decorator will negate the effect of ATOMIC_REQUESTS for a given view.

See docs.

Вернуться на верх