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

