ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Python+Vue教学质量评价系统开发实战:Django与Flask技术选型全解析

Python+Vue教学质量评价系统开发实战:Django与Flask技术选型全解析 教学质量评价系统这个名字放在校园项目里几乎每年都会出现但真正把它做成一套能跑起来、能录入数据、能出统计结果的完整系统并不是写几个页面那么简单。我手头这个项目就是用Python做后端、Vue做前端在PyCharm里从空目录一步步搭起来的。整个过程用到了Django也对比试过Flask最终选择了Django作为主框架。如果你正准备做类似的管理系统或者想搞清楚PythonVue这条技术路线到底怎么落地这篇博文应该能帮你少走不少弯路。我会把需求拆解、技术选型、环境搭建、后端接口、前端页面、联调部署这几个环节全部过一遍中间穿插实际踩过的坑和最终采用的写法。1. 从需求出发教学质量评价系统要管哪些人和哪几件事动代码之前我习惯先把业务盘清楚。教学质量评价表面上看就是“学生给老师打分”但真拆开来看至少涉及三类角色、四个核心流程任何一个环节理解偏了后端模型和前端页面都会返工。1.1 三类用户三种权限边界第一个角色是学生他关心的是“我能不能方便地给这学期教我的老师打分、提交文字建议”而且要让学生相信评价是匿名的否则很多人不敢说真话。第二个角色是教师他看到的是自己的最终评分、排名区间、以及匿名文字反馈但看不到“具体是哪个学生打了分”。第三个角色是管理员负责配置评价任务、管理课程与教师关系、查看全院的统计报表。这三类角色看起来很常规但权限边界一定要在需求阶段固定下来。我见过不少项目做到一半教师突然问“为什么我不能看到每个学生的打分明细”这就是一开始没把匿名原则约束清楚。系统设计时教师端所有接口只能返回聚合数据不能返回明细记录哪怕管理员也只能按课程维度去查看原始评价而不是按学生姓名去筛选。1.2 评价数据流从指标配置到统计报表整套系统跑起来之后数据流向大致是这样的管理员创建评价任务设置评价指标比如教学态度、内容讲解、课堂互动、作业批改每个指标对应一个分值区间。管理员将评价任务关联到具体的课程和任课教师。学生登录后看到“待评价课程列表”对每个课程逐项打分并填写文字意见。系统将每条评价记录落库打上课程、教师、指标类型等维度。教师登录后读取自己的聚合结果管理员按学期、学院、课程做多维统计。这个流程直接决定了数据库模型怎么设计。评价任务和课程是多对多关系指标项和评价记录是一对多关系教师和课程又是一对多关系。如果一开始就把这些关系理顺后面写ORM模型会非常轻松反过来如果一上来就建一张宽表把所有字段都塞进去统计维度会瞬间变复杂。1.3 先用原型对齐字段再写后端模型我在这类项目上的一个经验是不要急着建表先用前端页面原型把字段全部列出来。比如评价页面到底需要哪些字段课程名称、教师姓名、指标评分1到5分、评语输入框、提交按钮这些字段确认以后后端模型几乎可以直接对应着写。我吃过一次亏最初做模型时我把指标和评价记录合并成了一张表每一条记录就是一行带十几个字段的冗余数据。等做到统计页面时发现“按指标维度做平均”要写一堆条件聚合代码又长又难维护。后来改成“评价任务表 指标表 评价记录表 记录明细表”的结构统计查询才变得干净。所以说需求梳理阶段花一天画原型、列字段比后期重构省三天不止。2. 技术栈磨合Django与Vue如何分工Flask为什么被“降级”为备选标题里既写了Django又写了Flask确实很多人会在两个框架之间纠结。我两个框架都写过这个项目也分别做了技术验证最终选了Django。这不是说Flask不好而是这个项目的特性更适合Django。2.1 Django与Flask的取舍对比先看一张我从实战角度做的对比维度DjangoFlaskORM自带强大ORM迁移、查询、关联都顺手默认无ORM通常要接SQLAlchemyAdmin后台自带Admin配置一下就出现管理界面需要额外集成Flask-Admin用户认证自带User模型、session、权限体系需要自己搭或用扩展插件项目成熟度重量级适合业务复杂的系统轻量级适合单一接口或微服务学习曲线概念多前期理解成本高上手快但后期要自己组装对这个教学质量评价系统来说最要命的是用户认证和权限。Django内置的User模型和Permission机制能直接支撑学生、教师、管理员三种角色而用Flask的话光用户角色控制就要花不少时间自己去拼。另一个点是ORM评价相关的数据模型关系复杂Django的ORM在跨表查询、annotate聚合统计时非常顺滑Flask接SQLAlchemy虽然也能写但多一层依赖出错排查时又多一个变量。2.2 前后端分离的架构价值项目选择PythonVue这套组合本质上是把系统拆成两个独立工程后端Django只提供JSON接口前端Vue负责渲染页面和交互。这样做的好处有三个。第一前后端可以并行开发。我这边写Django接口的同时另一头Vue页面可以先写假数据最后联调时再替换成真实请求整体开发周期明显缩短。第二后续维护边界清晰。前端改样式、后端该逻辑互不干扰如果以后要出小程序端直接复用同一套后端API。第三部署灵活。Django跑在服务器上Vue构建后的静态文件交给Nginx托管前后端分离部署抗压能力也更好。2.3 数据库和IDE的配套选择数据库方面开发环境我用SQLite就够了但最终交付我切到了MySQL。原因很简单SQLite在并发写入和权限控制上行官方推荐生产环境使用MySQL或PostgreSQL。如果你的项目是直接用SQLite一路写完建议预留出数据库切换的配置入口Django的settings里把数据库连接配置独立出来换库时只改一处。IDE选PyCharm倒不是因为它有多神而是在Python项目的虚拟环境管理、Django命令行集成、调试模式上做得足够顺手。尤其是PyCharm Professional版自带的Django支持可以直接建Django项目、启动runserver、debug断点对刚入门的人来说省去了很多命令行的繁琐。3. PyCharm环境搭建从Python解释器到Vue脚手架环境搭建是整个项目最容易卡住的地方尤其是很多同学第一次在PyCharm里同时跑两套项目经常一头雾水。我把整个流程拆成了三个步骤先配Python后端再建Vue前端最后统一调版本兼容。3.1 在PyCharm中创建虚拟环境并安装依赖打开PyCharm新建项目时我建议直接选择虚拟环境Python版本用3.10或3.11都行。项目创建好后打开Terminal执行虚拟环境激活和依赖安装python -m venv venv # Windows下激活虚拟环境 venv\Scripts\activate # Mac/Linux下激活虚拟环境 source venv/bin/activate pip install django djangorestframework django-cors-headers PyJWT这几个包的作用分别是Django框架本身、Django REST Framework简称DRF用来快速写REST API、django-cors-headers解决前后端分离时的跨域问题、PyJWT用来做JWT登录鉴权。安装完成后创建Django项目和应用django-admin startproject qa_project cd qa_project python manage.py startapp evaluation这里有个小建议不要把业务代码全塞在一个“主应用”里我习惯把评价相关逻辑单独放一个app叫evaluation用户认证逻辑放users这样后期代码组织更清晰。3.2 初始化Vue项目并调整目录结构前端部分我使用的是Vue 3 Vite。命令行执行npm create vuelatest脚手架会让你选择是否安装TypeScript、Pinia、Vue Router等依赖根据自己的需求勾选即可。项目生成后进入项目目录安装依赖并启动开发服务npm install npm run devVite默认的端口是5173而后端Django默认跑在8000端口所以前后端联调时必须解决跨域问题这一步我放在第六部分细说。Vue项目目录我一般会做两个调整把src/router里放路由配置用于定义登录页、学生端、教师端、管理端的页面路径在src/api下新建request.js统一封装axios请求把后端API地址和请求拦截器集中管理。3.3 版本兼容这个隐形坑版本兼容是新手最容易忽略的问题。Django 5.0发布后很多人直接pip install django然后发现项目跑起来一堆弃用警告甚至某些第三方库还没适配。我实测下来Django 4.2 DRF 3.14是一个很稳的组合Python版本用3.10或3.11都不要紧。前端方面Node.js版本最好用18以上Vite 5要求Node 18否则装依赖时可能会出现ES模块解析报错。如果不确定本机Node版本可以先执行node -v检查低于18就先升级Node环境再创建Vue项目。还有一个坑是npm install报错多半是由于网络环境导致的可以临时切镜像源再重试。4. 后端核心实现模型关系设计、JWT登录与评价接口的完整链路后端是这套系统的大脑把所有业务规则落实到数据模型和接口逻辑上。我按模型、接口、鉴权三个维度来说。4.1 数据模型四张核心表把业务关系串起来先放最终采用的模型结构from django.contrib.auth.models import AbstractUser from django.db import models class User(AbstractUser): ROLE_CHOICES ( (student, 学生), (teacher, 教师), (admin, 管理员), ) role models.CharField(max_length10, choicesROLE_CHOICES, defaultstudent) real_name models.CharField(max_length50, verbose_name姓名) teacher models.ForeignKey(self, nullTrue, blankTrue, on_deletemodels.SET_NULL, related_namestudents) class Course(models.Model): name models.CharField(max_length100) code models.CharField(max_length20, uniqueTrue) teacher models.ForeignKey(User, on_deletemodels.CASCADE, related_namecourses) class EvaluationTask(models.Model): title models.CharField(max_length100) start_time models.DateTimeField() end_time models.DateTimeField() courses models.ManyToManyField(Course, related_nameevaluation_tasks) class Indicator(models.Model): task models.ForeignKey(EvaluationTask, on_deletemodels.CASCADE, related_nameindicators) name models.CharField(max_length100) full_score models.IntegerField(default5) class EvaluationRecord(models.Model): task models.ForeignKey(EvaluationTask, on_deletemodels.CASCADE, related_namerecords) course models.ForeignKey(Course, on_deletemodels.CASCADE) student models.ForeignKey(User, on_deletemodels.CASCADE) comment models.TextField(blankTrue) created_time models.DateTimeField(auto_now_addTrue) class Score(models.Model): record models.ForeignKey(EvaluationRecord, on_deletemodels.CASCADE, related_namescores) indicator models.ForeignKey(Indicator, on_deletemodels.CASCADE) value models.IntegerField()这套模型的核心思路是评价任务EvaluationTask挂多个课程每个课程由某个教师教学学生提交一条评价记录EvaluationRecord记录下面有多条指标得分Score和一段文字评价。这样拆开以后“查某位教师某门课的平均分”就是一次简单的跨表聚合。4.2 用DRF实现接口从queryset到viewset的简化写法DRF最大的价值在于常规的增删改查逻辑几乎不用手写。以评价任务接口为例from rest_framework import serializers, viewsets class IndicatorSerializer(serializers.ModelSerializer): class Meta: model Indicator fields [id, name, full_score] class EvaluationTaskSerializer(serializers.ModelSerializer): indicators IndicatorSerializer(manyTrue, read_onlyTrue) class Meta: model EvaluationTask fields [id, title, start_time, end_time, indicators] class EvaluationTaskViewSet(viewsets.ModelViewSet): queryset EvaluationTask.objects.all().prefetch_related(indicators) serializer_class EvaluationTaskSerializerModelViewSet自动帮我把列表、详情、创建、更新、删除的路由都生成了。省略了手写一堆重复的if request.method POST之类的判断。URL配置也很简单from rest_framework.routers import DefaultRouter router DefaultRouter() router.register(tasks, EvaluationTaskViewSet) urlpatterns router.urls4.3 JWT登录鉴权用户体系这块我没有用Session而是选了JWT。因为前后端分离部署之后前端在浏览器里访问Django接口如果还用SessionCSRF校验和Cookie传递会比较麻烦。JWT的方式是登录成功后后端返回一个Token前端把Token存起来后续请求带上Authorization头即可。登录接口的核心逻辑很简单import jwt from django.contrib.auth import authenticate from rest_framework.views import APIView from rest_framework.response import Response SECRET_KEY your-secret-key class LoginView(APIView): def post(self, request): username request.data.get(username) password request.data.get(password) user authenticate(usernameusername, passwordpassword) if not user: return Response({error: 用户名或密码错误}, status400) token jwt.encode( {user_id: user.id, role: user.role}, SECRET_KEY, algorithmHS256 ) return Response({token: token, role: user.role, name: user.real_name})前端拿到Token后每次请求都在拦截器里自动带上。后端再写一个解析Token的装饰器就能实现接口权限控制。这里要特别提醒JWT密钥一定要放在Django的settings里并且生产环境不要硬编码在代码里最好用环境变量读取。5. 前端页面落地从评价表单到统计图表的Vue组件写法前端Vue部分我最想分享的是路由设计、评价表单的交互写法以及权限控制三个点。5.1 路由守卫按角色控制页面访问Vue Router可以通过路由守卫拦截未登录或无权访问的页面。我的做法是在路由规则里给每个路由加一个meta字段{ path: /student/evaluate, component: StudentEvaluate, meta: { requiresAuth: true, role: [student, admin] } }然后全局注册一个路由守卫router.beforeEach((to, from, next) { const token localStorage.getItem(token) const role localStorage.getItem(role) if (to.meta.requiresAuth !token) { next(/login) } else if (to.meta.role !to.meta.role.includes(role)) { next(/403) } else { next() } })这样教师登录后想去学生评价页面会被直接重定向到403页从入口上杜绝越权操作。5.2 评价页面核心交互动态指标列表与提交校验评价页面是学生端最核心的界面。它的特点是每门课的指标数量不一定相同所以指标列表必须由后端返回前端动态渲染。用Vue 3的组合式API写起来很干净script setup import { ref, onMounted } from vue import { useRoute } from vue-router import api from /api/request const route useRoute() const taskId route.params.taskId const indicators ref([]) const scores ref({}) const comment ref() onMounted(async () { const res await api.get(/api/tasks/${taskId}/indicators/) indicators.value res.data }) async function submit() { const missing indicators.value.some(item !scores.value[item.id]) if (missing) { alert(请完成所有指标评分) return } await api.post(/api/tasks/${taskId}/evaluate/, { scores: scores.value, comment: comment.value }) } /script template div v-foritem in indicators :keyitem.id {{ item.name }} select v-model.numberscores[item.id] option v-forn in item.full_score :valuen{{ n }}/option /select /div textarea v-modelcomment/textarea button clicksubmit提交评价/button /template这段逻辑看起来简单但包含一个很重要的设计提交前必须校验是否全部评分否则漏掉某项指标会导致统计结果失真。mock阶段我曾经跳过校验结果教师端查总评时发现少了一门课的平均分排查半天才发现是有学生提交时漏选了评分项。5.3 教师端统计图表按课程和指标维度聚合教师端统计功能我选择引入ECharts来展示。后端需要提供一个聚合接口比如返回某教师各门课程在不同指标上的平均分from django.db.models import Avg from rest_framework.views import APIView class TeacherCourseScoreView(APIView): def get(self, request): teacher request.user courses Course.objects.filter(teacherteacher) result [] for course in courses: row {course: course.name} records EvaluationRecord.objects.filter(coursecourse) indicators Indicator.objects.filter(task__coursescourse) for ind in indicators: avg Score.objects.filter( record__coursecourse, indicatorind ).aggregate(avg_valueAvg(value))[avg_value] row[ind.name] round(avg or 0, 2) result.append(row) return Response(result)前端拿到数据后用ECharts的折线图或柱状图渲染即可。一个经验之谈是饼图和柱状图尽量按“课程”分组而不是把多个指标塞进一堆看似酷炫的仪表盘里因为教学管理者最关心的永远是不同课程之间的横向对比。6. 联调、部署与坑后反思CORS、CSRF与静态文件系统开发完成后联调阶段往往比写代码更折磨人。我在这部分总结三个最经典的问题和解决办法。6.1 CORS跨域前端访问不了后端接口怎么办前后端分离开发时Vite跑在5173端口Django跑在8000端口浏览器会认为这两个端口属于不同源默认不允许前端发起跨域请求。解决方式是安装django-cors-headersINSTALLED_APPS [ ... corsheaders, ] MIDDLEWARE [ corsheaders.middleware.CorsMiddleware, ... ] CORS_ALLOWED_ORIGINS [ http://localhost:5173, ]配置好之后前端就能正常请求后端了。这里要注意生产环境千万不要用CORS_ALLOW_ALL_ORIGINS True否则任何网站都能向你的接口发起请求安全隐患非常大。6.2 CSRF校验POST请求被403拦截这个问题只在没有启用JWT、使用Session时会遇到。如果坚持用Django自带的登录系统所有POST请求都需要带上CSRF Token。我建议既然选了前后端分离就统一用JWT方案登录接口用APIView的authentication_classes指定为空其他接口统一校验JWT这样就不会和CSRF机制纠缠不清。如果确实要用Django的登录页面那前端要在请求头中加入X-CSRFToken字段并且从Cookie中读取csrftoken。网上关于这个报错的解决方案很多但大多是拆东墙补西墙最干净的方案还是换JWT。6.3 部署Nginx uWSGI Vue静态文件部署方案我最终选了Nginx uWSGI的组合。流程是1.后端代码打包放到服务器创建虚拟环境安装依赖。 2.执行Django的collectstatic把静态文件收集到指定目录。 3.用uWSGI启动Django服务。 4.前端执行npm run build生成dist目录。 5.Nginx配置里把/api请求转发到uWSGI把其他路径指向前端dist目录。一个参考版的Nginx配置server { listen 80; server_name your-domain.com; location /api/ { include uwsgi_params; uwsgi_pass 127.0.0.1:8000; } location / { root /var/www/qa_frontend/dist; try_files $uri $uri/ /index.html; } }部署中最容易漏掉的一步是Vue是单页应用前端路由必须由Nginx做try_files回退到index.html否则刷新页面会直接404。我在测试阶段就栽过一次学生端提交完评价后刷新页面直接白屏排查了很久才发现是Nginx配置里少了try_files这一行。6.4 最后一个值得强调的教训整个项目做完我最大的体会是教学质量评价系统的价值不在“会打分”这个功能点上而在“统计结果可信”这件事上。为了这个目标前端必须有完整的评分校验后端必须在保存评价记录时做事务处理防止一半成功一半失败数据库层面对同一学生同一门课只允许提交一次这些约束都比页面样式重要得多。如果你正准备照着这个方向做毕业设计或小项目我建议你先把第1部分的角色和数据流想透再动手写代码。一个清晰的模型设计能让你后半程少花一半精力去改表、改接口。最后分享一个小技巧开发过程中所有耗时超过100ms的接口可以在Django里加一个简单的耗时日志中间件随时监控接口性能。评价统计接口如果后期数据量上来了记得给EvaluationRecord的task和course字段加上联合索引聚合查询的提升会非常明显。
返回列表