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
相关产品推荐
相关产品推荐

