Django类视图如何传递pk参数到queryset过滤关联账户数据
问题根源
类视图中无法获取到账户pk参数、查询返回全量数据的核心原因有三点:
- 你使用
DefaultRouter注册IdentifierViewSet时,默认生成的列表接口路径为/api/portfolio/,路径中本身就没有携带账户主键参数 - 路由器默认生成的详情路径
/api/portfolio/<pk>/中的pk是Identifier模型自身的主键,并非关联的Account账户主键,直接取self.kwargs["pk"]要么取不到值,要么取错字段 - 自定义的
query_identifier_by_args函数内部直接从全量表查询数据,完全没有用到过滤后的结果集,就算路由参数取值正确,也不会生效
修复方案
1. 调整路由配置,显式传入账户主键参数
修改Urls.py中的路由注册逻辑,把账户pk作为路径参数加入接口前缀,注意参数名不要和模型默认的pk重名:
from . import views from rest_framework.routers import DefaultRouter from apps.portfolio.views import IdentifierViewSet router = DefaultRouter() # 显式在路径中加入account_pk参数,指定basename避免路由名生成冲突 router.register(r'account/(?P<account_pk>[^/.]+)/portfolio', IdentifierViewSet, basename='portfolio') urlpatterns = [ path('portfolios/', views.portfolios, name="portfolios"), path('portfolio/<str:pk>/', views.portfolio, name="portfolio"), path('api/', include(router.urls)), ]
调整后,Identifier列表接口的路径为/api/account/<账户pk>/portfolio/,路径中明确携带需要过滤的账户主键。
2. 修改数据查询逻辑,基于账户参数过滤
首先修改自定义的query_identifier_by_args函数,接收外部传入的已过滤基础结果集,不要直接查询全量表,同时修复参数缺省时的报错问题:
def query_identifier_by_args(base_queryset, **kwargs): draw = int(kwargs.get('draw', [0])[0]) length = int(kwargs.get('length', [10])[0]) start = int(kwargs.get('start', [0])[0]) search_value = kwargs.get('search[value]', [''])[0] order_column = kwargs.get('order[0][column]', ['0'])[0] order = kwargs.get('order[0][dir]', ['asc'])[0] order_column = ORDER_COLUMN_CHOICES[order_column] # django orm 前加'-'代表倒序 if order == 'desc': order_column = '-' + order_column queryset = base_queryset total = queryset.count() if search_value: queryset = queryset.filter(Q(id__icontains=search_value) | Q(code__icontains=search_value) | Q(code_type__icontains=search_value) | Q(created__icontains=search_value) | Q(updated__icontains=search_value) | Q(valid__icontains=search_value)) count = queryset.count() queryset = queryset.order_by(order_column)[start:start + length] return { 'items': queryset, 'count': count, 'total': total, 'draw': draw }
再修改IdentifierViewSet类,移除硬编码的全量查询配置,在get_queryset方法中取出路由参数做过滤,同时在list方法中传入过滤后的基础结果集:
from django.shortcuts import get_object_or_404 from rest_framework import viewsets, status from rest_framework.response import Response class IdentifierViewSet(viewsets.ModelViewSet): # 移除硬编码的全量queryset配置,所有查询走get_queryset动态生成 serializer_class = IdentifierSerializer authentication_classes = [] def get_queryset(self): account_pk = self.kwargs.get("account_pk") # 校验账户是否存在,不存在直接返回404 account = get_object_or_404(Account, id=account_pk) # 仅返回当前账户关联的Identifier数据 return Identifier.objects.filter(account=account) def list(self, request, *args, **kwargs): try: # 先获取已经按账户过滤的基础结果集 base_qs = self.get_queryset() identifier = query_identifier_by_args(base_qs, **request.query_params) serializer = IdentifierSerializer(identifier['items'], many=True) result = { 'data': serializer.data, 'draw': identifier['draw'], 'recordsTotal': identifier['total'], 'recordsFiltered': identifier['count'] } return Response(result, status=status.HTTP_200_OK) except Exception as e: return Response(str(e), status=status.HTTP_404_NOT_FOUND)
可选替代方案
如果你不想修改现有路由结构,也可以把账户pk作为查询参数传递,前端请求时拼接为/api/portfolio/?account_pk=xxx,此时不需要修改路由,只需要把get_queryset中的取值逻辑改为从请求参数中获取即可:
def get_queryset(self): account_pk = self.request.query_params.get("account_pk") account = get_object_or_404(Account, id=account_pk) return Identifier.objects.filter(account=account)
这种方式实现更简单,但语义不如路径参数清晰,可根据业务场景选择。
内容的提问来源于stack exchange,提问作者rob
相关产品推荐
相关产品推荐

