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

Django创建ModelForm保存各问题用户选择的实现方法

Django多问题批量投票保存方案

针对你当前的批量提交用户选择的场景,直接用modelformset_factory实现即可,它的作用就是批量生成同一模型的表单实例,自动处理表单命名、校验、批量保存逻辑,不需要你手写复杂的表单name规则。

第一步:新建表单定义(forms.py)

在app目录下新建forms.py,定义单条用户选择的ModelForm,再生成对应表单集:

from django import forms
from django.forms import modelformset_factory
from .models import UserChoice, Choice

class UserChoiceForm(forms.ModelForm):
    # 将外键字段渲染为单选按钮组,初始查询集为空,后续动态绑定对应问题的选项
    user_choice = forms.ModelChoiceField(
        queryset=Choice.objects.none(),
        widget=forms.RadioSelect,
        required=True,
        empty_label=None
    )

    class Meta:
        model = UserChoice
        fields = ['user_choice']

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

第二步:修改视图逻辑(views.py)

调整原有index视图,同时处理GET页面渲染和POST提交保存逻辑,动态根据问题数量生成对应数量的表单,给每个表单绑定对应问题的选项:

from django.shortcuts import render, redirect
from django.forms import modelformset_factory
from .models import Question
from .forms import UserChoiceForm

def index(request):
    question_list = Question.objects.all().order_by('pub_date')
    question_count = question_list.count()

    # 动态生成表单集
    UserChoiceFormSet = modelformset_factory(
        model=UserChoice,
        form=UserChoiceForm,
        extra=0
    )

    # 初始化表单集,传入POST数据,空查询集代表新建数据而非编辑已有数据
    formset = UserChoiceFormSet(
        request.POST or None,
        queryset=UserChoice.objects.none(),
        initial=[{} for _ in range(question_count)] # 生成和问题数量一致的空表单
    )

    # 给每个表单绑定对应问题,过滤出该问题下的可选选项
    for form, question in zip(formset.forms, question_list):
        form.fields['user_choice'].queryset = question.choice_set.all()
        form.question = question # 把问题实例挂到表单上,方便模板渲染问题标题

    # 处理POST提交
    if request.method == 'POST':
        if formset.is_valid():
            # 批量保存所有用户选择
            # 如果需要关联登录用户、更新投票计数,用commit=False先拿到实例再处理
            # instances = formset.save(commit=False)
            # for instance in instances:
            #     instance.user = request.user # 需提前给UserChoice加user外键字段
            #     instance.save()
            #     # 给对应选项的投票数+1
            #     instance.user_choice.votes += 1
            #     instance.user_choice.save(update_fields=['votes'])
            formset.save()
            return redirect('polls:index') # 替换为你自己的路由名

    context = {
        'question_list': question_list,
        'formset': formset
    }
    return render(request, 'polls/index.html', context)

第三步:修改模板(index.html)

替换你原来手写的表单逻辑,直接渲染Django表单集,注意必须加management_form,这是formset正常工作的必要隐藏字段:

<form method="post">
    {% csrf_token %}
    <!-- 必须渲染表单集管理字段,存储表单总数等元数据,缺失会导致校验失败 -->
    {{ formset.management_form }}

    {% for form in formset %}
    <h3>{{ form.question.question_text }}</h3>
    <!-- 渲染单选选项组 -->
    {{ form.user_choice }}
    <!-- 渲染单字段校验错误 -->
    {% if form.user_choice.errors %}
        <p style="color: red;">请为该问题选择一个选项</p>
    {% endif %}
    <br>
    {% endfor %}

    <!-- 渲染表单集全局错误 -->
    {% if formset.non_form_errors %}
        <p style="color: red;">提交数据异常,请刷新页面重试</p>
    {% endif %}

    <br>
    <input type="submit" value="提交选择">
</form>

注意事项

  • 原有UserChoice模型的__str__方法存在隐患,self.user_choice是Choice实例,建议修改为return self.user_choice.choice_text,避免字符串转换报错。
  • 如果需要防止用户重复投票,可给UserChoice模型加用户外键,再加联合唯一约束unique_together = ('user', 'user_choice__question'),限制一个用户对同一个问题只能投一次票。
  • 表单集的initial参数用来控制生成的空表单数量,必须和问题数量保持一致,否则会出现表单和问题不匹配的问题。
  • 如果你需要自定义单选选项的样式,可以遍历form.user_choice字段渲染,不需要手写name、id属性,Django会自动生成符合formset规则的属性值。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 19:39:19