
简介基于Python与Django框架开发的教师教学质量评价系统定位为毕业设计/课程设计完整项目适合计算机相关专业学生用于提交毕设、期末大作业或快速入门Web开发实战。项目包含前端页面、后端逻辑与数据库覆盖评价指标管理、学生打分、教师查看结果等典型教学评价流程。压缩包共134个文件以HTML网页模板、Python源码、JS/CSS前端资源为主辅以SQLite数据库、SQL脚本和XML配置结构清晰。资源包仅3.78MB轻量易部署。目前已有264人学习下载适合需要参考完整实现或直接在此基础上扩展的开发者。项目内置多个页面模板与基础布局可直观了解页面跳转与模块组织方式Python代码分层较清晰便于二次开发、调试和论文写作时对照讲解是兼顾学习与实战的高性价比资料。1. 教师教学质量评价系统毕设题里最绕不开的Django实战项目年末一降温毕设选题表就在群里传疯了。教师教学质量评价系统这个名字年年出现表面看就是给学生填一个打分页面再展示排名。但真的动手做一个能过答辩的Django项目要练的内容远不止写页面教师与课程数据怎么建模、学号与课程怎么约束才能防重复提交、三个评分维度怎么按权重聚合成总分、最后怎么把结果导出来装进数据库文件。这套系统适合正在选方向的毕设学生也适合想在校内小范围替代纸质评教的教务人员。标题里的这套源码加数据库就是一个把以上逻辑都落地的参考实现。2. 模型设计先行数据表关系与Django骨架搭建2.1 评价数据里的三张核心表教师、评价指标、评价记录一个评价系统能不能撑住答辩第一眼的判断标准是数据表设计。我的习惯是先画表关系再写代码。教师表存工号、姓名、院系工号必须有唯一索引评价记录表存学号、课程名、各项评分、评语、提交时间其中学号和课程名要一起做联合唯一约束这是防重复提交的数据库层保险。还有一个容易被忽略的评价指标表把教学态度、教学内容、教学方法这类指标做成可配置的字典表比写死在代码里灵活得多答辩时也能多讲一个点。Django的ORM把表定义写成models.py里的一个类不需要手写SQL。定义好类之后执行迁移Django会生成CREATE TABLE语句并同步到数据库。这也是我推荐用Django的原因前期建模快后期改字段也只是加一行类属性再migrate一次。三层MTV架构正好切中评价系统的三个块Model管教师和评价记录的数据结构View管提交评价、算分、排名的业务流程Template管学生看到的填写页和排名页。如果用Flask同样的逻辑得自己拼ORM、自己搭表单校验工作量至少翻一倍。Django的好处是自带Admin后台教师数据可以直接在后台上录演示时打开后台给评委看观感比命令行操作好得多。2.2 最小可运行命令从虚拟环境到创建app我一般会先建虚拟环境再装Django。虚拟环境是为了避免本机Python环境里包版本互相打架这个习惯在毕设阶段养成以后工作也用得上# 创建并激活虚拟环境Windows python -m venv venv venv\Scripts\activate # 创建并激活虚拟环境macOS / Linux python -m venv venv source venv/bin/activate # 安装Django锁版本避免意外行为 pip install django4.2.7 # 创建项目 evaluation_system django-admin startproject evaluation_system cd evaluation_system # 创建评价业务app python manage.py startapp evaluationstartproject生成的是整个项目的配置壳包括settings.py、urls.py等startapp生成的才是业务模块。这里先解释一下为什么要拆成项目和应用两层一个Django项目可以由多个app组成用户管理一个app、评价统计一个app、数据导出一个appApp之间通过URL和模型关联代码不会挤成一团。后续所有评价逻辑都写在evaluation这个app里。创建完app之后必须去settings.py的INSTALLED_APPS列表里加一行evaluation否则模型表不会被创建。这是新手最容易漏的一步漏掉之后执行迁移会提示No changes detected一脸懵地查半天查不出来。2.3 数据模型实现models.py里的字段、约束与含义现在实现教师和评价两张表的模型代码。这个models.py是一个可运行的骨架字段名可以根据自己的业务调整但约束设计最好保留# evaluation/models.py from django.db import models from django.core.validators import MinValueValidator, MaxValueValidator class Teacher(models.Model): name models.CharField(max_length50, verbose_name姓名) employee_no models.CharField(max_length20, uniqueTrue, verbose_name工号) department models.CharField(max_length100, verbose_name所属院系) class Meta: db_table teacher # 显式指定数据表名 verbose_name 教师 def __str__(self): return f{self.name}{self.employee_no} class Evaluation(models.Model): teacher models.ForeignKey(Teacher, on_deletemodels.CASCADE, verbose_name被评教师) course models.CharField(max_length100, verbose_name课程名称) student_no models.CharField(max_length20, verbose_name学号) score_teaching models.IntegerField(validators[MinValueValidator(1), MaxValueValidator(10)], verbose_name教学态度分) score_content models.IntegerField(validators[MinValueValidator(1), MaxValueValidator(10)], verbose_name教学内容分) score_method models.IntegerField(validators[MinValueValidator(1), MaxValueValidator(10)], verbose_name教学方法分) comment models.TextField(blankTrue, verbose_name评语) created_at models.DateTimeField(auto_now_addTrue, verbose_name提交时间) class Meta: db_table evaluation unique_together (student_no, course) # 同一学生在同一门课只能评价一次 verbose_name 评价记录 def __str__(self): return f{self.course} - {self.teacher.name}这段模型代码里有三个设计点值得展开。第一个是employee_no的uniqueTrue它保证工号不重复管理教师数据时不至于出现两个人共用一个工号。第二个是teacher字段用ForeignKey外键指向Teacher表而不是直接存一个字符串教师名外键能联动删除、查询时能通过关联拿到教师的完整信息这在算排名时是必需的。第三个是unique_together它把学号和课程绑在一起从数据库层面杜绝重复评分——即使应用层忘记校验数据库也会抛IntegrityError拒绝插入。写完模型后执行两条命令让模型生效# 先生成迁移文件再写进数据库 python manage.py makemigrations evaluation python manage.py migratemakemigrations生成的是一个Python文件记录模型变化migrate才是真正往数据库写表。顺序不要颠倒每次修改models.py都要把这两条命令重新跑一遍。漏跑makemigrations直接migrate会出现操作不存在的报错这是初学Django时最容易遇到的日常翻车现场。3. 评价功能实现从页面表单到分数排名3.1 用ModelForm快速做一个能交差的评价填写页Django的表单功能可以直接从模型生成表单不需要手写HTML里的每个输入框。常见做法是把评价模型包成ModelForm# evaluation/forms.py from django import forms from .models import Evaluation class EvaluationForm(forms.ModelForm): class Meta: model Evaluation fields [teacher, course, score_teaching, score_content, score_method, comment] widgets { score_teaching: forms.NumberInput(attrs{min: 1, max: 10}), score_content: forms.NumberInput(attrs{min: 1, max: 10}), score_method: forms.NumberInput(attrs{min: 1, max: 10}), comment: forms.Textarea(attrs{rows: 3}), } labels { teacher: 被评教师, course: 课程名称, score_teaching: 教学态度得分1-10, score_content: 教学内容得分1-10, score_method: 教学方法得分1-10, }这个表单类做的事声明评分字段的取值范围是1到10评语是多行文本框每个字段在页面上显示什么中文标签。ModelForm的好处是字段定义沿用模型里的约束比如模型里validators写的1到10表单同样会校验比纯手写表单少了一半工作量。表单对应的模板submit.html可以简化成四行form methodpost {% csrf_token %} {{ form.as_p }} button typesubmit提交评价/button /formas_p是Django模板系统内置的渲染方法把每个表单字段包在段落标签里显示。正式项目会拆开自己排版但毕设演示用as_p完全够。csrf_token是Django的安全机制模板里漏掉它提交时会报CSRF验证失败这个报错在初学阶段经常遇到——不是代码逻辑问题是模板缺了一行标签。3.2 提交处理后端查重才是防重复评分的钥匙表单写好了后端view处理POST请求才是关键。这里最容易踩坑有人只在提交按钮上写个onclick禁用双击或快速刷新照样插入两条记录。正确做法是后端查库一次再做保存# evaluation/views.py from django.shortcuts import render, redirect from django.contrib import messages from .forms import EvaluationForm from .models import Evaluation def submit_evaluation(request): if request.method POST: form EvaluationForm(request.POST) if form.is_valid(): student_no form.cleaned_data[student_no] course form.cleaned_data[course] # 后端检查是否重复这是第一道保险 if Evaluation.objects.filter(student_nostudent_no, coursecourse).exists(): messages.error(request, 你已经评价过这门课了不能重复提交) return redirect(submit_evaluation) form.save() messages.success(request, 评价提交成功) return redirect(evaluation_list) else: form EvaluationForm() return render(request, evaluation/submit.html, {form: form})这段view有两个细节。第一个是student_no从cleaned_data里取——Django表单通过is_valid()校验后数据会清洗成一个干净的数据字典直接取request.POST拿到的可能是不安全的原始值。第二个是exists()查询比get()更合适因为get查不到目标时会抛DoesNotExist异常需要try包一层exists只返回布尔值判断逻辑更清爽。但你以为这样就万无一失了吗两个请求同时通过exists()检查还是可能双双插入。所以数据库层的unique_together会在第二条insert时抛IntegrityError如果要处理这个异常就把save包在事务里做第二道保险。答辩时能主动说出这层双保险——应用层查重加数据库约束这比只写一个exists的版本高一个档次。3.3 分数统计与排名aggregate和权重公式怎么一起用评价系统的最终输出是排名页一般叫教师教学质量排行榜。这个页面要按权重算综合得分常见的指标权重是教学态度30%、教学内容35%、教学方法35%。用Django的annotate和F表达式做# evaluation/views.py接上一个文件 from django.db.models import Avg, F, ExpressionWrapper, FloatField def teacher_ranking(request): teachers Teacher.objects.annotate( avg_teachingAvg(evaluation__score_teaching), avg_contentAvg(evaluation__score_content), avg_methodAvg(evaluation__score_method), ).annotate( total_scoreExpressionWrapper( F(avg_teaching) * 0.3 F(avg_content) * 0.35 F(avg_method) * 0.35, output_fieldFloatField(), ) ).order_by(-total_score) for i, teacher in enumerate(teachers, start1): teacher.rank i # 排名在Python侧生成便于模板直接用 return render(request, evaluation/ranking.html, {teachers: teachers})这里的evaluation__score_teaching是跨表查询的写法evaluation是Teacher模型里外键反向关联的名字双下划线表示穿透到Evaluation表再取它的score_teaching字段。第一个annotate算出三个维度的平均分第二个annotate用ExpressionWrapper做算术运算order_by按总分降序排列。为什么必须用ExpressionWrapper三个平均分相乘相加的结果是浮点数需要显式声明output_field否则ORM会把结果当整型处理小数点直接截断总分变成50而不是50.2——这是很隐蔽的翻车点。模板里遍历teachers展示排名table trth排名/thth教师/thth总分/th/tr {% for teacher in teachers %} trtd{{ teacher.rank }}/tdtd{{ teacher.name }}/tdtd{{ teacher.total_score|floatformat:2 }}/td/tr {% endfor %} /tablefloatformat:2过滤器把浮点数保留两位小数显示页面不会出现一大串小数。如果你在这个排名页上没看到数据先回到第一步确认表单里是否真的提交过评价记录空表算排名annotate出来的平均值是Nulltotal_score也全是Null。4. 数据库落地从SQLite切到MySQL与演示数据准备4.1 SQLite和MySQL怎么选一个不纠结的决策方法Django默认用SQLite——一个文件就是一个数据库零配置开发调试特别省心。毕设阶段用SQLite完全够但如果项目要求体现数据库设计或者想展示多用户同时提交的架构考虑我会建议切到MySQL。两者对比如下对比项SQLiteMySQL部署成本零配置文件即库需要安装服务配置账号权限并发能力写操作串行并发高易锁库支持并发写行级锁适合场景开发调试、演示数据量小生产环境、多人使用迁移成本Django默认值无需改动需安装驱动并改settings切换的代价其实很小。安装mysqlclient或pymysql驱动后修改settings.py的DATABASES配置# evaluation_system/settings.py DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: evaluation_db, USER: root, PASSWORD: your_password, HOST: 127.0.0.1, PORT: 3306, OPTIONS: { charset: utf8mb4, }, } }我有一个血泪经验MySQL的字符集。如果进MySQL命令行手动建库时不指定字符集默认latin1中文存进去全变问号。所以建库语句要显式声明utf8mb4CREATE DATABASE evaluation_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;4.2 用ORM做查询和删除对象五种日常操作评价系统开发中最常操作的是评价记录Django的ORM查询API撑起日常增删改查完全够用。结合标题热词里最常被搜的django执行查询-删除对象我把五种操作写在一起# 查询操作一览 from .models import Teacher, Evaluation # 1. 取全部数据 all_evaluations Evaluation.objects.all() # 2. 条件查询教学态度分大于等于8的记录 good_teaching Evaluation.objects.filter(score_teaching__gte8) # 3. 排序按提交时间倒序 recent Evaluation.objects.order_by(-created_at) # 4. 删除单个对象 e Evaluation.objects.get(id1) e.delete() # 5. 删除满足条件的多个对象 Evaluation.objects.filter(student_no20210001).delete()get和filter的删除机制不同。get是取一个对象调用它的delete()方法filter取出的是一组对象也支持delete()返回的是删除总数, {表名: 数字}这样的元组。日常用filter().delete()更高效因为它在SQL层直接执行DELETE。执行删除前强烈建议先跑一下相同的查询语句确认影响范围这个习惯救过我很多次不然随手一个delete()下去发现删多了Django也没有后悔药可吃。4.3 演示数据的两种造法dumpdata和自定义命令毕业设计演示需要看起来像真的的评分数据。Django提供了两条路线第一条是dumpdata配合loaddata# 把当前数据库里的评价数据导出成JSON文件 python manage.py dumpdata evaluation --indent 2 evaluation_data.json # 换到另一台机器后导入 python manage.py loaddata evaluation_data.json这条命令适合在不同环境间搬运数据。但有一种场景它解决不了我需要在空数据库里自动造10个教师和几十条评价记录。写一个自定义管理命令是常见做法# evaluation/management/commands/seed_data.py # 前提创建 evaluation/management/commands/ 目录并补齐 __init__.py from django.core.management.base import BaseCommand from evaluation.models import Teacher, Evaluation import random class Command(BaseCommand): help 生成演示用教师与评价数据 def handle(self, *args, **options): teachers [] for i in range(10): teacher, created Teacher.objects.get_or_create( employee_nofT{i:03d}, defaults{name: f张老师{i}, department: 计算机学院} ) teachers.append(teacher) # 给每位老师造5条评价 for teacher in teachers: for j in range(5): Evaluation.objects.create( teacherteacher, coursef数据结构{teacher.employee_no}, student_nof2021{j:03d}, score_teachingrandom.randint(7, 10), score_contentrandom.randint(6, 10), score_methodrandom.randint(7, 10), comment演示评语 ) self.stdout.write(self.style.SUCCESS(演示数据生成完成))写这个命令要注意目录结构。Django规定管理命令放在app/management/commands/文件夹下management和commands两个目录都要有__init__.py文件否则命令找不到。get_or_create的第二个位置参数defaults是创建时补充的字段如果记录已存在则直接返回不创建——所以这个命令可以重复执行不报错刷新演示数据很方便。执行命令python manage.py seed_data跑完之后想验证数据量可以用Django shell执行一条查询python manage.py shell -c from evaluation.models import Evaluation; print(Evaluation.objects.count())如果输出50说明数据造好了可以继续做后面的排名和导出测试。5. 避坑指南从迁移乱象到并发重复的五类典型问题5.1 makemigrations提示No changes detected现象明明改了models.py执行python manage.py makemigrations时却提示No changes detected。 原因最常见的是app名没注册进INSTALLED_APPSDjango根本感知不到这个模型变化。其次是修改的模型类和上次迁移文件相比确实没有变化——比如只改了注释没改字段。 解决去settings.py检查INSTALLED_APPS里有没有evaluation如果注册无误删除该app下migrations目录里最近一次生成的迁移文件再重新makemigrations。注意这个删迁移文件的操作只适合开发阶段项目一旦上了生产环境不要轻易动迁移文件。5.2 迁移成功但打开数据库找不到表现象migrate命令执行成功用Navicat或DB Browser打开数据库文件却看不到teacher和evaluation两张表。 原因数据库连接指向的不是你打开的那个数据库。切到MySQL时尤其常见——settings里NAME写成evaluation_db但MySQL里实际建的库名是evaluation_test连接当然指向一个不存在的库。SQLite则容易散落多个evaluation.db文件系统里每个目录都可能藏一个。 解决先在数据库客户端执行SELECT DATABASE();确认当前在用哪个库再用反向核对settings的NAME和HOST。SQLite的话把数据库文件的绝对路径打出来确认自己打开的是否是同一个文件。这一类问题属于典型的环境黑匣子查的时候不要只盯着models.py。5.3 中文全部变成问号现象页面显示中文正常写入MySQL后变成????导出Excel也全是问号。 原因数据库表字符集是latin1或连接串没指定charset。MySQL默认字符集在有些版本里不是utf8mb4这是最常踩的坑。 解决建库时用utf8mb4settings里OPTIONS配置charset。SQLite一般不出现这个问题重点排查MySQL。如果数据已经写入修改字符集后再重新导入数据。血泪经验是建库语句在项目文档里写清楚换个人接手不至于再踩一遍。5.4 重复提交导致数据翻倍现象学生快速点击两次提交按钮库里出现两条完全相同的评价记录。 原因只做了前端禁用没有后端查重或者后端只写了exists()检查但没有处理并发。 解决三层保险。第一层前端点击后把按钮设为disabled第二层后端view用exists()查询第三层数据库unique_together约束。这三层都上了基本堵死重复提交。答辩时建议主动讲这个场景能体现考虑问题的全面性。5.5 DEBUGFalse之后静态文件全丢现象开发环境一直正常为了演示把settings里的DEBUG改成False页面样式和图片全部消失。 原因Django在开发模式下会自动处理静态文件但生产模式出于性能和安全考虑不会这么做。这不是代码写错了是模式改变的已知行为。 解决在settings里配置STATIC_ROOT比如STATIC_ROOT BASE_DIR / staticfiles然后在项目根目录运行python manage.py collectstatic把散落在各app里的静态资源收集到一个目录。之后用runserver再次验证或者配合Nginx静态目录指向staticfiles。验证时多留意控制台里404的资源路径一般是静态文件路径前缀没配对。6. 从毕设到演示部署验证与三个加分小功能整套逻辑跑通后最后一步是把项目演示给评委看。演示前务必按顺序做一次全流程验证makemigrations、migrate、seed_data然后启动开发服务器python manage.py runserver 0.0.0.0:80000.0.0.0让服务监听本机所有网卡同一局域网内的任何设备都可以用http://你的IP:8000访问。答辩现场让评委用手机直接打开这个地址比所有人围在一块笔记本屏幕前体验好得多。找IP的方法很简单Windows下用ipconfigmacOS和Linux下用ifconfig或ip addr找无线网卡的IPv4地址。顺带提一句如果演示时发现页面F12报错优先看控制台里的静态文件地址十次里有八次是路径少了前缀。值得加的三个功能每个工作量都不大。第一个把三个评分指标的权重30/35/35放到一个独立模型里管理员可在后台修改权重业务代码从表读数而不是写死常量。第二个导出Excel报表——不引入重依赖用Python标准库csv模块加BOM标识能导出中文不乱码毕设阶段能说清楚原理就够了。第三个登录鉴权——Django自带User模型加一个login_required装饰器让只有学生能提交评价、只有管理员能看排名成为标配功能。这三个扩展合计不超过150行代码但能让课题从演示品变成一个小型可用系统。我做这类管理系统有一个固定习惯每次改完模型立刻跑makemigrations和migrate每次删除数据前先做等值查询确认范围每次切换数据库都重跑一遍迁移和seed脚本。这些动作听着基础帮我躲过了无数次翻车。希望这套笔记也能让你少踩几个坑。评价系统的灵魂不在页面多好看在数据模型稳、查得准、算得对。希望帮到你。本文还有配套的精品资源点击获取