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未涉及的处理环节
- 完整的中间件执行链:Factory直接构造请求对象,不会触发认证、会话、自定义中间件等逻辑,而真实请求会经过所有中间件,可能修改请求或用户状态。
- 真实HTTP请求解析:Factory无需经过WSGI服务器(如Gunicorn)的参数解析,而真实请求需要服务器解析HTTP参数,可能存在参数处理差异。
- 会话与Cookie处理:真实请求可能携带Cookie,导致用户状态与Factory模拟的匿名用户不同,即使权限为
AllowAny,用户身份也可能影响逻辑分支。 - 数据库连接上下文:Factory使用的数据库连接与真实请求的连接可能处于不同事务上下文(比如测试环境自动提交事务,生产环境事务处理逻辑不同)。
内容的提问来源于stack exchange,提问作者cruise
相关产品推荐
相关产品推荐

