DRF ViewSet始终返回HTTP 200,无法触发404问题求助
问题分析与解决方案
你的核心问题是路由与视图方法的匹配逻辑不匹配,导致请求始终进入list方法而非retrieve方法,具体原因和解决方法如下:
为什么当前代码不生效?
- DefaultRouter的默认路由规则:使用
DefaultRouter注册ViewSet时,默认生成的retrieve接口路径是accounts/<pk>/,这里的<pk>是URL路径参数,而非你传入的Query参数或Headers。你把邮箱作为Params/Headers传递时,请求路径还是accounts/,DRF会判定这是list请求,直接执行list方法返回所有用户,根本没走到retrieve逻辑。 - 视图方法参数的来源错误:你的
retrieve方法定义了email=None参数,但这个参数是从URL路径中获取的,不是Query参数或Headers,所以你传递的邮箱值根本不会被该方法接收。
解决方案
方案一:使用GenericViewSet配置lookup_field(推荐)
借助GenericViewSet的内置能力,指定用email作为资源查找字段,让Router自动生成正确的路由:
from rest_framework import viewsets from rest_framework.response import Response from .models import User from .serializers import AccountSerializer class AccountViewSet(viewsets.GenericViewSet): queryset = User.objects.all() serializer_class = AccountSerializer lookup_field = 'email' # 指定用email作为查找标识 def list(self, request): serializer = self.get_serializer(self.get_queryset(), many=True) return Response(serializer.data) def retrieve(self, request, email=None): user = self.get_object() # 自动按lookup_field查找,不存在则返回404 serializer = self.get_serializer(user) return Response(serializer.data)
此时Router会生成accounts/<email>/的路由,请求时需将邮箱放在URL路径中,例如:
http GET http://127.0.0.1:8000/api/accounts/test@example.com/
- 存在的邮箱:返回单个用户数据
- 不存在的邮箱:自动返回404
方案二:手动自定义路由
如果不想使用GenericViewSet,可以手动添加路由覆盖默认的retrieve规则:
from django.urls import path from rest_framework.routers import DefaultRouter from .views import AccountViewSet router = DefaultRouter() router.register(r'accounts', AccountViewSet, basename='accounts') # 添加自定义的retrieve路由,接收email作为路径参数 urlpatterns = router.urls + [ path('accounts/<str:email>/', AccountViewSet.as_view({'get': 'retrieve'}), name='account-retrieve'), ]
请求方式同方案一,将邮箱放在URL路径中即可触发retrieve方法。
额外说明
如果你的需求是通过Query参数(如accounts/?email=xxx)获取单个用户,这种方式不符合RESTful规范,但可以在list方法中处理:
def list(self, request): email = request.query_params.get('email') if email: user = get_object_or_404(User, email=email) serializer = AccountSerializer(user) return Response(serializer.data) # 没有传email时返回所有用户 queryset = User.objects.all() serializer = AccountSerializer(queryset, many=True) return Response(serializer.data)
内容的提问来源于stack exchange,提问作者Omid Shojaee
相关产品推荐
相关产品推荐

