Django中如何通过单个ModelForm同时创建Quiz及关联Question、Choice多条记录
实现方案
完全有可行方案,Django原生能力即可实现,不需要引入第三方依赖。需要注意的是,原生单个ModelForm仅支持处理单模型的增改操作,你这种三层关联的嵌套数据创建,搭配表单集合(Formset)或者手动处理层级提交逻辑即可满足需求,具体实现如下:
1. 定义基础表单类
首先创建Quiz的基础ModelForm用于处理主表数据:
from django import forms from .models import Quiz, Question, Choice class QuizForm(forms.ModelForm): class Meta: model = Quiz fields = ['name', 'description', 'num_questions', 'slug', 'img']
如果需要内置的表单验证、错误提示能力,可额外定义Question和Choice的表单集合,避免手动拼接参数:
# 题目表单 class QuestionForm(forms.ModelForm): class Meta: model = Question exclude = ['quiz'] # 固定生成10个题目的表单集合 QuestionFormSet = forms.formset_factory( QuestionForm, extra=10, max_num=10, can_delete=False ) # 选项表单 class ChoiceForm(forms.ModelForm): class Meta: model = Choice exclude = ['question'] # 每个题目固定生成4个选项的表单集合 ChoiceFormSet = forms.formset_factory( ChoiceForm, extra=4, max_num=4, can_delete=False )
2. 视图层核心逻辑
核心逻辑为先保存Quiz主记录拿到主键,再依次关联保存Question和Choice,自动完成外键赋值,这里给出最易理解的手动处理版本示例:
from django.shortcuts import render, redirect def create_quiz(request): if request.method == 'POST': quiz_form = QuizForm(request.POST) if quiz_form.is_valid(): # 第一步:保存Quiz主记录 quiz = quiz_form.save() # 第二步:循环创建10个关联的题目 for q_idx in range(10): question = Question.objects.create( quiz=quiz, content=request.POST.get(f'question_{q_idx}_content', ''), img=request.POST.get(f'question_{q_idx}_img', '') ) # 第三步:给当前题目创建4个关联的选项 for c_idx in range(4): Choice.objects.create( question=question, content=request.POST.get(f'question_{q_idx}_choice_{c_idx}_content', ''), correct=request.POST.get(f'question_{q_idx}_choice_{c_idx}_correct') == 'on' ) # 自定义保存成功后的跳转逻辑 return redirect('quiz_list') else: quiz_form = QuizForm() return render(request, 'create_quiz.html', { 'quiz_form': quiz_form, 'question_count': 10, 'choice_per_question': 4 })
3. 模板层表单渲染
直接按层级渲染对应字段即可,示例代码如下:
<form method="post"> {% csrf_token %} <!-- 渲染Quiz基础字段 --> {{ quiz_form.as_p }} <!-- 渲染10个题目输入项 --> <h3>测验题目</h3> {% for q in question_count|rjust:question_count|make_list %} <div style="margin: 20px 0; padding: 10px; border: 1px solid #eee;"> <h4>第{{ forloop.counter }}题</h4> <p> <label>题目内容:</label> <input type="text" name="question_{{ forloop.counter0 }}_content" required maxlength="200"> </p> <p> <label>题目图片链接(选填):</label> <input type="url" name="question_{{ forloop.counter0 }}_img"> </p> <!-- 渲染当前题目的4个选项 --> <div style="margin-left: 20px;"> <h5>选项</h5> {% for c in choice_per_question|rjust:choice_per_question|make_list %} <div style="margin: 10px 0;"> <p> <label>选项{{ forloop.counter }}内容:</label> <input type="text" name="question_{{ forloop.parentloop.counter0 }}_choice_{{ forloop.counter0 }}_content" required maxlength="200"> </p> <p> <label>是否为正确答案:</label> <input type="checkbox" name="question_{{ forloop.parentloop.counter0 }}_choice_{{ forloop.counter0 }}_correct"> </p> </div> {% endfor %} </div> </div> {% endfor %} <button type="submit">创建测验</button> </form>
内容的提问来源于stack exchange,提问作者Lumiobyte
相关产品推荐
相关产品推荐

