Django 4.1添加路径参数后出现404路径不匹配问题求助
问题背景
使用Django 4.1和DRF开发,现有Product模型如下:
class Product(models.Model): product_id = models.CharField( blank=False, null=False, max_length=50, verbose_name="stock ID", ) name = models.CharField(max_length=30, blank=False) price = models.IntegerField(blank=False) availability = models.IntegerField(verbose_name="product availability") timestamp = models.DateTimeField(auto_now=True)
其中product_id为非主键字段,同一product_id对应多条产品记录(不同时间/价格)。
配置路由后,调用接口http://127.0.0.1:8000/api/product/2048cf23-6bb2-4532-a140-b13dd1361c4/时出现404错误。尝试过<str:product_id>、<uuid:product_id>等路径转换器均无效;仅将路由改为router.register("product", productViewset, basename="productPrice")时能正常访问,但无法通过product_id过滤查询集。
错误日志显示生成的路由存在格式问题(如^product/<slug:product_id>//$),当前请求路径无法匹配任何规则。
错误原因
DRF的DefaultRouter会自动为注册的ViewSet生成list(列表)和detail(详情)路由,detail路由默认基于模型主键pk。你在router.register的prefix中直接添加<slug:product_id>,会导致:
- 生成的路由出现多余斜杠(如
product/<slug:product_id>//),路径格式不符合请求URL; product_id被当作路由前缀的一部分,无法被ViewSet识别为查询参数;- 默认的detail路由仍要求传入
pk参数,与你期望的按product_id过滤逻辑冲突。
解决方案
方案一:通过查询参数过滤(最简单)
保持基础路由配置不变,在ViewSet中重写get_queryset方法,通过URL查询参数过滤结果:
- 路由配置(product/urls.py):
router = routers.DefaultRouter() router.register("product", productViewset, basename="productPrice") urlpatterns = [ path("", include(router.urls)), ]
- ViewSet代码:
from rest_framework import viewsets from .models import Product from .serializers import ProductSerializer class ProductViewSet(viewsets.ModelViewSet): queryset = Product.objects.all() serializer_class = ProductSerializer def get_queryset(self): queryset = super().get_queryset() # 获取URL中的product_id查询参数 product_id = self.request.query_params.get("product_id") if product_id: queryset = queryset.filter(product_id=product_id) return queryset
访问方式:http://127.0.0.1:8000/api/product/?product_id=2048cf23-6bb2-4532-a140-b13dd1361c4
方案二:自定义ViewSet动作(匹配期望路径)
通过DRF的@action装饰器自定义路由,实现/api/product/{product_id}/格式的路径访问:
路由配置同方案一;
ViewSet代码:
from rest_framework import viewsets, response from rest_framework.decorators import action from .models import Product from .serializers import ProductSerializer class ProductViewSet(viewsets.ModelViewSet): queryset = Product.objects.all() serializer_class = ProductSerializer # 自定义action,detail=False表示非详情路由(返回多条记录) @action(detail=False, methods=["get"], url_path=r"(?P<product_id>[^/.]+)") def by_product_id(self, request, product_id=None): # 按product_id过滤查询集 queryset = self.queryset.filter(product_id=product_id) serializer = self.get_serializer(queryset, many=True) return response.Response(serializer.data)
访问方式:http://127.0.0.1:8000/api/product/2048cf23-6bb2-4532-a140-b13dd1361c4/,与你期望的路径完全匹配。
方案三:手动配置路由(脱离DefaultRouter)
如果不需要ViewSet的默认路由,可以直接手动定义路径和视图:
- product/urls.py配置:
from django.urls import path from .views import ProductByIDView urlpatterns = [ path("product/<str:product_id>/", ProductByIDView.as_view(), name="product-by-id"), # 可添加其他路由 ]
- 视图代码:
from rest_framework.views import APIView from rest_framework.response import Response from .models import Product from .serializers import ProductSerializer class ProductByIDView(APIView): def get(self, request, product_id): products = Product.objects.filter(product_id=product_id) serializer = ProductSerializer(products, many=True) return Response(serializer.data)
访问方式同方案二,路径完全匹配。
内容的提问来源于stack exchange,提问作者Menna Magdy

