如何使用Django REST Framework查询参数实现产品列表按名称过滤
问题根源分析
- 路由配置错误:DRF的
DefaultRouter注册视图集时会自动生成标准REST风格路由,你额外注册的Products/(?P<name>.+)/$规则会把路径中的值当作视图集默认retrieve方法的主键参数,触发单实例查询逻辑,根本不会走你写的get_queryset过滤代码,所以访问/api/Products/guitar/无结果。 - 查询参数报错原因:
- 直接用f字符串拼接RAW SQL存在SQL注入风险,且如果
name参数携带单引号等特殊字符会直接触发SQL语法错误 - 关联查询时未指定
name字段所属表,如果Product和Shop表都有name字段会触发数据库字段歧义错误 - 你访问时路径带的尾斜杠会被带入
name参数值,导致匹配不到对应数据
- 直接用f字符串拼接RAW SQL存在SQL注入风险,且如果
修复方案
第一步:修正路由配置
删除多余的路由注册,查询参数无需额外配置路由,DRF会自动从请求中提取:
router = routers.DefaultRouter() router.register(r'Products', views.ProductView, 'Products') urlpatterns = [ path('admin/', admin.site.urls), path('api/', include(router.urls)) ]
第二步:优化视图查询逻辑
优先使用Django ORM实现关联查询,避免手写RAW SQL的安全和语法问题,假设Product模型通过id_shop外键关联Shop模型:
def get_queryset(self): queryset = Product.objects.select_related('shop').all()[:12] name = self.request.query_params.get('name') if name is not None: # 去除参数末尾多余的斜杠 name = name.rstrip('/') queryset = Product.objects.select_related('shop').filter(name__icontains=name)[:12] return queryset
如果必须使用RAW SQL,一定要用参数化查询避免注入和语法错误:
def get_queryset(self): name = self.request.query_params.get('name') if name is not None: name = name.rstrip('/') # 用占位符传递参数,Django会自动转义特殊字符,同时明确指定name所属表 queryset = ProductJoinShop.objects.raw( 'select * from Product join Shop using (id_shop) where Product.name like %s limit 12', [f'%{name}%'] ) else: queryset = ProductJoinShop.objects.raw('select * from Product join Shop using (id_shop) limit 12') return queryset
第三步(可选):支持路径格式的查询接口
如果需要兼容/api/Products/guitar/这种路径传参的形式,给视图集添加自定义action即可:
from rest_framework.decorators import action from rest_framework.response import Response class ProductView(viewsets.ModelViewSet): serializer_class = ProductSerializer # 保留上面修改后的get_queryset方法 @action(detail=False, methods=['get'], url_path='(?P<name>.+)') def search_by_name(self, request, name=None): name = name.rstrip('/') queryset = Product.objects.select_related('shop').filter(name__icontains=name)[:12] serializer = self.get_serializer(queryset, many=True) return Response(serializer.data)
修改完成后,/api/Products/?name=guitar和/api/Products/guitar两种访问方式都可以正常返回过滤结果。
内容的提问来源于stack exchange,提问作者Rami16
相关产品推荐
相关产品推荐

