简介:基于Python Django、MySQL与HTML技术栈构建的学生成绩管理系统,是一套面向Web开发初学者、课程设计与毕业设计场景的完整项目,能够帮助学习者快速了解Django项目从结构搭建到功能落地的过程。压缩包共373个文件,大小约2.58MB,核心包含Python源码、HTML页面模板、JavaScript交互脚本、CSS/LESS/SCSS样式文件、数据库SQL脚本和安装说明文档,前后端分层清晰,便于直接部署和二次扩展。系统内置超级管理员、学校管理员和成绩录入测试账号,覆盖创建学校、管理年级班级、维护学生名单、在线录入成绩与统计等功能,且通过角色权限区分可操作范围。对于学习者而言,可结合源码深入理解Django的MTV分层、ORM数据查询、模板渲染和权限设计,随包提供的安装教程也能减少环境配置阻力,无论是快速部署还是改造升级,都具备参考价值。当前已有827人学习/下载,适合需要完整可运行项目作为参考的开发者。
1. 用浏览器打开的成绩单:为什么学生成绩管理系统值得自己搭
很多学校还在用 Excel 管理成绩:一个班一个文件,合班排名靠手工复制,改一次分要把文件重新传一轮。用 Python + Django + MySQL + HTML 这套组合搭学生成绩管理系统,等于把成绩管理搬进浏览器——录入、修改、查询、统计都在同一个后台完成,打开页面就能操作,不用给每台电脑装桌面软件。对开发者来说,这是入门 Web 开发最典型的练手场景:Django 负责模型和业务逻辑,MySQL 负责持久化存储,HTML 模板负责展示,四者各司其职,做完之后对整条数据流会有直观认知。标题里特意写了「含安装教程」,说明真正的门槛不在写业务代码,而在把 Python、Django、MySQL 三个环境弄成能协作的状态——本节就从这里切入,先立住理论,再带你复现一个能跑的成绩管理项目。
2. 环境准备与安装:Python、Django、MySQL 先在本机跑通
2.1 版本选型:先定版本再动手,能避开一半报错
常见做法是先确认 Python、Django、MySQL 三者的版本兼容关系,再开始安装。以目前主流的组合为例:Python 3.10 或 3.11 配合 Django 4.2 LTS,MySQL 8.0 及以上。Django 4.2 是长期支持版本,官方维护周期到 2026 年,社区资料最全,遇到报错基本都能搜到案例。MySQL 用 8.0 的原因是它默认字符集是 utf8mb4,存中文成绩单姓名不会出现乱码,早期 5.7 版本则需要手动指定。
| 组件 | 推荐版本 | 说明 |
|---|---|---|
| Python | 3.10 / 3.11 | 不要用 3.12 以下的老版本与 Django 4.2 组合,避免第三方驱动不兼容 |
| Django | 4.2 LTS | 自带 admin 后台,ORM 支持 annotate 聚合查询,适合成绩统计 |
| MySQL | 8.0+ | 默认 utf8mb4,支持窗口函数,后续做排名统计更方便 |
| 连接驱动 | pymysql 或 mysqlclient | Windows 上 pymysql 更省事,Linux 上推荐 mysqlclient |
提示:如果你的电脑已经装了 MySQL 5.7,暂时不想升级,那么建库时必须显式指定
CHARACTER SET utf8mb4,否则写入中文成绩单会出现Incorrect string value错误。
2.2 Python 与 Django 安装:一条命令验证是否装好
Django 不是独立软件,它是以 Python 第三方库的形式安装的。用pip安装即可,安装后通过python -m django --version验证版本号。如果同时装了 Python 2 和 Python 3,务必使用python3和pip3前缀,避免装错解释器。
# 安装 Django 4.2 LTS pip install django==4.2 # 验证是否安装成功 python -m django --version逻辑说明:python -m django是官方推荐的验证方式,它明确指定用当前 python 解释器去加载 django 模块,比直接敲django-admin --version更能避免多版本冲突。安装后不要急着写代码,先执行一次django-admin startproject score_system .创建项目骨架,再执行python manage.py runserver,浏览器访问 127.0.0.1:8000 能看到 Django 默认欢迎页,就说明 Web 框架层已经通了。
2.3 MySQL 安装配置与建库:字符集和认证方式是关键
MySQL 安装教程网上版本很多,核心差距在初始化后的配置。Windows 用 MySQL Installer 按默认选项安装即可,安装过程中记得选「Server only」,root 密码设置成自己好记的字符串。安装完成后需要手动创建一个数据库给 Django 用,这一步在命令行或 MySQL Workbench 里操作都行。
-- 创建数据库,指定字符集 CREATE DATABASE score_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; -- 创建专用账号,避免直接用 root 连应用 CREATE USER 'score_user'@'localhost' IDENTIFIED BY 'your_password_123'; -- 授权 GRANT ALL PRIVILEGES ON score_db.* TO 'score_user'@'localhost'; -- 刷新权限 FLUSH PRIVILEGES;参数说明:utf8mb4_unicode_ci是排序规则,它对中文和英文字母的排序比较合理;score_user是应用专用的低权限账号,好处是即使项目源码泄露,别人也拿不到 root 权限。这里有个容易忽略的点——MySQL 8.0 默认的认证插件是caching_sha2_password,老版本的 pymysql 不支持,所以要么把 pymysql 升级到 1.1.0 以上,要么在创建用户时指定IDENTIFIED WITH mysql_native_password BY '密码'。
2.4 配置 settings.py 连接 MySQL:Django 接入数据库的必经步骤
Django 默认配置的是 SQLite,切换 MySQL 需要改settings.py里的DATABASES配置,并在项目__init__.py里声明使用 pymysql 冒充 MySQLdb 驱动。
# score_system/__init__.py import pymysql pymysql.install_as_MySQLdb()# settings.py DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'score_db', 'USER': 'score_user', 'PASSWORD': 'your_password_123', 'HOST': '127.0.0.1', 'PORT': '3306', 'OPTIONS': { 'charset': 'utf8mb4', 'init_command': "SET sql_mode='STRICT_TRANS_TABLES'", }, } }参数说明:ENGINE是驱动引擎入口,Django 内置了 mysql 后端;init_command里的 sql_mode 建议保持 STRICT_TRANS_TABLES,它会让非法数据在写入时报错而不是被静默截断,对成绩这种敏感数据尤其重要。改完配置后执行python manage.py migrate看是否能生成内置表,不报错就说明 Django 和 MySQL 的连接通道已经完全打通。
3. 数据模型与 Django 后台:把成绩表设计成三个模型
3.1 创建 app 与模型字段:学生、课程、成绩各有各的归属
先用python manage.py startapp grades创建名为grades的应用,然后打开models.py设计数据模型。常见的做法是把成绩单拆成三个模型:Student 存学生基本信息,Course 存课程信息,Score 存成绩并关联学生和课程。拆开的理由很简单——同一个学生有多门课成绩,同一门课有多个学生,成绩表是典型的关联表。
from django.db import models class Student(models.Model): """学生模型""" student_no = models.CharField('学号', max_length=20, unique=True) name = models.CharField('姓名', max_length=50) gender = models.CharField('性别', max_length=10, choices=[('男', '男'), ('女', '女')]) class_name = models.CharField('班级', max_length=50) created_at = models.DateTimeField('创建时间', auto_now_add=True) def __str__(self): return f"{self.student_no} {self.name}" class Course(models.Model): """课程模型""" course_no = models.CharField('课程编号', max_length=20, unique=True) course_name = models.CharField('课程名称', max_length=100) credit = models.FloatField('学分', default=2.0) def __str__(self): return self.course_name class Score(models.Model): """成绩模型:学生与课程的关联表""" student = models.ForeignKey(Student, on_delete=models.CASCADE, verbose_name='学生') course = models.ForeignKey(Course, on_delete=models.CASCADE, verbose_name='课程') score = models.DecimalField('成绩', max_digits=5, decimal_places=2) exam_date = models.DateField('考试日期', null=True, blank=True) class Meta: # 联合唯一:同一学生同一门课只能有一条成绩 unique_together = ('student', 'course') def __str__(self): return f"{self.student.name} - {self.course.course_name} - {self.score}"字段设计说明:student_no设置unique=True是硬约束,防止学号重复录入;Score模型里的ForeignKey是成绩与实体之间的绑定关系,on_delete=models.CASCADE表示学生被删除时其成绩记录一并删除,这符合成绩管理的直觉——没有学生的成绩没有存在意义。DecimalField而不是FloatField存成绩,避免浮点数精度问题导致 89.99 显示成 89.99000001。
3.2 迁移数据库与注册 admin:让后台能直接看到表
模型定义完毕,执行迁移命令让 Django 把模型翻译成 MySQL 的 CREATE TABLE 语句。然后在admin.py注册模型,就可以在 Django 自带的 admin 后台直接增删改查数据。
python manage.py makemigrations grades python manage.py migrate# grades/admin.py from django.contrib import admin from .models import Student, Course, Score @admin.register(Student) class StudentAdmin(admin.ModelAdmin): list_display = ('student_no', 'name', 'gender', 'class_name') search_fields = ('student_no', 'name') @admin.register(Course) class CourseAdmin(admin.ModelAdmin): list_display = ('course_no', 'course_name', 'credit') @admin.register(Score) class ScoreAdmin(admin.ModelAdmin): list_display = ('student', 'course', 'score', 'exam_date') list_filter = ('course', 'exam_date')list_display控制了后台列表页显示的列,search_fields提供搜索框,list_filter让管理员可以按课程和考试日期筛选成绩。注册完以后启动服务,访问 127.0.0.1:8000/admin 输入超级管理员账号,就能看到三张表的入口。
注意:创建超级管理员是硬需求,执行
python manage.py createsuperuser,按提示输入用户名、邮箱、密码,否则 admin 后台无法登录。
4. 视图 + 模板实现成绩录入、查询与统计
4.1 URL 路由规划:从页面到视图的映射关系
成绩管理系统的页面一般包含:成绩列表页、成绩录入页、成绩编辑页。URL 的设计遵循资源化思路,/scores/是列表和录入,/scores/<id>/edit/是编辑。打开urls.py,将路由指向views.py里对应的函数。
# grades/urls.py from django.urls import path from . import views app_name = 'grades' urlpatterns = [ path('scores/', views.score_list, name='score_list'), path('scores/add/', views.score_add, name='score_add'), path('scores/<int:pk>/edit/', views.score_edit, name='score_edit'), path('scores/statistics/', views.score_statistics, name='score_statistics'), ]参数说明:<int:pk>是 Django 2.0 引入的 path converter,它限制了 URL 中的 pk 必须是整数,如果写abc直接返回 404,比老式的(\d+)正则匹配更安全。app_name是命名空间,模板里用{% url 'grades:score_add' %}引用这个 URL,即使以后路径变了,模板不用改。
4.2 成绩录入与列表查询:视图逻辑的分层写法
视图层按职能拆成三个函数:录入、编辑、列表。成绩添加时可以直接用 Django 内置的CreateView类视图,但为了让新手看清流程,下面用函数视图写出完整逻辑。
# grades/views.py from django.shortcuts import render, get_object_or_404, redirect from django.contrib import messages from .models import Student, Course, Score # 成绩列表 def score_list(request): # 关联查询,减少 SQL 查询次数 scores = Score.objects.select_related('student', 'course').all() # 支持按课程筛选 course_id = request.GET.get('course_id') if course_id: scores = scores.filter(course_id=course_id) return render(request, 'grades/score_list.html', {'scores': scores}) # 成绩录入 def score_add(request): if request.method == 'POST': student_id = request.POST.get('student') course_id = request.POST.get('course') score_value = request.POST.get('score') # 检查是否已存在同名学生同课程的成绩 if Score.objects.filter(student_id=student_id, course_id=course_id).exists(): messages.error(request, '该学生此课程已有成绩,请直接编辑') return redirect('grades:score_add') Score.objects.create( student_id=student_id, course_id=course_id, score=score_value ) messages.success(request, '成绩添加成功') return redirect('grades:score_list') students = Student.objects.all() courses = Course.objects.all() return render(request, 'grades/score_form.html', { 'students': students, 'courses': courses, })逻辑说明:select_related('student', 'course')是 Django 执行查询时的性能优化手段,它用一条带 JOIN 的 SQL 把关联对象一并取出,避免在模板里访问score.student.name时重复查询数据库。新增成绩前的去重检查属于业务兜底,虽然数据库层已经设置了unique_together,但提前给出友好提示比让用户看 500 错误体验好得多。
4.3 HTML 模板与表单提交:页面如何和视图交换数据
模板用 Django 模板语法写,表单的action留空表示提交到当前 URL,name属性必须与视图里request.POST.get()的 key 一一对应。
<!-- grades/templates/grades/score_form.html --> <!DOCTYPE html> <html lang="zh-cn"> <head> <meta charset="utf-8"> <title>录入成绩</title> </head> <body> <h2>录入学生成绩</h2> {% if messages %} {% for msg in messages %} <p style="color: {{ msg.tags }}">{{ msg }}</p> {% endfor %} {% endif %} <form method="post"> {% csrf_token %} <label>学生:</label> <select name="student"> {% for s in students %} <option value="{{ s.id }}">{{ s.student_no }} {{ s.name }}</option> {% endfor %} </select> <br> <label>课程:</label> <select name="course"> {% for c in courses %} <option value="{{ c.id }}">{{ c.course_name }}</option> {% endfor %} </select> <br> <label>成绩:</label> <input type="number" name="score" step="0.01" min="0" max="100" required> <br> <button type="submit">保存</button> </form> <a href="{% url 'grades:score_list' %}">返回列表</a> </body> </html>两条容易忽略的细节:{% csrf_token %}是 Django 的跨站请求伪造防护,模板里不写它会提示 403;input标签的step="0.01" min="0" max="100"让浏览器端先做格式校验,减少无效请求打到服务器。列表模板的写法类似,用{% for score in scores %}遍历并展示每个字段,这里不再重复贴出。
4.4 平均分与排名统计:用 annotate 聚合查询
学生成绩管理系统的核心价值在于统计——平均分、最高分、班级排名。Django 执行查询时最省心的方式是用annotate配合聚合函数,把统计交给数据库完成。
# 课程平均分与选课人数统计 from django.db.models import Avg, Max, Count def score_statistics(request): stats = Score.objects.values('course__course_name').annotate( avg_score=Avg('score'), max_score=Max('score'), student_count=Count('student'), ).order_by('-avg_score') return render(request, 'grades/score_statistics.html', {'stats': stats})参数说明:values('course__course_name')先按课程名分组,annotate为每组计算结果列——Avg('score')生成平均分,Count('student')统计选课人数。order_by('-avg_score')按平均分降序排,页面里用{{ item.avg_score|floatformat:1 }}过滤模板过滤器控制小数点位数即可。这样一个统计接口就能承载课程比较和成绩分析两个功能点。
5. 验证项目可复现:三个检查步骤与两个收尾技巧
5.1 按接口路径逐项验证功能是否完整
项目是否真正跑通,别只看默认欢迎页,建议按下面顺序在浏览器里逐项操作验证:
- 访问
127.0.0.1:8000/admin,用超级管理员账号登录,在后台分别添加 3 个学生、2 门课程,再录入 5 条成绩。此时 MySQL 的score_db库三张表已经各自落数据。 - 访问
127.0.0.1:8000/scores/,确认列表页能看到刚才录入的成绩,且学生姓名、课程名正确显示——如果显示成Student object (1),说明模板里没有使用.name属性。 - 在列表页点击新增成绩,故意录入已存在的「学生 + 课程」组合,确认出现友好错误提示而不是数据库异常报错。
- 打开 MySQL 客户端,执行
SELECT * FROM grades_score;,确认数据与页面一致。这里能验证中文字符集是否正常。
5.2 高概率踩坑排查表
| 报错现象 | 根本原因 | 解决方式 |
|---|---|---|
ModuleNotFoundError: No module named 'pymysql' | 未安装驱动 | 执行pip install pymysql,在__init__.py中引入 |
django.db.utils.OperationalError: (1045, Access denied) | 数据库账号密码错误或未授权 | 确认settings.py的 USER/PASSWORD,并执行 GRANT 授权 |
(1366, Incorrect string value: '\\xE5\\xBC\\xA0...') | 表或库字符集非 utf8mb4 | 建库时指定DEFAULT CHARACTER SET utf8mb4,删除重建不可逆 |
TemplateDoesNotExist | 模板目录结构错误 | 确认 templates 目录下按grades/score_list.html路径放置 |
| 表单提交显示 CSRF 403 | 模板缺少 csrf_token | 在 form 标签后第一行加入{% csrf_token %} |
5.3 admin 界面美化和批量导入数据
Django admin 界面美化是高频搜索词,但根本目的不是炫技,而是让非技术用户愿意使用系统。常规做法是继承 admin 自带的admin/base_site.html模板覆盖标题和样式:
# grades/admin.py 内追加 admin.site.site_header = '学生成绩管理后台' admin.site.site_title = '成绩管理' admin.site.index_title = '数据管理入口'这段代码把后台顶部的 Django administration 替换成中文标题,零成本提升可用性。如果想把 Excel 里的成绩批量导入,推荐用django-import-export库,在 admin 的 ModelAdmin 子类中添加入口。
# 需要先安装 pip install django-import-export from import_export import resources from import_export.admin import ImportExportModelAdmin class ScoreResource(resources.ModelResource): class Meta: model = Score class ScoreAdmin(ImportExportModelAdmin, admin.ModelAdmin): resource_class = ScoreResource配合import_export的导入页面,管理员可以上传 Excel 一键录入整班成绩,学习成本低且复用性高。做完这一层,这个学生成绩管理系统就从一个 demo 变成了真正能交给教务人员日常使用的工具,数据的维护、备份、迁移也都有了清晰的路径——后续需要扩展权限、导出报告时,这套 Django + MySQL 的骨架不用动,直接往上加模块即可。
本文还有配套的精品资源,点击获取