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

如何基于Django实现依前置回答动态展示的多步骤表单?

这个需求我之前做过类似的,刚好可以用一种既符合DRY原则又适配你现有模型的方案来实现。不用硬套django-formtools的默认流程,我们可以通过动态表单生成+步骤配置化的方式来搞定,下面是具体的实现思路和代码示例:

核心思路:配置化步骤+动态表单

把每个步骤的字段、展示模板、触发条件都集中配置在一个列表里,然后通过视图动态生成对应步骤的表单,同时根据上一步的提交数据判断是否需要展示后续步骤。这样新增或修改步骤只需要改配置,不用重复写视图/表单代码,完美符合DRY。

1. 定义步骤配置

首先把所有步骤的规则写在一个配置列表里,清晰直观:

# 在你的views.py或者单独的config.py里
SURVEY_STEPS = [
    {
        "name": "general_info",
        "fields": ["name", "age", "married"],
        "template": "survey/step_general.html",
        # 该步骤无前置条件,默认展示
    },
    {
        "name": "married_details",
        "fields": ["worth_it", "job_status"],
        "template": "survey/step_married.html",
        # 触发条件:上一步的married为True
        "condition": lambda cleaned_data: cleaned_data.get("married") is True,
    },
    # 可以继续添加更多步骤,比如未婚用户的专属步骤
    {
        "name": "single_details",
        "fields": ["hobby", "travel_frequency"],
        "template": "survey/step_single.html",
        "condition": lambda cleaned_data: cleaned_data.get("married") is False,
    },
]

2. 实现多步骤视图

写一个通用的视图类,处理步骤流转、表单生成和数据保存。这里我们基于Django的通用视图扩展,不用重复造轮子:

from django.views.generic.edit import ProcessFormView, FormMixin
from django import forms
from django.contrib import messages
from .models import Survey

class DynamicSurveyView(ProcessFormView, FormMixin):
    # 默认表单类会被动态覆盖
    form_class = forms.Form
    # 最终完成页的URL
    final_success_url = "/survey/complete/"

    def get_current_step(self):
        # 从URL参数获取当前步骤,默认第一步
        return self.request.GET.get("step", SURVEY_STEPS[0]["name"])

    def get_step_config(self, step_name):
        # 根据步骤名获取对应配置
        return next(step for step in SURVEY_STEPS if step["name"] == step_name)

    def get_form_class(self):
        # 动态生成当前步骤的ModelForm
        step_config = self.get_step_config(self.get_current_step())
        
        class DynamicSurveyForm(forms.ModelForm):
            class Meta:
                model = Survey
                fields = step_config["fields"]
            
            # 可以在这里给当前步骤的字段加专属验证(可选)
            def clean_age(self):
                age = self.cleaned_data.get("age")
                if not age.isdigit():
                    raise forms.ValidationError("年龄请输入数字")
                return age

        return DynamicSurveyForm

    def get_form_kwargs(self):
        kwargs = super().get_form_kwargs()
        # 获取当前用户的草稿实例(如果是登录用户,推荐用这个替代session)
        # 如果是匿名用户,可以用session存survey_pk,不过登录用户方案更可靠
        if self.request.user.is_authenticated:
            survey, _ = Survey.objects.get_or_create(
                user=self.request.user,
                status="draft"  # 给模型加个status字段:draft/complete
            )
            kwargs["instance"] = survey
        else:
            # 匿名用户用session临时存储
            survey_pk = self.request.session.get("survey_pk")
            if survey_pk:
                kwargs["instance"] = Survey.objects.get(pk=survey_pk)
            else:
                kwargs["instance"] = Survey()
        return kwargs

    def form_valid(self, form):
        # 保存当前步骤的数据到数据库
        survey = form.save(commit=False)
        if self.request.user.is_authenticated:
            survey.user = self.request.user
        survey.save()

        # 存储实例PK到session(匿名用户用)
        if not self.request.user.is_authenticated:
            self.request.session["survey_pk"] = survey.pk

        # 确定下一步骤
        current_step = self.get_current_step()
        current_index = next(i for i, step in enumerate(SURVEY_STEPS) if step["name"] == current_step)
        next_steps = SURVEY_STEPS[current_index + 1 :]

        # 筛选符合触发条件的下一步
        next_step = None
        for step in next_steps:
            if "condition" not in step or step["condition"](form.cleaned_data):
                next_step = step
                break

        if next_step:
            # 跳转到下一步
            self.success_url = f"/survey/?step={next_step['name']}"
        else:
            # 所有步骤完成,标记为已完成
            survey.status = "complete"
            survey.save()
            # 清除session(匿名用户)
            if "survey_pk" in self.request.session:
                del self.request.session["survey_pk"]
            self.success_url = self.final_success_url
            messages.success(self.request, "问卷提交成功!")

        return super().form_valid(form)

    def get_template_names(self):
        # 返回当前步骤的模板
        step_config = self.get_step_config(self.get_current_step())
        return [step_config["template"]]

3. 模型适配(可选但推荐)

给你的Survey模型加一个status字段,方便区分草稿和已完成的问卷,管理起来更方便:

class Survey(models.Model):
    # 原有字段...
    STATUS_CHOICES = (("draft", "草稿"), ("complete", "已完成"))
    status = models.CharField(max_length=20, choices=STATUS_CHOICES, default="draft")
    # 如果是登录用户,还可以加个外键关联User
    user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True)

方案优势

  • 完全符合DRY:所有步骤规则集中配置,新增/修改步骤只需要改配置,不用重复写视图和表单代码
  • 灵活适配动态逻辑:通过condition lambda可以实现任意复杂的触发规则,比如多个字段组合判断
  • 适配现有模型:不用改动你现有的模型结构,直接基于现有字段生成表单
  • 可扩展:后续可以轻松添加步骤、修改字段或触发条件,完全适配客户需求的变化

额外优化建议

  • 可以给步骤配置加order字段,更灵活控制步骤顺序
  • 对于复杂的字段验证,可以把验证逻辑也写到配置里,动态注入到表单中
  • 如果步骤很多,可以把配置拆分成单独的文件,比如survey_steps.py,更易维护

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.12 03:55:34