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

如何在Django中使用多个模型创建批量录入班级学生学科成绩的表单

如何在Django中使用多个模型创建批量录入班级学生学科成绩的表单

嘿,我完全懂你要做的这个批量录成绩的需求——给一整个班的学生一次性录入某门课的分数,之前拆分开做各个模块的表单拼起来没成功,那咱们就用Django的原生工具来搞定这个多模型的批量表单!

首先得把咱们的模型结构捋顺,毕竟要关联班级、学生、学科和成绩这几个模块,我给你一个符合需求的模型示例:

# models.py
from django.db import models

class Grade(models.Model):
    name = models.CharField(max_length=50, verbose_name="班级")
    # 比如填"高一(1)班"这种名称

    def __str__(self):
        return self.name

class Subject(models.Model):
    name = models.CharField(max_length=50, verbose_name="学科")
    # 比如"数学"、"英语"

    def __str__(self):
        return self.name

class Student(models.Model):
    name = models.CharField(max_length=100, verbose_name="学生姓名")
    grade = models.ForeignKey(Grade, on_delete=models.CASCADE, related_name="students", verbose_name="所属班级")
    # 你还可以加学号、性别这些字段,按需扩展

    def __str__(self):
        return self.name

class Result(models.Model):
    student = models.ForeignKey(Student, on_delete=models.CASCADE, related_name="results", verbose_name="学生")
    subject = models.ForeignKey(Subject, on_delete=models.CASCADE, verbose_name="学科")
    marks = models.IntegerField(verbose_name="分数")
    exam_name = models.CharField(max_length=100, verbose_name="考试名称", blank=True, null=True)
    # 加个考试名称,方便区分期中、期末不同考试的成绩

    class Meta:
        unique_together = ('student', 'subject', 'exam_name')  # 避免同一学生同一学科同一考试重复录分

    def __str__(self):
        return f"{self.student.name} - {self.subject.name}: {self.marks}"

接下来是表单部分,咱们分两个部分:一个用来选班级、学科和考试名称的表单,另一个是批量处理每个学生成绩的表单集合(FormSet):

# forms.py
from django import forms
from .models import Grade, Subject, Result

# 用来选择班级、学科和考试的表单
class GradeSubjectSelectForm(forms.Form):
    grade = forms.ModelChoiceField(queryset=Grade.objects.all(), label="选择班级", required=True)
    subject = forms.ModelChoiceField(queryset=Subject.objects.all(), label="选择学科", required=True)
    exam_name = forms.CharField(label="考试名称", max_length=100, required=True, help_text="比如'2024年期中测试'")

# 单个学生的成绩录入表单
class ResultForm(forms.ModelForm):
    class Meta:
        model = Result
        fields = ['marks']
        widgets = {
            'marks': forms.NumberInput(attrs={'class': 'form-control', 'min': 0, 'max': 100})
        }

然后是核心的视图逻辑,处理用户选择班级后的表单生成,以及提交后的成绩保存:

# views.py
from django.shortcuts import render, redirect
from django.forms import modelformset_factory
from .forms import GradeSubjectSelectForm, ResultForm
from .models import Student, Result

