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

Django ViewSet在Shell/测试与实际请求中返回数据不一致排查

排查Django ViewSet真实请求返回空结果的问题

先理清楚背景:我们有一套用于存储用户营销邮件接收设置的模型,视图逻辑在单元测试和Django Shell模拟请求中完全正常,但通过浏览器或curl发起真实请求时,却会时不时返回空结果,已经排除了缓存和代理的影响。

模型与视图代码

模型结构

class Contact(models.Model):
    receive_marketing = models.DateTimeField(null=True, blank=True)

class User(models.Model):
    email_hash = models.CharField(max_length=255)
    contact = models.ForeignKey(Contact)

这里用纯字母数字格式的email_hash标识用户,防止篡改他人设置。

视图逻辑

class ContactViewSet(viewsets.ModelViewSet):
    queryset = Contact.objects.all()
    permission_classes = (AllowAny, )

    def get_queryset(self):
        if self.request.user.is_staff:
            return ContactViewSet.queryset
        token = self.request.query_params.get('ref')
        return Contact.objects.filter(user__email_hash__isnull=False, user__email_hash=token).distinct()

逻辑很明确:员工可访问所有Contact数据;普通用户通过请求参数ref匹配User的email_hash,获取对应的设置条目。

验证情况

  • 单元测试全覆盖了员工/非员工、有无ref参数等场景,全部通过。
  • Django Shell模拟请求完全正常:
>>> from user.views import ContactViewSet
>>> from rest_framework.test import APIRequestFactory
>>> factory = APIRequestFactory()
>>> view = ContactViewSet.as_view({'get': 'list'})
>>> request = factory.get('/contact/?ref=a-valid-hash')
>>> view(request).rendered_content
b'{"count":1,"next":null,"previous":null,"results":[{"id":222,"receive_marketing":"2018-05-15T14:11:13.449719Z"}]}'

异常现象

但用浏览器或curl发起完全相同的请求时,会反复(非持续)返回空结果:

$ curl http://user-service-url/contact/?ref=a-valid-hash
{"count":0,"next":null,"previous":null,"results":[]}

排查方向

1. 确认请求参数的一致性

虽然email_hash是纯字母数字,但仍要排查真实请求与模拟请求的参数差异:

  • 在get_queryset开头加日志,打印token的原始值、长度、大小写,对比真实请求与Shell中使用的a-valid-hash是否完全一致。
  • 检查curl或浏览器发送的ref是否存在隐性编码问题(比如意外的空格、大小写错误)。

2. 检查请求的用户状态

APIRequestFactory默认创建匿名用户,但真实请求的用户状态可能不同:

  • 浏览器中是否有已登录的用户会话?即使permission_classes设为AllowAny,self.request.user也可能不是匿名用户。若登录用户非员工且其email_hash与ref不匹配,就会返回空结果。
  • 在get_queryset中打印self.request.user的详细信息(is_authenticated、is_staff、关联的email_hash),对比真实请求与模拟请求的用户状态。

3. 数据库事务与隔离级别问题

问题时有时无,大概率和数据库事务有关:

  • 检查是否有其他操作在修改User或Contact数据(比如事务未提交时,查询无法匹配到数据)。
  • 打印实际执行的SQL语句(在过滤后添加print(queryset.query)),对比真实请求与Shell中执行的SQL是否完全一致,是否存在隐性过滤条件。

4. 中间件的影响

APIRequestFactory不会触发完整的Django中间件链,但真实请求会经过所有注册的中间件:

  • 排查是否有中间件在修改请求的query_params(比如参数清洗、转义或过滤)。
  • 临时禁用非必要的中间件,测试是否还会出现空结果,逐步定位问题中间件。

5. 并发竞态条件

若服务存在并发流量,可能出现竞态条件:

  • 比如多个请求同时修改和查询同一条数据,导致查询时数据状态不一致。
  • 查看数据库的隔离级别,是否需要调整为更合适的级别(比如读已提交)。

APIRequestFactory未涉及的处理环节

  1. 完整的中间件执行链:Factory直接构造请求对象,不会触发认证、会话、自定义中间件等逻辑,而真实请求会经过所有中间件,可能修改请求或用户状态。
  2. 真实HTTP请求解析:Factory无需经过WSGI服务器(如Gunicorn)的参数解析,而真实请求需要服务器解析HTTP参数,可能存在参数处理差异。
  3. 会话与Cookie处理:真实请求可能携带Cookie,导致用户状态与Factory模拟的匿名用户不同,即使权限为AllowAny,用户身份也可能影响逻辑分支。
  4. 数据库连接上下文:Factory使用的数据库连接与真实请求的连接可能处于不同事务上下文(比如测试环境自动提交事务,生产环境事务处理逻辑不同)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 04:22:18