You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Django动态表单开发求助:基于问题类型生成对应关联字段

解决方案:Django动态题型表单实现

1. 后端表单配置(forms.py)

先定义主问题表单和关联的选项、子问题表单,再用表单集处理批量字段:

from django import forms
from .models import Question, Option, Subquestion

# 主问题表单(仅题型选择)
class QuestionForm(forms.ModelForm):
    class Meta:
        model = Question
        fields = ['question_type']

# 选项子表单
class OptionForm(forms.ModelForm):
    class Meta:
        model = Option
        fields = ['option']
        widgets = {'option': forms.TextInput(attrs={'placeholder': '输入选项内容'})}

# 子问题子表单
class SubquestionForm(forms.ModelForm):
    class Meta:
        model = Subquestion
        fields = ['subquestion']
        widgets = {'subquestion': forms.TextInput(attrs={'placeholder': '输入子问题内容'})}

# 创建表单集(初始额外数量设为0,后续动态调整)
OptionFormSet = forms.formset_factory(OptionForm, extra=0)
SubquestionFormSet = forms.formset_factory(SubquestionForm, extra=0)

2. 视图逻辑(views.py)

处理表单提交、验证和数据保存,同时兼容验证失败后的回显:

from django.shortcuts import render, redirect
from .forms import QuestionForm, OptionFormSet, SubquestionFormSet
from .models import Question

def create_question(request):
    if request.method == 'POST':
        question_form = QuestionForm(request.POST)
        if question_form.is_valid():
            question = question_form.save()
            q_type = question.question_type

            # 保存选项(SBA/EMQ题型)
            if q_type in ['SBA', 'EMQ']:
                option_formset = OptionFormSet(request.POST, prefix='options')
                if option_formset.is_valid():
                    for form in option_formset:
                        if form.cleaned_data.get('option'):
                            option = form.save(commit=False)
                            option.question = question
                            option.save()

            # 保存子问题(仅EMQ题型)
            if q_type == 'EMQ':
                subq_formset = SubquestionFormSet(request.POST, prefix='subquestions')
                if subq_formset.is_valid():
                    for form in subq_formset:
                        if form.cleaned_data.get('subquestion'):
                            subq = form.save(commit=False)
                            subq.question = question
                            subq.save()

            return redirect('question_list')  # 替换为你的问题列表页面URL
        else:
            # 验证失败,根据提交的题型重新初始化对应数量的表单集
            q_type = request.POST.get('question_type', '')
            option_extra = 5 if q_type == 'SBA' else 7 if q_type == 'EMQ' else 0
            subq_extra = 5 if q_type == 'EMQ' else 0
            option_formset = OptionFormSet(request.POST, prefix='options', extra=option_extra)
            subq_formset = SubquestionFormSet(request.POST, prefix='subquestions', extra=subq_extra)
    else:
        # GET请求初始化空表单
        question_form = QuestionForm()
        option_formset = OptionFormSet(prefix='options', extra=0)
        subq_formset = SubquestionFormSet(prefix='subquestions', extra=0)

    context = {
        'question_form': question_form,
        'option_formset': option_formset,
        'subq_formset': subq_formset
    }
    return render(request, 'create_question.html', context)

3. 前端模板(create_question.html)

通过JS监听题型选择变化,动态生成/隐藏对应字段:

<form method="post">
    {% csrf_token %}
    <div class="form-group">
        {{ question_form.question_type.label_tag }}
        {{ question_form.question_type }}
    </div>

    <!-- 选项区域 -->
    <div id="options-section" style="display: none; margin-top: 1rem;">
        <h4>选项</h4>
        {{ option_formset.management_form }}
        <div id="options-container">
            {% for form in option_formset %}
                <div class="option-item">{{ form.option }}</div>
            {% endfor %}
        </div>
    </div>

    <!-- 子问题区域 -->
    <div id="subquestions-section" style="display: none; margin-top: 1rem;">
        <h4>子问题</h4>
        {{ subq_formset.management_form }}
        <div id="subquestions-container">
            {% for form in subq_formset %}
                <div class="subq-item">{{ form.subquestion }}</div>
            {% endfor %}
        </div>
    </div>

    <button type="submit" style="margin-top: 1rem;">保存问题</button>
</form>

<script>
    const typeSelect = document.getElementById('id_question_type');
    const optionsSection = document.getElementById('options-section');
    const subqSection = document.getElementById('subquestions-section');
    const optionsContainer = document.getElementById('options-container');
    const subqContainer = document.getElementById('subquestions-container');
    const optionTotalForms = document.getElementById('id_options-TOTAL_FORMS');
    const subqTotalForms = document.getElementById('id_subquestions-TOTAL_FORMS');

    // 根据题型更新表单字段
    function updateFields() {
        const selectedType = typeSelect.value;
        
        // 清空现有表单
        optionsContainer.innerHTML = '';
        subqContainer.innerHTML = '';

        // 处理选项
        if (selectedType === 'SBA' || selectedType === 'EMQ') {
            optionsSection.style.display = 'block';
            const count = selectedType === 'SBA' ? 5 : 7;
            optionTotalForms.value = count;
            
            for (let i = 0; i < count; i++) {
                const item = document.createElement('div');
                item.className = 'option-item';
                item.innerHTML = `<input type="text" name="options-${i}-option" placeholder="选项 ${i+1}" id="id_options-${i}-option">`;
                optionsContainer.appendChild(item);
            }
        } else {
            optionsSection.style.display = 'none';
            optionTotalForms.value = 0;
        }

        // 处理子问题
        if (selectedType === 'EMQ') {
            subqSection.style.display = 'block';
            const count = 5;
            subqTotalForms.value = count;
            
            for (let i = 0; i < count; i++) {
                const item = document.createElement('div');
                item.className = 'subq-item';
                item.innerHTML = `<input type="text" name="subquestions-${i}-subquestion" placeholder="子问题 ${i+1}" id="id_subquestions-${i}-subquestion">`;
                subqContainer.appendChild(item);
            }
        } else {
            subqSection.style.display = 'none';
            subqTotalForms.value = 0;
        }
    }

    // 初始化+监听变化
    typeSelect.addEventListener('change', updateFields);
    updateFields();
</script>

关键说明

  • 表单集的prefix参数必须设置,避免不同表单集的字段名冲突
  • 前端通过修改TOTAL_FORMS隐藏字段,告诉后端需要处理的表单数量
  • 保存时过滤空内容,避免创建无意义的关联数据
  • 验证失败时,根据提交的题型重新渲染对应数量的表单,保留用户输入

内容的提问来源于stack exchange,提问作者Shiva

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 13:43:16