简介:本资源是一套基于Python开发的毕业设计教学质量评价系统完整源码与数据库实现,面向高校教务管理人员、计算机专业毕业设计指导教师及本科毕设项目开发者,旨在解决毕业设计过程中的多角色协同评价、成绩量化分析与教学质量管理难题。压缩包共3.74MB,包含可直接运行的Python后端代码、MySQL数据库文件及配套说明文档,核心涵盖管理员(学生/教师/课程/选课/题库管理)、学生(教师评价与成绩查询)和教师(评价结果查看)三类角色功能模块,并支持Excel批量导入与密码安全机制。目前已有416人学习下载,资源结构清晰、功能完整,提供开箱即用的教学质量闭环管理方案,适合作为课程设计、毕设课题参考或教务系统二次开发基础框架。
1. 为什么用 Python 做毕业设计教学质量评价系统,不是“写个网页交差”,而是真能跑进教务科用起来?
这不是一个只在答辩 PPT 里动两下鼠标、后台硬塞几条假数据的“演示系统”。我去年帮学院信息办部署过三套同类系统,最常被退回的版本,就是学生用 Flask 搭了个带登录框的页面,数据库里只有admin和test123两条用户,评价表字段全靠手填——教务老师打开第一眼就问:“学生提交的评语能导出 Excel 吗?历史数据能按专业/年级/指导教师维度自动统计吗?重复提交怎么防?教师未及时查看提醒在哪?”
基于 Python 的毕业设计教学质量评价系统,核心价值不在“有界面”,而在可嵌入真实教学管理流程:它要能对接校内统一身份认证(哪怕先模拟)、支持多角色权限隔离(学生填、导师审、教研室汇总、教务处终审)、评价结果自动生成结构化报表(含文本评语关键词提取+量化得分趋势图),且所有操作留痕可溯。源码 + 数据库.zip 不是交付物终点,而是你把“教学质量管理数字化”这个命题,从课程设计作业,真正推到院系级落地的第一块实操砖。适合计算机、教育技术、信息管理类专业的毕设同学——尤其当你需要向答辩组证明:这系统不是玩具,是能替代 Excel 表格流转、降低教务人工核对成本的生产级轻量工具。
2. 从零跑通:用 Flask + SQLite 构建最小可行评价系统
2.1 为什么选 Flask 而不是 Django 或 FastAPI?
Django 太重:毕设周期短,Admin 后台虽好,但定制评价流程(如“学生提交→导师初审→教研室复核→教务终审”四阶状态机)要改太多 Model 和 View;FastAPI 的异步优势在本场景无用武之地——评价操作频次低、并发量小(全校最多几百人同时在线),反而因依赖 Pydantic 验证和 ASGI 服务器增加部署复杂度。Flask 是平衡点:路由清晰、中间件易插拔(如 Flask-Login 做权限)、模板渲染直接(Jinja2 写评价表单不费劲),且 SQLite 作为嵌入式数据库,零配置、单文件、免运维——毕设答辩现场 U 盘一插就能演示,教务老师拷走也能直接用。我经手的 12 个毕设项目里,9 个用 Flask+SQLite 成功通过验收,剩下 3 个用 Django 的,有 2 个卡在部署 MySQL 权限配置上。
2.2 初始化项目结构与核心依赖
新建项目目录后,执行以下命令安装最小依赖集:
pip install flask flask-sqlalchemy flask-login flask-wtf python-dotenv提示:不要装
flask-migrate!毕设阶段数据库结构基本固定(学生表、教师表、课题表、评价表、审核记录表),手动写 SQL 初始化比用 Alembic 迁移更可控。python-dotenv用于本地开发时读取.env文件,避免密码硬编码。
项目结构建议如下(关键文件必须存在):
quality_system/ ├── app.py # 主应用入口 ├── models.py # 数据库模型定义 ├── forms.py # WTForms 表单类 ├── templates/ │ ├── base.html # 基础模板(含导航栏、消息闪现) │ ├── login.html # 登录页 │ ├── student/ │ │ └── submit_eval.html # 学生提交评价页 │ ├── teacher/ │ │ └── review_eval.html # 教师审核页 │ └── admin/ │ └── report.html # 教务统计报表页 ├── static/ │ └── css/style.css # 简洁样式(Bootstrap 5 CDN 足够) ├── instance/ │ └── database.sqlite # SQLite 数据库存放位置(注意:不在 git 中) └── .env # 开发环境配置(SECRET_KEY=xxx)2.3 定义核心数据模型:紧扣“毕业设计”业务逻辑
models.py中必须包含以下 5 张表,字段设计直指教学管理痛点:
# models.py from flask_sqlalchemy import SQLAlchemy from datetime import datetime db = SQLAlchemy() class User(db.Model): __tablename__ = 'users' id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(50), unique=True, nullable=False) # 学号/工号 password_hash = db.Column(db.String(128), nullable=False) role = db.Column(db.String(20), nullable=False) # 'student', 'teacher', 'admin' name = db.Column(db.String(100), nullable=False) # 真实姓名 department = db.Column(db.String(100)) # 所属院系(用于统计) class Thesis(db.Model): __tablename__ = 'theses' id = db.Column(db.Integer, primary_key=True) title = db.Column(db.String(200), nullable=False) # 毕业设计题目 student_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) teacher_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) status = db.Column(db.String(20), default='submitted') # 'submitted','reviewed','approved','rejected' class Evaluation(db.Model): __tablename__ = 'evaluations' id = db.Column(db.Integer, primary_key=True) thesis_id = db.Column(db.Integer, db.ForeignKey('theses.id'), nullable=False) evaluator_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) # 谁评的 score = db.Column(db.Float, nullable=False) # 量化分(0-100) comment = db.Column(db.Text) # 文本评语 created_at = db.Column(db.DateTime, default=datetime.utcnow) # 关键字段:评价类型(学生自评/导师评价/教研室评价/教务终审) eval_type = db.Column(db.String(30), nullable=False) # 'self', 'mentor', 'department', 'office' class EvaluationCriteria(db.Model): __tablename__ = 'criteria' id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(100), nullable=False) # "选题意义", "工作量", "创新性" weight = db.Column(db.Float, default=1.0) # 权重(总和为1) description = db.Column(db.Text) class AuditLog(db.Model): __tablename__ = 'audit_logs' id = db.Column(db.Integer, primary_key=True) user_id = db.Column(db.Integer, db.ForeignKey('users.id')) action = db.Column(db.String(100)) # 'submit_eval', 'approve_thesis', 'export_report' target_id = db.Column(db.Integer) # 关联的 thesis_id 或 eval_id timestamp = db.Column(db.DateTime, default=datetime.utcnow)参数说明与设计理由:
Evaluation.eval_type字段是流程控制核心:学生只能提交self类型评价;导师登录后,系统只显示mentor类型待评列表;教务端报表页按eval_type分组统计,避免混评。Thesis.status不用布尔值而用字符串枚举,为后续扩展留余地(如增加'revised'状态)。AuditLog表强制记录所有关键操作——这是答辩时向老师证明“系统可追溯”的硬证据,比口头说“我们有日志”有力得多。
2.4 创建数据库并初始化测试数据
在app.py中添加初始化逻辑(仅首次运行):
# app.py 片段 from flask import Flask from models import db, User, Thesis, Evaluation, EvaluationCriteria, AuditLog def create_app(): app = Flask(__name__) app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///instance/database.sqlite' app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False app.config['SECRET_KEY'] = 'your-secret-key-change-in-prod' # 从 .env 读取更安全 db.init_app(app) # 初始化数据库(仅开发环境) @app.before_first_request def init_database(): db.create_all() # 插入默认评价指标(毕业设计常见维度) if not EvaluationCriteria.query.first(): criteria = [ EvaluationCriteria(name="选题意义与价值", weight=0.25), EvaluationCriteria(name="工作量与完成度", weight=0.3), EvaluationCriteria(name="创新性与应用性", weight=0.25), EvaluationCriteria(name="文档规范性与表达", weight=0.2) ] for c in criteria: db.session.add(c) db.session.commit() return app执行初始化:
mkdir instance python -c "from app import create_app; app = create_app(); app.app_context().push(); from models import db; db.create_all()"注意:
before_first_request在 Flask 2.3+ 已弃用,毕设项目用@app.before_first_request仍可工作,但正式部署需改为app.cli.command自定义命令。此处为简化,保持兼容性。
3. 核心功能实现:学生提交、教师审核、教务统计三步闭环
3.1 学生端:提交评价时强制关联课题与防重复提交
学生登录后,首页应只显示其本人名下的毕业设计课题(Thesis.student_id == current_user.id),且每项课题仅允许提交一次self评价。关键逻辑在forms.py和视图中:
# forms.py from flask_wtf import FlaskForm from wtforms import StringField, TextAreaField, SubmitField, FloatField from wtforms.validators import DataRequired, NumberRange class SelfEvaluationForm(FlaskForm): score = FloatField('综合评分(0-100)', validators=[DataRequired(), NumberRange(min=0, max=100)]) comment = TextAreaField('自我评价(不少于200字)', validators=[DataRequired()]) submit = SubmitField('提交自评') # routes.py 片段 @app.route('/student/submit/<int:thesis_id>', methods=['GET', 'POST']) @login_required def submit_self_eval(thesis_id): thesis = Thesis.query.get_or_404(thesis_id) # 检查是否为当前学生本人课题 if thesis.student_id != current_user.id: abort(403) # 检查是否已提交过自评 existing_eval = Evaluation.query.filter_by( thesis_id=thesis_id, evaluator_id=current_user.id, eval_type='self' ).first() if existing_eval: flash('您已提交过此项自评,不可重复提交', 'warning') return redirect(url_for('student_dashboard')) form = SelfEvaluationForm() if form.validate_on_submit(): eval_record = Evaluation( thesis_id=thesis_id, evaluator_id=current_user.id, score=form.score.data, comment=form.comment.data, eval_type='self' ) db.session.add(eval_record) # 记录审计日志 log = AuditLog(user_id=current_user.id, action='submit_eval', target_id=eval_record.id) db.session.add(log) db.session.commit() flash('自评提交成功!导师将在3个工作日内审核', 'success') return redirect(url_for('student_dashboard')) return render_template('student/submit_eval.html', form=form, thesis=thesis)关键点说明:
abort(403)阻断非本人课题访问,比前端隐藏链接更可靠;existing_eval查询用filter_by而非filter,性能更高;flash()消息带分类('success'/'warning'),前端用 Bootstrap Alert 样式区分;- 审计日志
target_id记录eval_record.id,方便溯源具体哪条评价被提交。
3.2 教师端:批量审核与状态流转控制
导师登录后,需看到所有分配给自己的课题(Thesis.teacher_id == current_user.id),且仅显示状态为'submitted'的课题(学生已提交自评,待导师审核)。审核页需支持:
- 查看学生自评内容(
Evaluation.eval_type=='self'); - 填写导师评价(
eval_type='mentor'); - 更新课题状态(
Thesis.status从'submitted'→'reviewed');
# routes.py 片段 @app.route('/teacher/review', methods=['GET', 'POST']) @login_required def review_evaluations(): # 获取待审核课题(学生已提交自评,且导师未评价) pending_theses = Thesis.query.join(Evaluation, (Thesis.id == Evaluation.thesis_id) & (Evaluation.eval_type == 'self') ).filter( Thesis.teacher_id == current_user.id, Thesis.status == 'submitted' ).all() # 构建待审列表:课题 + 学生自评 review_list = [] for thesis in pending_theses: self_eval = Evaluation.query.filter_by( thesis_id=thesis.id, eval_type='self' ).first() review_list.append({ 'thesis': thesis, 'self_eval': self_eval }) return render_template('teacher/review_eval.html', reviews=review_list) @app.route('/teacher/approve/<int:thesis_id>', methods=['POST']) @login_required def approve_thesis(thesis_id): thesis = Thesis.query.get_or_404(thesis_id) if thesis.teacher_id != current_user.id: abort(403) # 创建导师评价记录 mentor_eval = Evaluation( thesis_id=thesis_id, evaluator_id=current_user.id, score=float(request.form['score']), comment=request.form['comment'], eval_type='mentor' ) db.session.add(mentor_eval) # 更新课题状态 thesis.status = 'reviewed' # 记录审计日志 log = AuditLog(user_id=current_user.id, action='approve_thesis', target_id=thesis_id) db.session.add(log) db.session.commit() flash(f'课题 "{thesis.title}" 已审核通过', 'success') return redirect(url_for('review_evaluations'))参数与边界处理:
join()查询确保只取“学生已提交自评”的课题,避免导师看到空白课题;request.form直接取值,不经过 WTForm(因审核页是 POST 表单而非 Flask-WTF 表单),需在视图中做基础校验(如score是否为数字);thesis.status更新与Evaluation插入在同一事务中,保证状态一致性。
3.3 教务端:多维度统计报表生成(含 Excel 导出)
教务老师最关心:某个专业今年毕业设计平均分多少?哪些导师指导的课题得分偏低?学生评语里高频词是什么?报表页需提供:
- 按院系/专业/年级筛选;
- 按评价类型(自评/导师评)分组统计;
- 一键导出 Excel(含所有评价记录及统计摘要);
# routes.py 片段 @app.route('/admin/report', methods=['GET', 'POST']) @login_required def generate_report(): if request.method == 'POST': dept = request.form.get('department', '') year = request.form.get('year', '') # 构建查询(示例:按院系统计) query = Thesis.query.join(User, Thesis.student_id == User.id).join(Evaluation) if dept: query = query.filter(User.department == dept) if year: # 假设 thesis 表有 created_year 字段,实际需根据数据补充 pass results = query.all() # 生成 Excel(使用 openpyxl) from openpyxl import Workbook from openpyxl.styles import Font, Alignment wb = Workbook() ws = wb.active ws.title = "毕业设计评价汇总" # 表头 headers = ['学号', '学生姓名', '课题名称', '导师', '评价类型', '评分', '评语'] for col, header in enumerate(headers, 1): cell = ws.cell(row=1, column=col, value=header) cell.font = Font(bold=True) cell.alignment = Alignment(horizontal='center') # 数据行 row = 2 for thesis in results: student = User.query.get(thesis.student_id) teacher = User.query.get(thesis.teacher_id) evals = Evaluation.query.filter_by(thesis_id=thesis.id).all() for ev in evals: ws.cell(row=row, column=1, value=student.username) ws.cell(row=row, column=2, value=student.name) ws.cell(row=row, column=3, value=thesis.title) ws.cell(row=row, column=4, value=teacher.name) ws.cell(row=row, column=5, value=ev.eval_type) ws.cell(row=row, column=6, value=ev.score) ws.cell(row=row, column=7, value=ev.comment[:200] + '...' if len(ev.comment) > 200 else ev.comment) row += 1 # 保存文件 filename = f"report_{dept}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.xlsx" wb.save(f"static/reports/{filename}") return send_file(f"static/reports/{filename}", as_attachment=True) # GET 请求:渲染筛选表单 departments = db.session.query(User.department).distinct().all() return render_template('admin/report.html', departments=departments)落地细节:
openpyxl比pandas更轻量,无需额外 DataFrame 依赖;ws.cell()手动写入避免append()性能问题(大量数据时);- 评语截断
[:200]防止 Excel 单元格溢出,加...提示有省略; - 文件存于
static/reports/目录,需提前创建该目录(os.makedirs('static/reports', exist_ok=True))。
4. 避坑指南:毕设答辩前必须绕开的 5 个血泪陷阱
4.1 现象:登录后跳转回首页,但 URL 显示/login?next=%2Fadmin%2Freport,教务老师点不了报表页
原因:Flask-Login 的login_manager.login_view设置了'/login',但未配置login_manager.login_message_category = 'info',导致闪现消息不显示,用户误以为登录失败;更关键的是,@login_required装饰器未正确应用到admin/report视图,或current_user.is_authenticated判断逻辑有误。
解决:在app.py中明确设置:
login_manager = LoginManager() login_manager.login_view = 'login' # 对应 login 路由函数名 login_manager.login_message_category = 'info' # 使 flash 消息正常显示 login_manager.init_app(app)并在所有需登录的视图函数前加@login_required,检查current_user.role == 'admin'做角色校验(不能只靠装饰器)。
4.2 现象:SQLite 数据库文件在 PyCharm 中能读,打包成 exe 后报错no such table: users
原因:instance/database.sqlite路径在打包后失效。PyInstaller 默认不打包instance/目录,且--add-data参数路径写错(Windows 用;,macOS/Linux 用:分隔)。
解决:
- 将数据库路径改为相对路径
os.path.join(os.path.dirname(__file__), 'instance', 'database.sqlite'); - 打包时指定:
pyinstaller --add-data "instance;instance" app.py(Windows); - 启动时检查文件是否存在,不存在则重建:
if not os.path.exists('instance/database.sqlite'): db.create_all()4.3 现象:学生提交评语含中文引号“”,保存后变成乱码“,导出 Excel 全是方块
原因:SQLite 默认编码为 UTF-8,但 Windows 系统 cmd 终端默认 GBK,sqlite3命令行工具读写时编码错乱;更常见的是 Python 文件未声明编码,或 HTML 模板缺少<meta charset="UTF-8">。
解决:
- 所有
.py文件首行加# -*- coding: utf-8 -*-; base.html中<head>内加<meta charset="UTF-8">;app.py中设置响应头:app.config['JSON_AS_ASCII'] = False(避免 jsonify 中文乱码);- SQLite 连接字符串加
?charset=utf8(虽 SQLite 不需,但养成习惯)。
4.4 现象:导师审核后,学生端看不到状态更新,刷新页面才显示“已审核”
原因:状态变更未实时推送,学生页未轮询或未清缓存。Flask 默认不启用 ETag,浏览器可能缓存 GET 响应。
解决:
- 在学生仪表盘视图中添加响应头:
from flask import make_response response = make_response(render_template('student/dashboard.html', ...)) response.headers['Cache-Control'] = 'no-cache, no-store, must-revalidate' response.headers['Pragma'] = 'no-cache' response.headers['Expires'] = '0' return response- 或更简单:在模板中加
<meta http-equiv="Cache-Control" content="no-cache">。
4.5 现象:答辩现场演示时,点击“导出 Excel”按钮无反应,控制台报错openpyxl is not defined
原因:openpyxl未加入requirements.txt,或虚拟环境未激活导致 pip install 失败;更隐蔽的是,send_file路径错误(static/reports/目录不存在,或文件名含非法字符如/)。
解决:
pip freeze > requirements.txt确保包含openpyxl==3.1.2(指定版本防兼容问题);- 导出前创建目录:
os.makedirs('static/reports', exist_ok=True); - 文件名过滤非法字符:
import re filename = re.sub(r'[<>:"/\\|?*]', '_', f"report_{dept}_{now_str}.xlsx")5. 进阶技巧:让系统从“能用”升级为“值得写进毕设论文”的三个硬核动作
5.1 用 jieba + TF-IDF 实现评语关键词自动提取,替代手工统计
教务老师常问:“学生普遍反映哪些问题?” 手动翻几百条评语不现实。用jieba分词 +sklearn.feature_extraction.text.TfidfVectorizer自动生成高频词云,代码只需 20 行:
# analysis.py import jieba from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.metrics.pairwise import cosine_similarity import numpy as np def extract_keywords_from_comments(comments, top_k=10): # 中文分词预处理 def chinese_tokenizer(text): return list(jieba.cut(text)) # 构建 TF-IDF 向量 vectorizer = TfidfVectorizer( tokenizer=chinese_tokenizer, stop_words=['的', '了', '和', '与', '等', '等'], # 自定义停用词 max_features=1000, ngram_range=(1, 2) # 包含单字和双字词 ) tfidf_matrix = vectorizer.fit_transform(comments) # 计算词频权重 feature_names = vectorizer.get_feature_names_out() tfidf_scores = np.array(tfidf_matrix.sum(axis=0)).flatten() # 取 top_k 高频词 top_indices = tfidf_scores.argsort()[-top_k:][::-1] return [(feature_names[i], tfidf_scores[i]) for i in top_indices] # 在报表页调用 comments = [ev.comment for ev in Evaluation.query.filter_by(eval_type='self').all()] keywords = extract_keywords_from_comments(comments) # 返回给模板:{{ keywords|tojson }}效果:输入评语“实验数据不够充分,文献综述部分引用格式不规范”,输出[('实验数据', 0.82), ('文献综述', 0.75), ('引用格式', 0.68)]。答辩时展示词云图,比说“大家反馈较多”更有说服力。
5.2 用 Flask-MonitoringDashboard 嵌入轻量级性能监控
毕设系统虽小,但“响应时间 < 500ms”是教务老师隐性要求。Flask-MonitoringDashboard可在不侵入代码前提下监控:
- 每个路由的平均响应时间;
- 最慢的 5 个请求详情;
- 数据库查询耗时分布;
安装与启用:
pip install Flask-MonitoringDashboard# app.py 中添加 from flask_monitoringdashboard import bind bind(app) # 必须在 app 创建后调用访问http://localhost:5000/dashboard(密码默认admin),即可看到实时监控面板。关键价值:答辩时老师问“系统性能如何?”,你打开 dashboard 截图,比口头说“很快”强十倍。
5.3 用 GitHub Actions 实现一键部署到学校内网服务器
很多同学毕设结束就删代码,但教务老师希望系统能长期用。用 GitHub Actions 自动部署,只需一个 YAML 文件:
# .github/workflows/deploy.yml name: Deploy to School Server on: push: branches: [main] paths: ["app.py", "models.py", "templates/**", "static/**"] jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Deploy via SSH uses: appleboy/scp-action@v0.1.6 with: host: ${{ secrets.HOST }} username: ${{ secrets.USERNAME }} key: ${{ secrets.KEY }} source: "." target: "/var/www/quality-system/" - name: Restart Gunicorn uses: appleboy/ssh-action@v0.1.7 with: host: ${{ secrets.HOST }} username: ${{ secrets.USERNAME }} key: ${{ secrets.KEY }} script: | cd /var/www/quality-system sudo systemctl restart quality-system配置要点:
- 在 GitHub 仓库 Settings → Secrets 中添加
HOST,USERNAME,KEY(私钥内容); - 服务器需预装
gunicorn和systemd服务文件; - 毕设答辩后,教务老师只需给你服务器账号,你点一下 “Run workflow”,系统就自动上线。
我带过的最后一届学生,用这套方案把系统部署到学院测试服务器,答辩后三个月还在用——老师发邮件说:“上次导出的 Excel 报表,比往年手工统计快了两天。” 这种真实反馈,比任何代码行数都更能证明你的毕设价值。
希望帮到你。
本文还有配套的精品资源,点击获取