简介:这是一份基于Django+MySQL+Bootstrap架构的学生管理系统毕业设计项目,适合计算机相关专业在校生用于毕设、课设或项目初期立项演示,也适合有一定Python基础的学习者作为Web开发进阶案例。资源包共104个文件,涵盖24个Python源码文件、17个HTML页面模板、14个SQL数据库脚本,以及图片、CSS、JavaScript等静态资源,整体大小约8.84MB,目录结构清晰,便于按功能模块阅读与二次开发。项目包含学生、教师、课程、成绩等核心模块,并配有登录认证与数据管理界面,可帮助快速理解Django MTV开发模式与MySQL联调流程。目前已有116人学习下载,代码均通过运行测试,可直接部署使用或在此基础上扩展功能。
1. 为什么毕设里总能看到 Django+MySQL+Bootstrap 这套组合
每年毕业答辩前,计算机专业的学生都会从学长手里接过一个命名高度一致的文件:xx管理系统设计与实现+使用说明.zip。学生管理系统恰好是这类项目的“标准件”:Django 负责 ORM 和 Admin 后台,MySQL 负责持久化,Bootstrap 负责页面观感。这套组合能同时满足“有真实数据库、有后台管理、有页面交互”三个验收点,企业初级 Django 岗位也常拿它当常规练习。
但真正落地这套组合,坑都在教科书写不到的地方:Python 版本、mysqlclient 编译、数据库编码、静态文件路径。这篇博文把模型设计、环境搭建、前端集成和使用说明放在一条线上,适合准备答辩的学生,也适合接手同类 Django 项目的维护者。
2. 先把 Django+MySQL 运行环境跑起来:从 Python 安装到依赖配置
2.1 用虚拟环境隔离项目依赖
Python 项目最怕的就是全局装了一片包之后,A 项目要 Django 3.2、B 项目要 Django 4.2,一升级全部崩掉。所以我拿到这套系统的目录结构后,第一件事永远是先建一个虚拟环境,而不是直接 pip install django。虚拟环境会把 site-packages 隔离在项目目录下的 venv 文件夹里,后面不管怎么折腾依赖,都不会污染系统环境。
python -m venv venv # Windows 激活 venv\Scripts\activate # macOS / Linux 激活 source venv/bin/activate python -m pip install --upgrade pip激活成功后,命令行提示符前面会多出一个 venv 标识,这时 pip 装的包都会进这个虚拟环境。Windows 下如果 python 命令找不到,需要先到 python.org 安装 Python 并勾选 Add Python to PATH;macOS 上若同时有 python3 和 python,确认用的是 3.8 以上的解释器。之后的所有依赖安装、migrate 和 runserver 都要在激活状态下执行,激活只是当前终端的会话级行为,不会影响其他窗口。
2.2 安装 Django、mysqlclient 和其它依赖
一个典型的学生管理系统 requirements.txt 内容大概是这样,具体版本以 zip 包里的为准:
Django>=4.2,<5.0 mysqlclient>=2.2.0 python-dotenv>=1.0.0安装只需要一行命令:pip install -r requirements.txt。Django 4.2 的兼容性和资料密度都比较合适,官方支持到 2026 年;mysqlclient 是 Django 官方推荐的 MySQL 驱动,性能比 pymysql 稳妥,报错信息也更容易搜到。python-dotenv 用来把数据库密码、Secret Key 放到 .env 文件,避免把真实凭据写进 settings.py 再随 zip 包散播出去。
如果 mysqlclient 在 Windows 上报编译错误,别硬扛。常见做法是先升级 Visual Studio 的 C++ 构建工具,或者直接下载对应 Python 版本的预编译 wheel,装进项目环境。以 Python 3.11 为例,whl 文件名里会带 cp311 标记,下载后执行pip install 包名.whl即可,整个安装过程不会再碰 C 编译器。下表是这套系统最可能出现的三个依赖,每一项缺了都会在启动阶段立刻暴露。
| 依赖 | 作用 | 缺失时的现象 |
|---|---|---|
| Django | Web 框架、ORM、Admin | 提示 No module named 'django' |
| mysqlclient | 连接 MySQL 的数据库驱动 | 迁移时报 ModuleNotFoundError: MySQLdb |
| python-dotenv | 读取 .env 配置项 | 数据库密码硬编码在代码里 |
这几种依赖缺失时,错误信息都能在启动日志里直接看到,按报错去定位比盲装包快得多。
2.3 在 settings.py 里配置 MySQL 连接
这一步是环境能否跑通的命门。打开 manage.py 同级的 settings.py,把 DATABASES 替换成下面的结构:
import os import pymysql # 如果使用的是 mysqlclient,不需要这行;只有环境里装了 pymysql 才加 pymysql.install_as_MySQLdb() DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': os.getenv('DB_NAME', 'student_system'), 'USER': os.getenv('DB_USER', 'root'), 'PASSWORD': os.getenv('DB_PASSWORD', ''), 'HOST': os.getenv('DB_HOST', '127.0.0.1'), 'PORT': os.getenv('DB_PORT', '3306'), 'OPTIONS': { 'charset': 'utf8mb4', 'sql_mode': 'STRICT_TRANS_TABLES', }, } }Django 通过 ENGINE 判断使用哪套数据库后端,mysql 后端内部调用 mysqlclient 与 3306 端口上的 MySQL 服务通信。NAME 是数据库名,必须先在 MySQL 里手动创建;USER 和 PASSWORD 用本地 MySQL 的账号。OPTIONS 里的 charset 统一写 utf8mb4,否则插入生僻字或表情符号会报 Incorrect string value。sql_mode 设置成 STRICT_TRANS_TABLES 之后,写入超长字段会直接报错而不是静默截断,调试时能少踩很多坑。
注意 MySQL 8.0 默认认证插件是 caching_sha2_password,mysqlclient 2.2 已经兼容;如果用的是旧版本,会报 Authentication plugin ‘caching_sha2_password’ cannot be loaded。解决办法是把账号插件改回 mysql_native_password:
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '你的密码';执行完再重启 runserver。生产环境不建议长期用 native_password,这只是拿到 zip 包后本地快速跑通的手段。
2.4 建库和连接排错
先在终端里建库再迁移。我一般建议手工建库而不是让 Django 自动建,因为数据库的字符集和排序规则要提前定好:
mysql -u root -p -e "CREATE DATABASE IF NOT EXISTS student_system DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"-e是让 mysql 客户端直接执行后面的 SQL,不进入交互模式。DEFAULT CHARACTER SET 指定默认字符集,COLLATE 指定排序规则。utf8mb4_unicode_ci 在中文排序和模糊查询上比较稳定。建完库后,先跑python manage.py check看配置是否有误,输出 System check identified no issues 说明 Django 已经能连上数据库。
常见错误分三类:一是 Access denied for user,说明密码或账号不对;二是 Can't connect to MySQL server,说明 MySQL 服务没启动或端口不对,Windows 到服务管理器看 MySQL80,Linux 用systemctl status mysqld;三是 Unknown database,说明没建库或库名不匹配。如果 zip 包里附带了 .sql 文件,也可以直接导入,这条路径留到第 5 章讲。
3. 学生管理系统的数据模型:从班级到成绩的 ORM 设计
3.1 需求上最常见的四张表:学生、班级、课程、成绩
学生管理系统的核心业务是“维护人员”和“维护成绩”。绝大多数设计最终会落到四张基表:班级表、学生表、课程表、成绩表。班级和学生是一对多,一个班级有多个学生;学生和课程是多对多,一次选课最终落到成绩表里的一条记录。成绩表本质上是一张连接表,用 Django 可以建模为一个普通 Model,同时挂两个外键,再加一个分数字段。
这个结构的好处是扩展方便。后期要加教师模块,只需要在课程表上再挂一个外键;要算绩点,可以在成绩表里增加学分字段或单独建课程信息表。真正要提前想清楚的是唯一性约束:同一个学生同一门课不能有两条成绩,所以要在数据库层约束 (student, course) 的组合唯一。Django 的 Meta.unique_together 恰好解决这个问题。
3.2 models.py 里的具体字段与 MySQL 类型映射
一份能跑的 models.py 长这样:
from django.db import models class ClassInfo(models.Model): """班级表""" name = models.CharField('班级名称', max_length=50, unique=True) counselor = models.CharField('辅导员', max_length=20, blank=True) class Meta: db_table = 'class_info' ordering = ['name'] class Student(models.Model): """学生表""" no = models.CharField('学号', max_length=20, unique=True) name = models.CharField('姓名', max_length=30) gender = models.CharField('性别', max_length=2, choices=[('M', '男'), ('F', '女')]) class_info = models.ForeignKey(ClassInfo, on_delete=models.PROTECT, db_column='class_id', verbose_name='班级') phone = models.CharField('手机号', max_length=11, blank=True) class Meta: db_table = 'student' class Course(models.Model): """课程表""" code = models.CharField('课程编号', max_length=10, unique=True) name = models.CharField('课程名', max_length=40) class Meta: db_table = 'course' class Score(models.Model): """成绩表""" student = models.ForeignKey(Student, on_delete=models.CASCADE, related_name='scores') course = models.ForeignKey(Course, on_delete=models.CASCADE) score = models.DecimalField('成绩', max_digits=5, decimal_places=2) class Meta: db_table = 'score' unique_together = ('student', 'course')逐项推字段取舍。学号和课程编号都用 unique=True,业务上不允许重复;但不建议拿学号当主键,以后转专业或编号规则一改,自增主键更好维护。ForeignKey 的 on_delete 必须给值:班级删除时学生会无家可归,所以用 PROTECT 阻止删除班级;成绩跟着学生走,学生删了成绩没有意义,所以 CASCADE。db_column 指定数据库里的列名,让表结构更短;db_table 则可以把模型映射到 MySQL 里已有的表,这对导入 zip 包自带的 .sql 文件特别有用。
DecimalField 比 FloatField 更适合成绩,浮点会累积误差。max_digits=5 允许最大 999.99 分,decimal_places=2 控制小数位,映射到 MySQL 是 decimal(5,2)。gender 用 choices 之后,页面通过get_gender_display就能拿到中文“男/女”,不用单独建字典表。
3.3 用 makemigrations 和 migrate 把模型同步到 MySQL
Django 不直接读 models.py 建表,而是两步走:先生成迁移文件,再执行迁移。这样迁移文件可以被版本管理,别人拿到 zip 包后跑同一个 migrate,表结构就能保持一致。
python manage.py makemigrations python manage.py migrate python manage.py showmigrationsmakemigrations 执行后会在应用的 migrations 目录下生成 0001_initial.py,里面是用 Python 描述的建表操作。migrate 会逐个执行迁移,并写入 django_migrations 表,避免重复执行。showmigrations 可以看哪些已应用,哪些没有。如果 MySQL 里已经有旧表但迁移没记录,通常需要清掉旧表重新迁移。
如果你不想用 ORM,也可以直接导入 zip 包里的 .sql 文件:
mysql -u root -p student_system < student_system.sql导入前先确认数据库字符集,否则中文会变乱码。导入之后再用python manage.py migrate --fake-initial告诉 Django “表已存在”,这样后续新增的迁移不会跟已有结构冲突。
3.4 用自带 Admin 后台验证模型关系
Django Admin 是这套组合里最值钱的部分,能把增删改查的演示成本降到零。在 admin.py 里注册模型:
from django.contrib import admin from .models import Student, Course, Score, ClassInfo @admin.register(Student) class StudentAdmin(admin.ModelAdmin): list_display = ('no', 'name', 'gender', 'class_info', 'phone') list_filter = ('gender', 'class_info') search_fields = ('no', 'name')注册后执行python manage.py createsuperuser创建管理员,启动 runserver 后访问/admin/。list_display 控制后台列表显示哪些列,list_filter 在右侧生成筛选面板,search_fields 指定搜索框搜哪些字段。这三件事不需要写任何前端,就能让评委直观看到数据关系。
需要删除对象时,在 Django shell 里执行:
python manage.py shellStudent.objects.filter(no='2023001').delete()注意 CASCADE 关联的成绩会一起删除;PROTECT 外键存在时则会抛出 ProtectedError 并拒绝删除。这就是 on_delete 选择在真实数据上的表现,比背文档直观得多。
4. 用 Bootstrap 做界面:模板继承与表格展示
学生管理系统的前端页面,最省力又体面的方案是把 Bootstrap 本地化,然后做一个 base.html 让所有页面继承。别用 CDN 链接,答辩现场没有外网的情况太常见了。用本地静态文件除了离线可用,还能在局域网演示时保持同样的加载速度。
4.1 静态文件目录放 Bootstrap 资源
标准做法是在项目根目录建 static/ 文件夹,里面放第三方库和自定义样式。如果没有,需要自行补建。目录结构参考:
manage.py templates/ base.html student/ student_list.html static/ bootstrap/ css/bootstrap.min.css js/bootstrap.bundle.min.js css/custom.css对应到 settings.py 的配置是 STATIC_URL、STATICFILES_DIRS 和 STATIC_ROOT:
STATIC_URL = '/static/' STATICFILES_DIRS = [ BASE_DIR / 'static', ] STATIC_ROOT = BASE_DIR / 'staticfiles'STATIC_URL 是浏览器访问静态文件的 URL 前缀,模板里用{% static 'bootstrap/css/bootstrap.min.css' %}引用;STATICFILES_DIRS 告诉开发服务器去哪找文件;STATIC_ROOT 是部署时collectstatic的输出目录。三个概念不能混,最常见的症状是页面能渲染但样式 404,就是因为模板里直接写了/static/...路径而 STATIC_URL 改过。
4.2 一个带 Bootstrap 的基础模板
base.html 把导航、内容区、脚本都固定下来,子页面只填充 block:
{% load static %} <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>{% block title %}学生管理系统{% endblock %}</title> <link rel="stylesheet" href="{% static 'bootstrap/css/bootstrap.min.css' %}"> </head> <body> <nav class="navbar navbar-expand-lg navbar-dark bg-dark"> <a class="navbar-brand" href="{% url 'student_list' %}">学生管理系统</a> </nav> <main class="container mt-3"> {% block content %}{% endblock %} </main> <script src="{% static 'bootstrap/js/bootstrap.bundle.min.js' %}"></script> {% block extra_js %}{% endblock %} </body> </html>{% load static %}提供 static 标签,{% block content %}是子模板要覆盖的坑。Bootstrap 5 的 bundle.min.js 已经包含 Popper,下拉菜单和模态框不需要额外引 jQuery。如果想少背一套依赖,直接让项目停留在 Bootstrap 5 就好;如果 zip 包用的是 Bootstrap 4,则要严格按先 jquery 后 bootstrap 的顺序引脚本。
4.3 学生列表页:表格渲染与分页控件
这里是数据流最直观的部分,视图拿到分页对象,模板渲染成表格:
from django.core.paginator import Paginator def student_list(request): students = Student.objects.select_related('class_info').order_by('no') paginator = Paginator(students, 10) page_number = request.GET.get('page') page_obj = paginator.get_page(page_number) return render(request, 'student_list.html', {'page_obj': page_obj})视图里拿到 page_obj 后,模板循环就不复杂了:
<table class="table table-striped table-hover align-middle"> <thead> <tr> <th>学号</th><th>姓名</th><th>性别</th><th>班级</th><th>操作</th> </tr> </thead> <tbody> {% for s in page_obj %} <tr> <td>{{ s.no }}</td> <td>{{ s.name }}</td> <td>{{ s.get_gender_display }}</td> <td>{{ s.class_info.name }}</td> <td> <a href="{% url 'student_edit' s.pk %}" class="btn btn-sm btn-outline-primary">编辑</a> <a href="{% url 'student_delete' s.pk %}" class="btn btn-sm btn-outline-danger">删除</a> </td> </tr> {% empty %} <tr><td colspan="5" class="text-center">暂无学生</td></tr> {% endfor %} </tbody> </table> <nav> <ul class="pagination"> {% if page_obj.has_previous %} <li class="page-item"> <a class="page-link" href="?page={{ page_obj.previous_page_number }}">上一页</a> </li> {% endif %} <li class="page-item active"> <span class="page-link">{{ page_obj.number }}</span> </li> {% if page_obj.has_next %} <li class="page-item"> <a class="page-link" href="?page={{ page_obj.next_page_number }}">下一页</a> </li> {% endif %} </ul> </nav>select_related('class_info')把班级信息的一条 SQL JOIN 查回来,避免每行都触发一次查询,这是 Django 性能优化的第一课。Paginator 的第二个参数是每页条数,改大改小直接反馈到表格长度。模板里的page_obj.has_previous和has_next自动判断边界,空数据时用{% empty %}显示占位。分页链接用?page=传递页码,Bootstrap 的 pagination 样式直接套在 ul 上就可以。
其中删除链接用的是 GET,演示没问题,生产建议改成带 csrf_token 的 POST 表单。
4.4 下拉菜单、日期组件的 Bootstrap 与 jQuery 配合
除表格外,这类系统常用的筛选下拉菜单需要 JavaScript 才能展开。zip 包里常见两种依赖组织:Bootstrap 5 只引 bundle.min.js,Bootstrap 4 则要引 jquery.min.js 再引 bootstrap.min.js,顺序反了菜单点不开。
一个“按班级筛选”的下拉菜单可以这样写:
<div class="dropdown"> <button class="btn btn-secondary dropdown-toggle" >python -m venv venv venv\Scripts\activate pip install -r requirements.txt mysql -u root -p -e "CREATE DATABASE IF NOT EXISTS student_system DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" python manage.py migrate python manage.py createsuperuser python manage.py runserver 8000这个序列每一步都有目的:activate 把后续命令锁进虚拟环境;migrate 建表;createsuperuser 创建后台账号;runserver 起开发服务。如果数据库已经导入过 .sql 数据,就把 migrate 换成python manage.py migrate --fake-initial,避免重复建表报错。启动后访问/admin/,能登录并看到学生、课程、成绩菜单,就说明系统基础可用。
5.3 三个最容易卡住的错误
第一类,ModuleNotFoundError: No module named 'mysqlclient'。依赖没装全或版本不对,回到 2.2 用预编译 wheel 解决。第二类,ProgrammingError: (1146, "Table 'student_system.auth_user' doesn't exist")。migrate 没有成功执行,查一下数据库账号是否有建表权限,以及迁移过程有没有中断。第三类,UnicodeDecodeError: 'utf-8' codec can't decode byte。多半是 .sql 文件用了 GBK 编码,导入前把文件另存为 UTF-8,或者连接时加--default-character-set=utf8mb4。
到了部署阶段,最常见的是宝塔面板配 Django。部署时记得先collectstatic,再把项目目录和静态文件目录指给 Nginx;settings.py 里 DEBUG 必须设为 False,ALLOWED_HOSTS 填服务器 IP 或域名,否则反代后页面会一片空白。还需要留意 MySQL 的 wait_timeout 默认是 8 小时,Django 长连接闲置超时后会抛 “MySQL server has gone away”,在数据库 OPTIONS 里加'connect_timeout': 5,并给连接用一个不大于 wait_timeout 的存活时间,这个坑就压住了。
本文还有配套的精品资源,点击获取