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

如何让Django循环渲染的各组单选按钮相互独立互不干扰

问题现象

通过for循环遍历employee_list中的员工对象,为每位员工渲染forms.py中定义的、包含"Present"/"Absent"两个选项的单选按钮表单时,出现跨员工单选框互斥问题:选中员工1的选项后,再选员工2的选项,员工1的选中状态会自动取消,无法为不同员工单独选择出勤状态。


相关代码

mark_attendance.html

{% extends 'base.html' %}
{% block content %}

<form action="{% url 'confirm' %}" method="post">
            {% csrf_token %}
    <div class="card mb-3">
        <div class="card-header">
          <i class="fas fa-table"></i>
        <div class="card-body">
          <div class="table-responsive">
            <table class="table table-bordered" id="dataTable" width="100%" cellspacing="0">
              <thead>
                <tr>
                    <th>Employee name</th>
                    <th></th>
                </tr>
              </thead>
                <tbody>
                {% for employee in employee_list %}
                    {{<tr>
                    <td>{{employee}}</td>
                    <td>
                        {{ form }}
                    </td>
                    </tr>}}
                {% endfor %}
                </tbody>
            </table>
          </div>
        </div>
      </div>


    <input class="btn btn-success" type="submit" value="Submit">
</form>

{% endblock %}

forms.py

from django import forms

class AttendanceForm(forms.Form):
    ATTENDANCE_CHOICES = [
        ('Present', 'Present'),
        ('Absent', 'Absent'),
    ]
    decision = forms.CharField(label='Attendance', widget=forms.RadioSelect(choices=ATTENDANCE_CHOICES))

views.py

from django.shortcuts import render, get_object_or_404
from django.http import HttpResponseRedirect
from django.contrib.auth.decorators import login_required
from django.utils import timezone
from .models import Attendance
from django.urls import reverse
from .models import Student
from .forms import AttendanceForm
from datetime import datetime



employee_list = ["John", "Jane", "Jacob", "Mark", "Jingleheimer", "Rob", "Schmidt"]

# Create your views here.
@login_required
def home(request):
    return render(request, 'index.html')

@login_required
def attendance(request):
    now = timezone.now().date
    status = 0
    return render(request, 'attendance.html', {
        "date": now,
        "status": status,
    })

@login_required
def mark_attendance(request):
    employee_list = Student.objects.all()
    if request.method == "POST":
        form = AttendanceForm(request.POST)
        for employee in employee_list:
            if form.is_valid():
                decision = form.cleaned_data['decision']
                status = Attendance(student=employee, date=datetime.now(), status=decision)
                status.save()
                return HttpResponseRedirect('mark_attendance.html')
    else:
        form = AttendanceForm()

    return render(request, 'mark_attendance.html', {
        "form": form,
        "employee_list": employee_list,
    })

# @login_required
# def confirm(request):
#     status = request.POST.get('status')
#     print(status)

问题根因
  • HTML单选框依靠name属性做分组,同name的单选框同时只能选中一个。循环中反复渲染同一个AttendanceForm实例,所有单选框的name属性值均为decision,天然属于同一组,必然会出现互斥。
  • 视图逻辑存在缺陷:仅实例化了一个表单,提交后只能拿到一个出勤值,无法匹配所有员工;且现有POST逻辑保存完第一个员工的数据就直接重定向,后续员工的出勤数据完全不会被处理。
  • 模板存在语法错误:<tr>标签被错误包裹在Django模板变量插值符号{{}}中,会导致表格渲染异常。

修复方案

使用Django原生的表单集(Formset)处理批量同类型表单,表单集会自动为每个子表单的字段添加独立前缀,保证单选框name不重复,同时自带批量校验逻辑。

1. 修改视图代码

导入formset_factory,替换原有单表单实例化逻辑,修正重定向、时区相关错误:

from django.forms import formset_factory
# 其余原有导入保持不变

@login_required
def mark_attendance(request):
    employee_list = Student.objects.all()
    # 生成和员工数量匹配的表单集
    AttendanceFormSet = formset_factory(AttendanceForm, extra=len(employee_list))
    
    if request.method == "POST":
        formset = AttendanceFormSet(request.POST)
        if formset.is_valid():
            # 员工和表单一一对应保存
            for employee, form in zip(employee_list, formset):
                decision = form.cleaned_data['decision']
                Attendance.objects.create(
                    student=employee,
                    date=timezone.now().date(),
                    status=decision
                )
            # 用reverse解析路由,不要硬写模板路径
            return HttpResponseRedirect(reverse('mark_attendance'))
    else:
        formset = AttendanceFormSet()

    # 提前打包员工和表单的配对关系,避免模板自定义过滤器
    employee_form_pairs = list(zip(employee_list, formset))
    return render(request, 'mark_attendance.html', {
        "formset": formset,
        "employee_form_pairs": employee_form_pairs,
    })

2. 修改模板代码

修正模板语法错误,循环渲染配对的员工和表单,补全未闭合的标签:

{% extends 'base.html' %}
{% block content %}
<form action="{% url 'confirm' %}" method="post">
    {% csrf_token %}
    <div class="card mb-3">
        <div class="card-header">
            <i class="fas fa-table"></i>
        </div>
        <div class="card-body">
            <div class="table-responsive">
                <table class="table table-bordered" id="dataTable" width="100%" cellspacing="0">
                    <thead>
                        <tr>
                            <th>Employee name</th>
                            <th>Attendance status</th>
                        </tr>
                    </thead>
                    <tbody>
                        {% for employee, form in employee_form_pairs %}
                        <tr>
                            <td>{{ employee }}</td>
                            <td>{{ form.decision }}</td>
                        </tr>
                        {% endfor %}
                    </tbody>
                </table>
            </div>
        </div>
    </div>
    <input class="btn btn-success" type="submit" value="Submit">
</form>
{% endblock %}

修复后每个员工对应的单选框会有独立的name属性(如form-0-decision、form-1-decision),不会再出现跨员工互斥的问题,提交后也能正常批量保存所有员工的出勤状态。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.31 11:33:21