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
intransaction.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.