You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Django视图函数异常分支中更新模型并完成事务提交

问题根因

  1. @transaction.non_atomic_requests装饰器仅对Django视图函数生效,你将其装饰在普通业务函数obtain_tokens上完全不生效。如果你的项目数据库配置开启了ATOMIC_REQUESTS = True(多数Django项目默认开启该配置),整个generate_auth视图会被包裹在全局事务中。
  2. 内层with transaction.atomic()块抛出AuthenticationFailed异常时,即便你在外层捕获了异常,整个外层事务也会被Django标记为「仅回滚」状态,后续所有数据库写操作都会被直接丢弃,哪怕你执行了attempt.save()也不会真正写入数据库。
  3. 你创建AccessAttempt对象时,create方法已经自动执行了保存逻辑,后续额外调用的attempt.save()属于冗余操作。
  4. 原有视图代码里把返回状态的变量名和导入的status模块重名,会导致变量覆盖的隐藏bug。

修复步骤

  1. 把@transaction.non_atomic_requests装饰器移到views.py的视图函数上,移除obtain_tokens上的该装饰器,同时修复变量覆盖问题:
# views.py
from django.db import transaction
import json
from django.http import JsonResponse
from rest_framework import status

@transaction.non_atomic_requests
def generate_auth(request):
    b = json.loads(request.body)
    if b['username'] is None or b['password'] is None:
        return JsonResponse({'message': "Bad Request"}, status=status.HTTP_400_BAD_REQUEST)
    payload, res_status = obtain_tokens(request, b)
    return JsonResponse(payload, status=res_status)
  1. 修改obtain_tokens函数的异常分支逻辑,更新失败次数时使用独立原子块,配合F表达式避免并发竞态问题:
# validate_jwt_auth.py
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
from .models.access_attempt import AccessAttempt
from rest_framework import status
from rest_framework.exceptions import AuthenticationFailed
from django.db import transaction
from django.db.models import F

def obtain_tokens(request, credentials):
    attempt = AccessAttempt.objects.filter(username=credentials['username']).first()
    if attempt is None:
        # create方法已经自动保存,不需要额外调用save
        attempt = AccessAttempt.objects.create(
            username=credentials['username'],
            failures_since_start=0
        )
    if attempt.failures_since_start > 5:
        return {'message': "Failed too many times"}, status.HTTP_403_FORBIDDEN
    try:
        with transaction.atomic():
            payload = TokenObtainPairSerializer(context={'request':request}).validate(credentials)
            attempt.failures_since_start = 0
            attempt.save()
            return payload, status.HTTP_200_OK
    except AuthenticationFailed:
        # 独立事务更新失败次数,避免被内层异常导致的事务回滚标记影响
        with transaction.atomic():
            AccessAttempt.objects.filter(username=credentials['username']).update(
                failures_since_start=F('failures_since_start') + 1
            )
        return {'message': "Unauthorized"}, status.HTTP_401_UNAUTHORIZED

如果你不想开启非原子视图,也可以在内层异常捕获后先主动回滚事务再执行更新操作,效果一致:

except AuthenticationFailed:
    transaction.rollback()
    attempt.failures_since_start +=1
    attempt.save()
    return {'message': "Unauthorized"}, status.HTTP_401_UNAUTHORIZED

内容的提问来源于stack exchange,提问作者everspader

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.30 19:48:01