def bulk_marks_entry(request):
    if request.method == 'POST':
        # 先判断是提交选择的班级学科,还是提交成绩
        if 'select' in request.POST:
            select_form = GradeSubjectSelectForm(request.POST)
            if select_form.is_valid():
                grade = select_form.cleaned_data['grade']
                subject = select_form.cleaned_data['subject']
                exam_name = select_form.cleaned_data['exam_name']

                # 生成批量处理Result的FormSet,extra=0表示不生成额外的空表单
                ResultFormSet = modelformset_factory(Result, form=ResultForm, extra=0)
                # 用queryset=None表示我们要创建新的记录(或者更新已有)
                formset = ResultFormSet(queryset=Result.objects.none())

                # 把班级学生、表单集合、选择的信息传给模板
                students = grade.students.all()
                student_form_pairs = list(zip(students, formset.forms))
                return render(request, 'bulk_marks_entry.html', {
                    'select_form': select_form,
                    'formset': formset,
                    'student_form_pairs': student_form_pairs,
                    'selected_grade': grade,
                    'selected_subject': subject,
                    'exam_name': exam_name
                })
        elif 'save' in request.POST:
            # 处理成绩保存请求
            select_form = GradeSubjectSelectForm(request.POST)
            if select_form.is_valid():
                grade = select_form.cleaned_data['grade']
                subject = select_form.cleaned_data['subject']
                exam_name = select_form.cleaned_data['exam_name']

                ResultFormSet = modelformset_factory(Result, form=ResultForm, extra=0)
                formset = ResultFormSet(request.POST, queryset=Result.objects.none())
                students = grade.students.all()

                # 遍历每个表单和对应的学生,保存/更新成绩
                for form, student in zip(formset, students):
                    if form.is_valid():
                        marks = form.cleaned_data['marks']
                        # 检查是否已有该学生该学科该考试的成绩,有就更新,没有就创建
                        result, created = Result.objects.get_or_create(
                            student=student,
                            subject=subject,
                            exam_name=exam_name,
                            defaults={'marks': marks}
                        )
                        if not created:
                            result.marks = marks
                            result.save()
                # 跳转到成功页面或者当前页面提示成功
                return redirect('bulk_marks_entry')
    else:
        # GET请求时只显示选择表单
        select_form = GradeSubjectSelectForm()
        return render(request, 'bulk_marks_entry.html', {'select_form': select_form})

最后是模板部分,咱们做一个清晰的表格布局,和你想要的样式差不多:

<!-- templates/bulk_marks_entry.html -->
<!DOCTYPE html>
<html>
<head>
    <title>批量录成绩</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container mt-5">
    <h2>批量录入班级学生学科成绩</h2>

    <!-- 班级、学科、考试选择区域 -->
    <form method="post" class="mb-4 p-3 border rounded">
        {% csrf_token %}
        <div class="row g-3 align-items-center">
            <div class="col-auto">
                {{ select_form.grade.label_tag }}
                {{ select_form.grade }}
            </div>
            <div class="col-auto">
                {{ select_form.subject.label_tag }}
                {{ select_form.subject }}
            </div>
            <div class="col-auto">
                {{ select_form.exam_name.label_tag }}
                {{ select_form.exam_name }}
            </div>
            <div class="col-auto">
                <button type="submit" name="select" class="btn btn-primary">确认选择</button>
            </div>
        </div>
    </form>

    <!-- 成绩录入表格 -->
    {% if student_form_pairs %}
    <form method="post" class="p-3 border rounded">
        {% csrf_token %}
        {{ formset.management_form }}
        <!-- 隐藏传递选择的班级学科信息,不然保存时拿不到 -->
        <input type="hidden" name="grade" value="{{ selected_grade.id }}">
        <input type="hidden" name="subject" value="{{ selected_subject.id }}">
        <input type="hidden" name="exam_name" value="{{ exam_name }}">

        <table class="table table-hover">
            <thead class="table-light">
                <tr>
                    <th>序号</th>
                    <th>学生姓名</th>
                    <th>本次考试分数</th>
                </tr>
            </thead>
            <tbody>
                {% for student, form in student_form_pairs %}
                <tr>
                    <td>{{ forloop.counter }}</td>
                    <td>{{ student.name }}</td>
                    <td>{{ form.marks }}</td>
                </tr>
                {% endfor %}
            </tbody>
        </table>
        <button type="submit" name="save" class="btn btn-success w-100">保存所有成绩</button>
    </form>
    {% endif %}
</div>
</body>
</html>

最后给你几个小提示:

  • 如果你想让分数输入更严谨,可以在ResultForm的clean_marks方法里加验证,比如分数必须在0-100之间
  • 可以在模板里加一些提示信息,比如保存成功后弹出提示
  • 如果班级学生很多,可以考虑分页,但一般班级人数不多,直接显示没问题

备注:内容来源于stack exchange,提问作者SHAMMO

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.14 16:54:32