ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Flask与Django实现学生作业课程考核系统开发实战

Flask与Django实现学生作业课程考核系统开发实战 最近在做一个“Python基于flask-django形成性学生作业课程考核管理系统”简单说就是把一门课从布置作业、学生提交、教师评分到平时成绩汇总的整个链条搬到线上。这个项目的背景很实在老师不想再手动统计平时分了期末算错成绩要返工学生也想要及时的作业反馈。文章会根据我的实际开发过程把两条 Python Web 技术路线——Flask 和 Django——在同类系统里的选型思路、模型设计、部署细节和各类坑都捋一遍。如果你正在做课程考核系统、教学管理平台或者想找个练手的轻量级业务系统项目这篇内容应该能直接帮你避掉不少麻烦。我最初接到这个需求时甲方其实就是我一位朋友给的需求很朴素能发作业、能交作业、能给分、能自动算平时分。但一旦细化下去牵扯出来的东西并不少——作业要不要区分小组附件怎么上传逾期提交怎么处理教师评分后要不要允许学生申诉平时分占比每个老师还不一样。这些需求堆在一起技术选型就成了第一个绕不过去的问题。1. 需求拆解形成性考核到底要管什么1.1 “形成性考核”不是给自己贴金形成性考核Formative Assessment这个概念可能对非教育行业的开发者有点陌生但换成“过程性评价”就很好理解了。它的核心逻辑是在学生整个学习过程中进行多次、分散的评价而不是期末一次考试定成绩。比如一门课有5次作业、2次课堂测验、1次实验报告每次活动都有对应的得分和反馈最后按权重合成平时成绩。这种考核方式对系统提出的要求跟普通的作业管理完全不同。最典型的一点系统的核心对象不是“作业”而是“形成性考核项Assessment Item”。每个考核项有自己的类型作业/测验/实验、权重、评分标准和截止时间学生的成绩是多个考核项按权重加权计算出来的。这从根本上决定了数据库模型的长相也决定了后面所有功能逻辑的走向。1.2 用户角色与核心业务流程系统的用户角色很清晰就三类管理员、教师、学生。但这三类角色对系统的诉求完全不一样梳理清楚业务流程才能开始建模。教师创建课程、发布考核项、设定评分标准Rubric、查看学生提交、批量评分、写反馈、查看班级成绩分布。学生查看课程下的考核项列表、在规定时间内提交作业附件或填写文本、查看自己的成绩与教师反馈、查看个人成绩趋势。管理员维护用户账号、分配角色权限、管理课程信息、处理异常数据比如误删提交。整个主流程走下来就是管理员创建课程并导入学生名单 → 教师发布考核项并设置截止时间 → 学生在截止时间前提交 → 教师评分并反馈 → 系统按权重自动汇总成绩 → 教师导出或查看可视化统计。这个过程里最繁琐的是“评分”环节一门课五六十个学生老师一个个在网页表单里打分很痛苦所以系统一定要支持批量评分操作最好还能导入Excel。1.3 功能清单与优先级模块名称主要功能点优先级说明用户与权限注册登录、角色区分、管理员重置密码P0没有登录体系就谈不上区分师生课程管理课程创建、选课/导入名单、教师分配P0业务数据依赖课程维度考核项管理作业发布、截止时间、评分标准、类型P0系统核心数据源提交管理学生提交附件、逾期标记、重新提交P0允许覆盖提交但要有记录评分反馈分数评语标签、批量评分、Excel导入P0形成性反馈的核心功能成绩汇总加权计算、班级统计、个人趋势P1减去老师最大的计算负担通知推送新作业提醒、成绩发布提醒P1WebSocket 或站内消息这个优先级不是拍脑袋定的。P0 的东西缺了系统就没法用P1 是效率提升。实际开发时我按这个顺序推进先保证流程能闭环再谈体验优化。2. 技术选型Flask和Django的取舍2.1 Flask路线轻量、灵活、上手快第一次给朋友演示原型时我用的是 Flask原因很直接需求还不完全确定需要快速迭代Flask 足够轻改起来毫无心理负担。Flask 的全家桶方案我推荐这么配Flask Flask-SQLAlchemy Flask-Login Flask-WTF Flask-SocketIO。Flask-SQLAlchemy 提供 ORM 能力Flask-Login 管登录会话Flask-WTF 处理表单和 CSRF 防护Flask-SocketIO 做实时推送。有同学问“Flask 如何绑定到网页元素”这里顺带讲清楚Flask 的路由函数返回render_template(xxx.html, 变量值)在 Jinja2 模板里用{{ 变量 }}渲染内容用{% for %}循环渲染列表。比如app.route(/course/int:course_id/assignments) def assignment_list(course_id): assignments Assignment.query.filter_by(course_idcourse_id).all() return render_template(assignments.html, assignmentsassignments)模板里就是ul {% for a in assignments %} li{{ a.title }} —— 截止时间{{ a.deadline }}/li {% endfor %} /ulFlask 的灵活在这里体现得淋漓尽致你想怎么组织代码都行没有强制目录结构但要自己自觉。小项目没问题项目一大如果不刻意分层代码就会变成一锅粥。2.2 Django路线全家桶带来的省心当我决定把项目做成完整交付版本时我换到了 Django。原因很简单Django 自带的Admin后台管理实在太适合课程考核这种管理密集型系统了。Django 的路线是django-admin startprojectpython manage.py startapp assessment创建完 app 后去settings.py的INSTALLED_APPS里注册。它的优势非常明显内置 Auth 和权限系统RBACUser、Group、Permission 三件套可以直接用教师组、学生组一分配就完事不用自己手写角色装饰器。自带 Admin 后台数据管理界面是白送的老师甚至可以直接在 Django Admin 里录入成绩搭配 django-unfold 这类主题包还能换个好看的皮肤。ORM 和迁移机制成熟python manage.py makemigrations python manage.py migrate管理表结构迭代起来很舒服。Form 系统自带校验文件上传、字段校验、CSRF 防护都有现成方案。Django 给人的感觉是“框架替你做了决定”。它自带的查询接口也简洁热词里那个“django执行查询-删除对象”实际用起来大概是# 删除单个对象 s Submission.objects.get(id1) s.delete() # 条件删除 Submission.objects.filter(assignment__title__icontains实验).delete() # 查询后统一删除 grades Grade.objects.filter(score__lt60) grades.delete()Django 的 ORM 把 SQL 封装得很彻底几乎所有业务查询都能用 Python 写出来不用整天惦记原生 SQL。2.3 “flask-django”不是对立而是阶段很多人看到“flask-django”会以为是要二选一。但在我这种搞法里这俩是同一个项目在不同阶段的两种实现代表了两代版本原型验证阶段用 Flask快速拼出业务流程给客户确认需求是不是对的。正式交付阶段用 Django把稳定功能迁移到 Django利用自带权限管理和 Admin 后台提升开发效率。这种做法的好处在于需求风险被限制在原型阶段正式开发时所有坑都已经踩过一遍。如果你是自己学习我也建议把这两个都走一遍——你会发现很多 Web 开发的底层逻辑是相通的只是表达方式不同。2.4 选型决策参考维度FlaskDjango学习曲线平缓容易上手陡峭一些概念多开发速度原型快灵活较快但前期准备长内置功能少需自行集成Auth、Admin、ORM全自带适合场景小系统、API服务、原型企业级、权限复杂、后台管理重部署难度简单稍复杂静态文件、ASGI等这个表格不是绝对的。真要选型核心得看你的需求里“权限管理复杂度和后台管理需求量”有多大。如果是课程考核系统这种天然需要管理后台的Django 往往更省心。3. 数据库设计与核心模型3.1 实体关系设计思路模型设计是整个系统的地基我的建议是先画实体关系再写代码。核心实体有六个用户User、课程Course、选课记录Enrollment、考核项Assignment、提交记录Submission、成绩Grade。它们的关系是一个课程下有多个考核项一对多一个学生可以选择多门课程一个课程有多个学生多对多用 Enrollment 记录一个考核项对应多条提交记录一对多一条提交记录对应一条成绩记录一对一提交记录和考核项之间还要保证唯一性同一个学生不能对同一个考核项有两条提交。数据库层面用联合唯一索引来兜底防止代码层面并发导致脏数据。3.2 Flask版本SQLAlchemy建模用 Flask-SQLAlchemy 写这种模型非常顺手直接看代码from flask_sqlalchemy import SQLAlchemy from datetime import datetime db SQLAlchemy() class User(db.Model): id db.Column(db.Integer, primary_keyTrue) username db.Column(db.String(80), uniqueTrue, nullableFalse) role db.Column(db.String(20), nullableFalse) # teacher / student / admin class Course(db.Model): id db.Column(db.Integer, primary_keyTrue) name db.Column(db.String(200), nullableFalse) teacher_id db.Column(db.Integer, db.ForeignKey(user.id)) class Enrollment(db.Model): id db.Column(db.Integer, primary_keyTrue) student_id db.Column(db.Integer, db.ForeignKey(user.id)) course_id db.Column(db.Integer, db.ForeignKey(course.id)) db.UniqueConstraint(student_id, course_id, nameuq_enrollment) class Assignment(db.Model): id db.Column(db.Integer, primary_keyTrue) course_id db.Column(db.Integer, db.ForeignKey(course.id)) title db.Column(db.String(200), nullableFalse) deadline db.Column(db.DateTime, nullableFalse) weight db.Column(db.Float, default1.0) rubric db.Column(db.JSON) # 评分标准例如 [{item: 代码规范, score: 20}] class Submission(db.Model): id db.Column(db.Integer, primary_keyTrue) student_id db.Column(db.Integer, db.ForeignKey(user.id)) assignment_id db.Column(db.Integer, db.ForeignKey(assignment.id)) file_path db.Column(db.String(255)) submitted_at db.Column(db.DateTime, defaultdatetime.utcnow) is_late db.Column(db.Boolean, defaultFalse) db.UniqueConstraint(student_id, assignment_id, nameuq_submission) class Grade(db.Model): id db.Column(db.Integer, primary_keyTrue) submission_id db.Column(db.Integer, db.ForeignKey(submission.id), uniqueTrue) score db.Column(db.Float, nullableFalse) feedback db.Column(db.Text) tag db.Column(db.String(50)) # excellent / good / improve这里rubric用 JSON 字段是我比较推荐的玩法不同老师对评分标准的定义差异很大用关系表维护一套通用标准反而复杂JSON 字段直接按老师个人习惯存灵活且不破坏结构化查询需求。3.3 Django版本ORM模型对照Django 的模型代码长这样from django.db import models from django.contrib.auth.models import User class Course(models.Model): name models.CharField(max_length200) teacher models.ForeignKey(User, on_deletemodels.CASCADE, related_nametaught_courses) students models.ManyToManyField(User, throughEnrollment, related_nameenrolled_courses) class Enrollment(models.Model): student models.ForeignKey(User, on_deletemodels.CASCADE) course models.ForeignKey(Course, on_deletemodels.CASCADE) enrolled_at models.DateTimeField(auto_now_addTrue) class Meta: unique_together (student, course) class Assignment(models.Model): course models.ForeignKey(Course, on_deletemodels.CASCADE, related_nameassignments) title models.CharField(max_length200) deadline models.DateTimeField() weight models.FloatField(default1.0) rubric models.JSONField(defaultdict) class Submission(models.Model): student models.ForeignKey(User, on_deletemodels.CASCADE) assignment models.ForeignKey(Assignment, on_deletemodels.CASCADE, related_namesubmissions) file models.FileField(upload_tosubmissions/%Y/%m/) submitted_at models.DateTimeField(auto_now_addTrue) is_late models.BooleanField(defaultFalse) class Meta: unique_together (student, assignment) class Grade(models.Model): submission models.OneToOneField(Submission, on_deletemodels.CASCADE, related_namegrade) score models.FloatField() feedback models.TextField(blankTrue) tag models.CharField(max_length50, blankTrue)Django 的字段定义比 SQLAlchemy 更“高屋建瓴”比如FileField直接处理文件存储路径ForeignKey和OneToOneField的关系管理也更方便。模型写完后执行makemigrations和migrate就行不用手写建表语句。3.4 权限设计RBAC 怎么落地权限这块我要多说两句因为这是很多初学者容易忽略的地方。热词里有个“django rabc”应该就是 RBAC基于角色的访问控制。在 Django 里RBAC 几乎是开箱即用创建两个用户组teacher_group和student_group在视图层用login_requiredpermission_required装饰器控制访问from django.contrib.auth.decorators import login_required, permission_required login_required permission_required(assessment.add_assignment, raise_exceptionTrue) def create_assignment(request): # 只有具备添加考核项权限的用户能访问 ...Flask 没有内置权限体系我采用了装饰器方案from functools import wraps from flask_login import current_user def role_required(*roles): def wrapper(fn): wraps(fn) def inner(*args, **kwargs): if current_user.role not in roles: return Forbidden, 403 return fn(*args, **kwargs) return inner return wrapper app.route(/course/int:course_id/assignment/new, methods[POST]) login_required role_required(teacher, admin) def create_assignment(course_id): ...在正式项目里我强烈建议用 Django 的 RBAC 体系而不是自己造轮子。权限设计看似简单但牵扯到角色继承、数据范围隔离、审计日志之后手写很容易在某个细节上漏掉一个漏洞。4. 核心功能实现与实操细节4.1 作业发布与截止时间控制作业发布的逻辑不复杂但“截止时间”这个东西很容易踩坑。系统判断一个考核项的状态我用的是状态机思维尚未开始当前时间 发布可见时间如果设置的话进行中发布时间 当前时间 截止时间已截止当前时间 截止时间逾期提交提交时间晚于截止时间但系统允许提交只是打上is_lateTrue标记这个判断在前后端都要做但以后端为准因为用户可以把系统时间改掉绕过前端判断。Django 里写个模型方法from django.utils import timezone def get_status(self): now timezone.now() if now self.start_at: return upcoming if now self.deadline: return open return closed时间问题还有个隐形坑服务器时区。如果服务器是 UTC用户在中国时区截止时间存 23:59 还是 15:59我的做法是数据库统一存 UTC前端展示用 JavaScript 转本地时间判断截止状态时把用户传入的时间转成 UTC 再比较。这样暑假换服务器也好数据库迁移也好不会出现差8小时的诡异问题。4.2 文件上传与附件管理学生提交作业最常见的形式就是传附件这块的安全底线必须拉满。主要控制点有四个扩展名白名单只允许.py、.pdf、.docx、.zip等常规格式禁止.exe、.bat、.sh这些可执行文件。文件大小限制Django 在settings.py里设DATA_UPLOAD_MAX_MEMORY_SIZENginx 层还要配client_max_body_size双层限制。文件名重写用户上传的文件名不可信可能含路径穿越字符../../直接用 UUID 重命名存储原始文件名存数据库。存储目录隔离按考核项 ID 建子目录方便以后打包下载。示例代码Django 视图import uuid, os from django.conf import settings def handle_uploaded_file(submission, uploaded_file): ext os.path.splitext(uploaded_file.name)[1].lower() if ext not in settings.ALLOWED_UPLOAD_EXTENSIONS: raise ValueError(不支持的文件类型) new_name f{uuid.uuid4().hex}{ext} relative_path fsubmissions/{submission.id}/{new_name} full_path os.path.join(settings.MEDIA_ROOT, relative_path) os.makedirs(os.path.dirname(full_path), exist_okTrue) with open(full_path, wb) as dest: for chunk in uploaded_file.chunks(): dest.write(chunk) submission.file.name relative_path submission.save()如果还想控制学生重复提交只要设置Submission.unique_together ((student, assignment))第二次提交就会触发数据库唯一约束错误我在视图里捕获这个错误后选择覆盖旧文件并更新提交时间。4.3 教师评分批量操作与形成性反馈教师评分环节直接决定了系统能不能落地。一开始我做了个单一评分页老师看一个学生提交、打分、写评语、下一个结果朋友用一次就嫌弃了60 个学生的班级光点页面就得 5 分钟。后来我改成“列表批量评分”列出这个考核项下所有学生提交每个学生一行分数和评语输入框直接内联一次表单提交后端循环更新所有成绩支持 Excel 模板导入老师线下填好分数上传后系统解析导入Excel 导入用 pandas 就很方便import pandas as pd def import_grades(assignment_id, excel_file): df pd.read_excel(excel_file) for _, row in df.iterrows(): student User.objects.get(usernamerow[学号]) submission Submission.objects.get(assignment_idassignment_id, studentstudent) Grade.objects.update_or_create( submissionsubmission, defaults{score: row[成绩], feedback: row.get(评语, )} )形成性反馈还有个被低估的功能——标签系统。我给成绩加了tag字段excellent、good、improve、resubmit。老师打分时顺手点一个标签学生看到“需重新提交”就知道作业有问题还能改。这个标签还能做后续的数据分析比如统计一个班里“需要改进”的比例辅助老师调整教学节奏这才是“形成性”的真正含义。4.4 成绩统计与可视化成绩汇总的计算逻辑不算难学生每门课的总评 Σ考核项得分 × 权重。但是真正放到生产环境还要考虑没交作业的学生、被老师设置“零分”和“未交”的区分。我在Grade设计里用scoreNone表示“未评分”用score0表示“已交但得0分”这两者统计时都要区别对待。可视化部分我直接借了之前做过“农产品价格数据可视化-flask”那类项目的套路后端统计完数据返回 JSON前端用 ECharts 渲染。比如统计班级成绩分布from django.http import JsonResponse from django.db.models import Avg, Count def class_grade_distribution(request, course_id): data (Grade.objects.filter(submission__assignment__course_idcourse_id) .values(score) .annotate(countCount(id))) return JsonResponse(list(data), safeFalse)前端接住这个接口数据渲染成柱状图或散点图。老师一眼看清班级整体状态中间段人数多不多尾巴掉队的多不多这些都是形成性考核最关心的信息。图表用起来之后朋友的原话是“这比我用 Excel 拉透视表清楚多了”。5. 部署上线与成绩实时推送5.1 Flask项目的生产部署与附件路径坑把 Flask 项目部署到生产环境我最想吐槽的就是Windows 服务器上的附件路径问题。开发时在 Windows 本地跑没问题一到服务器上上传的文件要么报错要么找不着下面这个坑几乎人人会遇到# 错误写法相对路径 app.config[UPLOAD_FOLDER] uploads这个写法在python app.py启动时可能没问题但只要换用waitress-serve或改启动目录相对路径就跟着变了文件被存到莫名其妙的位置。正确做法是用绝对路径import os UPLOAD_FOLDER os.path.join(os.path.dirname(os.path.abspath(__file__)), uploads) app.config[UPLOAD_FOLDER] UPLOAD_FOLDER生产部署我推荐这套组合waitress Nginx 反向代理因为 waitress 是 Windows 和 Linux 通用的纯 Python WSGI 服务器比 gunicorn 在 Windows 上省心得多。pip install waitress waitress-serve --listen0.0.0.0:8000 app:appNginx 配置里重点要处理两件事一是上传大小限制二是附件目录的静态映射server { listen 80; server_name your-domain.com; client_max_body_size 50m; location / { proxy_pass http://127.0.0.1:8000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } location /uploads/ { alias /path/to/your_project/uploads/; } }这里/uploads/必须让 Nginx 直接服务不能走 Flask 应用进程否则每次下载附件都要经过 Python 处理性能和稳定性都差。我把这个经验和朋友一说他立刻拍大腿——之前就是全都走应用进程服务器带宽一打满就 502。5.2 Django项目的生产部署注意事项Django 部署比 Flask 多走几步路核心在静态文件收集和DEBUGFalse下的资源处理。我的标准流程是pip install gunicornLinux 服务器或waitressWindowspython manage.py collectstatic把静态文件收集到一个目录python manage.py migrate更新数据结构用环境变量管理DEBUG、SECRET_KEY、数据库连接等敏感配置启动 Django 服务配合 Nginx 反向代理Django 的settings.py里有几个必须注意的参数DEBUG False ALLOWED_HOSTS [your-domain.com] STATIC_ROOT /var/www/static/ MEDIA_ROOT /var/www/media/DEBUGFalse之后Django 默认不会再处理静态文件全部交给 Nginx。MEDIA_ROOT是学生上传文件存放的根目录STATIC_ROOT是前端资源收集目录。这两个目录要确保运行 Django 的进程有读写权限很多部署问题都出在权限上。5.3 成绩发布推送WebSocket 化朋友要求“学生提交后想第一时间知道老师评分了”这就需要服务端主动推送。我用的方案分两种但底层思路一致Flask 版用 Flask-SocketIO创建 SocketIO 实例后台评分完成后调用socketio.emit()。Django 版用 Channels用 daphne 作为 ASGI 服务器Redis 做 channel layer。Channels 的消费者代码大致这样# consumers.py import json from channels.generic.websocket import AsyncWebsocketConsumer class GradeConsumer(AsyncWebsocketConsumer): async def connect(self): self.course_id self.scope[url_route][kwargs][course_id] self.group_name fcourse_{self.course_id} await self.channel_layer.group_add(self.group_name, self.channel_name) await self.accept() async def disconnect(self, close_code): await self.channel_layer.group_discard(self.group_name, self.channel_name) async def grade_update(self, event): await self.send(text_datajson.dumps({ message: event[message], score: event[score], feedback: event[feedback], }))评分写入数据库后往对应课程组发一个广播async_to_sync(channel_layer.group_send)( fcourse_{course_id}, { type: grade_update, message: 你的作业已评分, score: grade.score, feedback: grade.feedback, } )前端的 JavaScript 就是一个常规 WebSocket 连接收到消息后弹个提示并把分数更新到页面上。实时推送功能做完之后整个系统终于有点“活”的感觉了。热词里提到的“python django websocket实现后台有数据前端推送”本质上就是上面这段逻辑。6. 常见问题排查与避坑清单6.1 一张容易踩坑的速查表问题现象根因分析解决方案上传的附件找不到使用了相对路径存储改用基于__file__的绝对路径并配置 Nginx alias中文文件名乱码浏览器与服务器编码不一致存储时用 UUID 重命名数据库存原始文件名学生重复提交导致数据错误数据库缺少唯一约束设置 unique_together代码捕获 IntegrityError截止时间判断差了8小时UTC 与本地时间混用数据库统一 UTC比较时全部转换为 UTC大批量并发提交时 502应用进程处理静态文件/上传上传和静态文件交给 Nginx应用只管业务后台管理页面样式丢失DEBUGFalse 后未收集静态文件执行 collectstatic 并配置 STATIC_ROOT不同浏览器看到的时间显示不一致前端直接渲染后端时间字符串后端返回 ISO 格式前端用 JS 转本地时间6.2 深度排查一附件路径错误全记录这是整个项目里我花时间最多的问题。Windows 开发环境下os.getcwd()返回的是项目根目录所以UPLOAD_FOLDERuploads看似正常。但用 waitress 部署时启动脚本可能放在别的目录或者通过服务方式启动导致工作目录变成C:\Windows\System32文件就被写到系统目录里了。最终排查思路是这样先打印app.root_path确认应用实例的基准目录然后用这个基准目录拼接上传路径。Flask 的app.root_path是应用模块所在目录比os.getcwd()可靠得多。另外上传目录要提前创建好否则首次运行会报FileNotFoundError。6.3 深度排查二并发提交导致成绩覆盖课程考核系统如果只在单机上跑并发量不算大但“同一个学生快速双击提交”这个场景一定会有。一开始我处理得粗糙判断“已有提交就覆盖文件”结果出现了两个线程同时读同一份旧文件、一个写新文件一个删旧文件的乱象。后来我在数据库层面加了唯一约束兜底视图里捕获IntegrityError后转成提示“请勿重复提交”同时在写文件时用uuid生成临时文件名、写完再os.replace()原子替换彻底解决文件被并发写坏的问题。这个经验让我意识到小程序没有高并发不代表不用考虑并发安全用户行为是天然的并发来源。6.4 安全提醒表单、文件与模板注入做这类管理系统安全意识不能少。CSRF 防护一定要开Flask 用 Flask-WTF 自带的 CSRFProtectDjango 的{% csrf_token %}是标配。文件上传要校验扩展名和 MIME不能只信前端。还有一点容易被忽略Jinja2/Django 模板注入SSTI。如果系统允许老师在某种富文本里填内容并直接渲染到页面用户输入却被当成模板语法解析就可能出大问题。我的做法是所有用户输入默认以纯文本转义输出允许格式的地方用 Markdown 渲染库处理绝不让用户输入直接拼进render_template_string。热词里出现过 flask ssti 相关的字样做 Flask 项目时一定要把模板渲染的口子封死。7. 一点实操心得这个项目做完我最大的体会是所谓“形成性考核管理系统”技术本身不难难的是把一线教师零散的考核流程梳理成一套可复用的数据结构。老师在白板上列的那一长串需求落到数据模型上就是六个表落到页面上就是发布、提交、评分、统计四个界面。做这类系统先跟真正用系统的老师聊通流程比闷头写代码重要得多。再分享一个小技巧如果你也是同时接触 Flask 和 Django不要试图一次把两个框架的所有细节都记牢。把注意力放在“路由怎么写、模型怎么建、表单怎么验证、文件怎么传”这几个共通问题上每个框架各做一个小项目等第二个框架上手时你会发现自己学得特别快。课程考核系统这个题目就非常适合拿来练手——业务边界清楚功能覆盖常用 Web 开发场景做出来还能真给老师用上。后面如果有兴趣我琢磨着给这个系统加上 AI 批改作业的能力先让学生自己跑一遍单元测试再让模型给代码写点评语那可能就是下一个版本的亮点了。
返回列表