使用django-tables2实现Question与Choice一对多关联查询展示异常排查
问题背景
现有三个Django模型:Quiz、Question、Choice,其中Question与Choice为一对多关系(一个问题对应多个选项)。使用django-tables2的SingleTableView以Question为起始模型展示数据,预期显示8行数据(4个问题×2个选项),但实际仅显示4行,且choice_text列始终为None。手动构造包含相同访问器的queryset(Question.objects.all().values("question_identifier", "choice__choice_text", "quiz_identifier__quiz_name"))却能正常返回8行完整数据。
使用版本:django4.2.3,django-tables22.5.3
原因分析
django-tables2默认基于模型实例渲染行,每个Question实例对应一行表格。而choice__choice_text是反向关联的集合(一个Question对应多个Choice),直接通过accessor无法遍历集合中的单个选项,因此只会显示None,且行数与Question实例数量一致。
而values()方法会执行数据库笛卡尔积查询,将每个Question与对应的Choice进行关联展开,生成每行对应一个选项的结果,但django-tables2不会自动为模型实例做这种展开处理。
解决方案
方案一:切换表格模型为Choice(推荐)
既然需要每个选项对应一行数据,直接将表格和视图的模型切换为Choice,通过反向关联获取Question和Quiz的信息:
- 修改
tables.py:
class QuestionChoiceTable(tables.Table): question_identifier = tables.Column( accessor='question_identifier__question_identifier', verbose_name='Question ID' ) quiz_name = tables.Column( accessor='question_identifier__quiz_identifier__quiz_name', verbose_name='Quiz Name' ) choice_text = tables.Column(verbose_name='Choice Text') class Meta: model = Choice fields = ("question_identifier", "choice_text", 'quiz_name',)
- 修改
views.py:
class QuestionListView(SingleTableView): model = Choice table_class = QuestionChoiceTable template_name = 'polls/question.html'
方案二:自定义QuerySet实现笛卡尔积展开
如果坚持以Question为起始模型,可以通过修改视图的get_queryset方法,生成包含选项的展开结果:
class QuestionListView(SingleTableView): model = Question table_class = QuestionTable template_name = 'polls/question.html' def get_queryset(self): # 生成包含选项的展开式查询结果 return Question.objects.all().values( "question_identifier", "choice__choice_text", "quiz_identifier__quiz_name" )
注意:此方法返回字典而非模型实例,部分django-tables2的高级功能可能受限,因此优先推荐方案一。
内容的提问来源于stack exchange,提问作者steffenle

