Django REST Framework URL无法映射到模型实例视图问题
问题:Django REST Framework + MongoDB 获取单条数据返回404,
get_object方法未执行 我有一个基于Django + DRF的应用,使用MongoDB(Djongo/pymongo)作为数据库,API路径设计为/api/v1/<object name>[/id]。目前GET /api/v1/whs可以正常返回Wh对象列表,但调用GET /api/v1/whs/640444031170f24828f324bc/时,不仅get_object方法里的打印语句没执行,还返回404错误,且该ObjectId对应的文档确实存在于数据库中。返回内容如下:
HTTP 404 Not Found Allow: GET, PUT, PATCH, DELETE, HEAD, OPTIONS Content-Type: application/json Vary: Accept { "detail": "Not found." }
相关代码
settings.py 核心配置
INSTALLED_APPS = [ ... 'rest_framework', ... ] REST_FRAMEWORK = { 'DEFAULT_PERMISSION_CLASSES': [ 'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly' ], 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', 'PAGE_SIZE': 10 }
models.py
from django.db import models from djongo.models import fields from django.contrib.auth.models import AbstractUser from django.contrib.auth.hashers import make_password CHARFIELD_MAX_LENGTH = 1024 class AbstractModel(models.Model): _id = fields.ObjectIdField() external_id = models.CharField(max_length = CHARFIELD_MAX_LENGTH, null = True, blank = True,) options = models.JSONField(null = True, blank = True,) created_at = models.DateTimeField(auto_now_add = True, null = False, blank = False,) updated_at = models.DateTimeField(null = True, blank = True,) class Meta: abstract = True ordering = ['created_at'] def id(self): return self._id class Wh(AbstractModel): name = models.CharField(max_length = CHARFIELD_MAX_LENGTH, null = False, blank = False,) address = models.CharField(max_length = CHARFIELD_MAX_LENGTH, null = False, blank = False,) map = models.JSONField( null = False, blank = False,) parent_wh = models.ForeignKey('self', on_delete = models.CASCADE, related_name = 'nested_whs', null = True, blank = True,) operator = models.ForeignKey('User', on_delete = models.CASCADE, related_name = 'whs', null = True, blank = True,) class Meta(AbstractModel.Meta): abstract = False verbose_name = 'wh' verbose_name_plural = 'whs' def __str__(self): return '[Wh' + self.name + ']' class User(AbstractUser, AbstractModel): class Meta(AbstractUser.Meta): abstract = False verbose_name = 'user' verbose_name_plural = 'users' def __str__(self): return self.username
serializers.py
from .models import * from rest_framework import serializers class UserSerializer(serializers.ModelSerializer): class Meta: model = User exclude = ['password'] class WhSerializer(serializers.HyperlinkedModelSerializer): operator = UserSerializer() class Meta: model = Wh fields = '__all__'
views.py
from django.http import HttpResponseRedirect from django.shortcuts import render, redirect, get_object_or_404 from django.utils.translation import gettext as _ from .models import * from .forms import * from django.contrib.auth.decorators import login_required from bson import ObjectId from rest_framework import viewsets from rest_framework import permissions from rest_framework.generics import RetrieveAPIView from .serializers import * class UserViewSet(viewsets.ModelViewSet): queryset = User.objects.all() serializer_class = UserSerializer permission_classes = [permissions.IsAuthenticated] class WhViewSet(viewsets.ModelViewSet): queryset = Wh.objects.all() serializer_class = WhSerializer permission_classes = [permissions.IsAuthenticated] class WhDetail(RetrieveAPIView): queryset = Wh.objects.all() serializer_class = WhSerializer def get_object(self): id = self.kwargs.get('id') print(id) return get_object_or_404(self.get_queryset(), _id=ObjectId(id))
urls.py
from django.urls import include, path from rest_framework import routers from . import views router = routers.DefaultRouter() router.register(r'whs', views.WhViewSet) router.register(r'users', views.UserViewSet) urlpatterns = [ path('', views.home, name='home'), path('wh/<str:id>', views.wh, name='wh'), path('api/v1/', include(router.urls)), path('api-auth/', include('rest_framework.urls', namespace='rest_framework')), path('api/v1/whs/<str:id>', views.WhDetail.as_view(), name='whs'), ]
问题原因及解决办法
核心问题
- 路由优先级冲突:
DefaultRouter会自动为WhViewSet生成详情路由(/api/v1/whs/<pk>/),且include(router.urls)在你手动添加的路由前面,导致请求被WhViewSet先拦截,你的WhDetail视图根本没被触发。 - 主键字段不匹配:DRF默认用
pk作为URL参数名查询数据,但你的模型主键是MongoDB的_id,WhViewSet无法正确将URL中的pk映射到_id字段,导致查询失败返回404。
最优解决方案(无需额外视图)
直接修改WhViewSet,适配MongoDB的_id主键,删除冗余的WhDetail视图和手动路由:
1. 更新views.py中的WhViewSet
class WhViewSet(viewsets.ModelViewSet): queryset = Wh.objects.all() serializer_class = WhSerializer permission_classes = [permissions.IsAuthenticated] lookup_field = '_id' # 指定模型主键字段为_id lookup_value_regex = '[0-9a-fA-F]{24}' # 匹配MongoDB ObjectId的正则规则 def get_object(self): # 手动将URL中的_id转换为bson.ObjectId类型 queryset = self.get_queryset() filter_kwargs = {self.lookup_field: ObjectId(self.kwargs[self.lookup_field])} obj = get_object_or_404(queryset, **filter_kwargs) self.check_object_permissions(self.request, obj) return obj
2. 清理urls.py
删除手动添加的path('api/v1/whs/<str:id>', views.WhDetail.as_view(), name='whs'),,保留router自动生成的路由即可。
备选方案(保留自定义视图)
如果一定要用WhDetail,需调整路由顺序让自定义路由先匹配:
urlpatterns = [ path('', views.home, name='home'), path('wh/<str:id>', views.wh, name='wh'), path('api/v1/whs/<str:id>', views.WhDetail.as_view(), name='whs'), # 移到router路由前面 path('api/v1/', include(router.urls)), path('api-auth/', include('rest_framework.urls', namespace='rest_framework')), ]
同时更新WhDetail视图,指定主键字段:
class WhDetail(RetrieveAPIView): queryset = Wh.objects.all() serializer_class = WhSerializer lookup_field = '_id' lookup_value_regex = '[0-9a-fA-F]{24}' def get_object(self): id = self.kwargs.get(self.lookup_field) print(id) return get_object_or_404(self.get_queryset(), _id=ObjectId(id))
额外注意
- 若使用
HyperlinkedModelSerializer,需确保序列化器能正确处理_id作为主键,或改用ModelSerializer(无需超链接字段时更简洁)。 - 确认请求用户拥有单条数据的访问权限,虽然列表接口正常,但单条数据的权限校验逻辑可能不同。
内容的提问来源于stack exchange,提问作者omega royal
相关产品推荐
相关产品推荐

