如何在Django REST Framework中实现选项随问题动态过滤?
解决Django REST Framework中问题与选项的联动需求
这是表单联动里很常见的场景,要实现「选完问题后只显示对应选项」,得前后端配合来做,我给你两种实用的方案:
方案一:后端动态过滤选项的QuerySet
通过自定义序列化字段,根据请求中的问题参数来动态调整选项的查询范围,适合在DRF的可浏览API里快速测试,或者前端不想额外写太多逻辑的情况。
首先自定义一个继承自SlugRelatedField的字段,重写get_queryset方法:
from rest_framework import serializers from .models import Option class DynamicOptionSlugRelatedField(serializers.SlugRelatedField): def get_queryset(self): # 从请求参数里获取选中的问题ID question_id = self.context['request'].query_params.get('question_id') queryset = Option.objects.all() if question_id: # 只返回该问题对应的选项 queryset = queryset.filter(question_id=question_id) return queryset
然后修改你的TestSerializer,把原来的option字段替换成自定义的这个字段:
class TestSerializer(serializers.ModelSerializer): question = serializers.SlugRelatedField(queryset=Question.objects.all(), slug_field='question') option = DynamicOptionSlugRelatedField(slug_field='option') class Meta: model = UserTest fields = ('question', 'option')
注意:使用这个方案时,前端在选择问题后,需要把问题ID作为查询参数(比如?question_id=1)附加到请求里,这样后端就能拿到参数过滤选项了。
方案二:前端主动请求对应选项(更推荐生产环境)
这种方案体验更流畅,用户选完问题后,前端立刻请求该问题的选项列表,再渲染到下拉框里,是实际项目中常用的方式。
第一步:后端新增接口返回指定问题的选项
先写一个API视图来提供某个问题的选项:
# views.py from rest_framework.views import APIView from rest_framework.response import Response from .models import Option from rest_framework import serializers class QuestionOptionsAPIView(APIView): def get(self, request, question_id): # 过滤出该问题下的所有选项 options = Option.objects.filter(question_id=question_id) # 序列化选项数据 serializer = serializers.SlugRelatedField( queryset=options, slug_field='option', many=True ) return Response(serializer.data)
然后在urls.py里配置路由:
from django.urls import path from .views import QuestionOptionsAPIView urlpatterns = [ # 其他路由... path('api/questions/<int:question_id>/options/', QuestionOptionsAPIView.as_view(), name='question-options'), ]
第二步:前端实现联动逻辑
这里用原生JavaScript举个例子,Vue/React等框架的思路是一致的:
// 获取页面上的下拉框元素 const questionSelect = document.getElementById('question-select'); const optionSelect = document.getElementById('option-select'); // 先加载所有问题到下拉框 fetch('/api/questions/') .then(res => res.json()) .then(questions => { questions.forEach(question => { const opt = document.createElement('option'); opt.value = question.id; opt.textContent = question.question; questionSelect.appendChild(opt); }); }); // 监听问题选择的变化 questionSelect.addEventListener('change', function() { const selectedQuestionId = this.value; // 如果没选问题,清空选项下拉框 if (!selectedQuestionId) { optionSelect.innerHTML = '<option value="">请先选择问题</option>'; return; } // 请求该问题对应的选项 fetch(`/api/questions/${selectedQuestionId}/options/`) .then(res => res.json()) .then(options => { // 清空原有选项,添加新选项 optionSelect.innerHTML = '<option value="">请选择选项</option>'; options.forEach(option => { const opt = document.createElement('option'); opt.value = option.id; opt.textContent = option.option; optionSelect.appendChild(opt); }); }); });
额外提示
另外,你的UserTest模型里有user字段,创建记录时记得自动关联当前登录用户,避免手动输入:
from rest_framework import viewsets class UserTestViewSet(viewsets.ModelViewSet): serializer_class = TestSerializer queryset = UserTest.objects.all() def perform_create(self, serializer): # 保存时自动设置当前用户 serializer.save(user=self.request.user)
内容的提问来源于stack exchange,提问作者Being Razeev
相关产品推荐
相关产品推荐

