Django实战:学生作业管理系统设计与部署全程解析 📅 发布时间:2026/9/14 15:43:14 👁 浏览次数: 学生作业管理系统这个题目在课程设计和毕业设计里出现的频率高得吓人。但说实话我见过太多人卡在同一个地方代码从网上down下来跑不起来或者数据库配不明白又或者逻辑混乱到根本看不懂。我自己早些年也踩过不少坑后来用Python Django把这套系统完整重写了一遍源码、数据库、文档一次配齐。这套系统解决的核心问题很简单老师发作业、学生交作业、老师批改打分、成绩统计归档整个流程全部在线完成不再靠微信群里刷屏交文件。管理员管老师和学生的账号、课程班级信息老师可以按课程发布作业、查看提交情况、在线批改评分学生登录后能看到自己所有课程的待办作业提交文件或者在线填写内容。权限边界清楚流程闭环数据在数据库里一目了然。这次我会把这套系统的设计思路、数据库结构、核心代码逻辑、部署步骤和排坑记录全部拆开讲适合正在做课设/毕设的同学也适合想快速搭一个内部小工具的开发者参考配置过程会非常详细直接照着做基本不会翻车。1. 技术选型和整体设计思路1.1 为什么偏偏是Python Django市面上的技术方案很多PHP、Java SSM、Spring Boot、Vue Node.js都能做这类系统但我最终还是选了Python搭配Django来实现。原因很实在这套系统本质上是业务管理系统核心价值在于快速把业务逻辑跑通而不是炫技。Django自带的东西太适合这种场景了。Admin后台开箱即用数据库模型靠着ORM直接映射不用手写SQL建表用户认证和会话管理也是内置的CSRF防护默认开启。对于一个学生作业管理系统来说我需要把精力集中在“作业发布、提交、批改”这条主干上而不是从零去搭RBAC权限、写登录注册。Django的MTV架构Model-Template-View也足够直观model管数据view管逻辑template管页面新人看代码很容易找到入口。拿Django的ORM举例真不是一般地省事。传统JDBC或者原生SQL操作的时候每写一个查询都要拼接字符串、管理连接、处理结果集而Django里就是一行Assignment.objects.filter(course__teacherrequest.user)链式查询、跨表关联、条件过滤全搞定开发效率至少快一倍。对于期限紧张的项目选Django就是选了一条捷径。1.2 角色权限和功能模块拆解先把系统要服务的“人”想清楚功能才能不跑偏。这套系统一共三种角色管理员管理教师和学生账号维护班级、课程信息查看全局数据。教师创建课程、发布作业、查看学生提交记录、在线批改打分、发布课程公告。学生查看作业列表、在线提交作业文本或附件、查看批改结果和分数。权限控制是这类系统最容易被忽略的地方。学生如果直接输入URL就能访问教师后台那系统就废了。Django自带的认证系统搞定了“登录”这层我再配合自定义装饰器控制“角色”这层语法非常简单后面代码部分会详细贴出来。模块划分上我做了认证登录模块、课程班级管理模块、作业发布模块、提交批改模块、公告模块和Admin后台管理模块。六个模块各管一摊新增功能时只改自己那一块不会牵一发动全身。比如后面想加一个“成绩排行榜”只需要在作业模块旁边加个新视图就行老代码完全不动。1.3 源码目录结构和MTV分层这类项目最怕的就是目录乱成一锅粥。我拿到这套源码的第一感受是它的目录结构一直在遵守Django的“约定优于配置”原则student_assignment/ ├── manage.py # Django管理入口 ├── requirements.txt # 环境依赖 ├── db.sqlite3 # 数据库文件默认SQLite ├── static/ # 静态文件CSS/JS/图片 ├── media/ # 用户上传的作业附件 ├── config/ # 项目配置 │ ├── settings.py # 全局配置文件 │ ├── urls.py # 根路由 │ └── wsgi.py └── apps/ ├── users/ # 用户模块 │ ├── models.py # 用户模型 │ ├── views.py # 登录注册视图 │ └── urls.py ├── courses/ # 课程班级模块 │ ├── models.py │ ├── views.py │ └── urls.py ├── assignments/ # 作业模块核心 │ ├── models.py │ ├── views.py # 发布作业/提交作业/批改 │ └── urls.py ├── students/ # 学生端视图 │ ├── views.py │ └── urls.py ├── teachers/ # 教师端视图 │ ├── views.py │ └── urls.py └── announcements/ # 公告模块 ├── models.py ├── views.py └── urls.py四个子应用各自独立Model层只管数据结构View层只管业务逻辑和请求处理Template只负责把数据渲染到页面上。这种分层最大的好处是排查问题的时候思路清晰页面显示不对就去查模板接口报错就去查视图持久化有问题就去查模型不用像个无头苍蝇一样瞎猜。2. 数据库设计和核心模型实现2.1 数据表关系梳理数据库设计是整个系统的地基。地基打歪了后面写多少代码都白搭。这套系统的核心表一共六张用户表User复用Django内置用户表再加上一个role字段区分管理员、教师、学生。班级表ClassInfo班级名称、所属专业、年级。课程表Course课程名称、课程代码、授课教师外键关联用户表。作业表Assignment作业标题、描述内容、所属课程外键、发布教师外键、截止时间、附件。提交表Submission作业外键、学生外键、提交内容、附件、提交时间、分数、教师评语、状态。公告表Announcement标题、内容、发布人外键、发布时间。表之间的关系说白了就是一个教师可以教多门课一门课只有一个授课教师一门课下面可以发布多个作业一个作业属于一门课一个作业可以接收多个学生的提交一个学生在同一课程下只能提交一次该作业。这里有一个细节要重点提醒提交表设立唯一约束即同一个学生针对同一个作业只能有一条记录。学生第一次提交后如果还没到截止时间可以更新这条记录但是不能插入新的一行。这是避免数据重复的关键很多半吊子系统就是在这里设计了放弃最后统计成绩的时候数据全乱套了。2.2 核心模型代码要点下面贴出作业和提交这两个核心模型字段设计上做了很多实际考量from django.db import models from django.contrib.auth.models import User class Course(models.Model): name models.CharField(max_length100, verbose_name课程名称) code models.CharField(max_length20, uniqueTrue, verbose_name课程代码) teacher models.ForeignKey(User, on_deletemodels.CASCADE, verbose_name授课教师) students models.ManyToManyField(User, related_namestudent_courses, verbose_name选课学生) created_at models.DateTimeField(auto_now_addTrue) class Meta: verbose_name 课程 verbose_name_plural 课程 def __str__(self): return self.name class Assignment(models.Model): title models.CharField(max_length200, verbose_name作业标题) description models.TextField(blankTrue, verbose_name作业要求) course models.ForeignKey(Course, on_deletemodels.CASCADE, related_nameassignments, verbose_name所属课程) teacher models.ForeignKey(User, on_deletemodels.CASCADE, verbose_name发布教师) due_date models.DateTimeField(verbose_name截止时间) attachment models.FileField(upload_toassignments/, blankTrue, nullTrue, verbose_name作业附件) created_at models.DateTimeField(auto_now_addTrue) class Meta: verbose_name 作业 verbose_name_plural 作业 def __str__(self): return self.title class Submission(models.Model): STATUS_CHOICES ( (submitted, 已提交), (graded, 已批改), (returned, 已退回), ) assignment models.ForeignKey(Assignment, on_deletemodels.CASCADE, related_namesubmissions, verbose_name作业) student models.ForeignKey(User, on_deletemodels.CASCADE, verbose_name学生) content models.TextField(blankTrue, verbose_name作业内容) attachment models.FileField(upload_tosubmissions/, blankTrue, nullTrue, verbose_name提交附件) submitted_at models.DateTimeField(auto_now_addTrue, verbose_name提交时间) score models.DecimalField(max_digits5, decimal_places1, blankTrue, nullTrue, verbose_name分数) feedback models.TextField(blankTrue, verbose_name教师评语) status models.CharField(max_length20, choicesSTATUS_CHOICES, defaultsubmitted, verbose_name状态) graded_at models.DateTimeField(blankTrue, nullTrue, verbose_name批改时间) graded_by models.ForeignKey(User, on_deletemodels.SET_NULL, blankTrue, nullTrue, related_namegraded_submissions, verbose_name批改教师) class Meta: unique_together (assignment, student) verbose_name 作业提交 verbose_name_plural 作业提交 def __str__(self): return f{self.student.username} - {self.assignment.title}字段设计有几个讲究的地方我特意解释一下score用的是DecimalField(max_digits5, decimal_places1)而不是IntegerField。因为成绩经常会出现85.5这样的0.5分用整型就存不了小数用浮点型又容易产生精度问题DecimalField才是正经做法。blankTrue, nullTrue的原因更简单作业发布时没有成绩成绩是教师批改的时候才算出来的所以允许为空。on_delete参数的选择也很关键。课程删除时下面的作业、提交记录会变成孤儿数据所以课程关联用了CASCADE一键级联删除省得留垃圾。但graded_by指向批改教师如果教师账号被删除还想保留历史批改记录这里用了SET_NULL从“删干净”变成“保留数据但置空”这是两类不同业务场景的取舍。2.3 数据库交付和迁移管理这套源码的数据库交付方案是SQLite加MySQL两套都兼容。默认配置用的是SQLite就是项目根目录下的那个db.sqlite3文件零配置直接用特别适合课程设计答辩展示。如果你想切到MySQL只需改settings.py里的数据库配置后面第4章会详细说。Django自带了一套“数据库同步”机制也就是makemigrations和migrate这一点跟传统课设里手动导入.sql脚本完全不一样。模型改完之后跑一句makemigrationsDjango会自动生成迁移脚本再跑migrate它就会按顺序把表结构同步到数据库里。不用自己写建表语句也基本不会出现“开发能跑、部署炸了”的情况这套迁移机制比手写SQL靠谱太多。3. 核心功能模块的实操实现3.1 登录认证和角色权限控制用户登录这块直接用Django内置的authenticate和login方法就行。真正需要自己写的是权限校验我写了一个通用装饰器判断当前登录用户的角色from functools import wraps from django.shortcuts import redirect def role_required(role): def decorator(view_func): wraps(view_func) def _wrapped_view(request, *args, **kwargs): if not request.user.is_authenticated: return redirect(login) if request.user.profile.role ! role: # 角色不匹配时重定向到首页不直接返回403 return redirect(index) return view_func(request, *args, **kwargs) return _wrapped_view return decorator用法直接往视图函数上一挂就行role_required(teacher) def assignment_create(request, course_id): # 只有教师角色能发布作业 ...这里有个设计细节值得说角色不匹配时不返回403错误页而是重定向到首页。理由是403页面对普通用户来说太不友好了很多学生会以为“系统坏了”而不是“我没权限”。重定向回首页再用Django的messages框架弹一个“当前账号无权限访问”的提示体验好得多。3.2 作业发布、提交、批改的完整主流程作业模块是整个系统的核心我按照“教师的作业管理”和“学生的作业提交”两个视角分开写。教师发布作业的流程是教师登录 - 进入课程详情 - 点击“发布新作业” - 填写作业标题、要求、截止时间可选上传附件 - 保存。保存逻辑里有个重要的校验点截止时间必须晚于当前时间否则直接提示“截止时间不能早于当前时间”。这个校验既在前端页面用JavaScript做了第一层拦截又在后端视图里做了二次校验防止有人绕过前端直接POST请求。学生提交作业的流程是学生登录后首页展示作业列表每个作业卡片会显示状态未开始、已提交、已批改一目了然。点击进入作业详情如果是“已提交”状态就显示已交内容且给出修改按钮如果已过截止时间则限制只能查看不能修改如果还没提交显示提交表单。学生可以填写文本内容也可以上传附件。教师批改是另一个入口教师看到某作业的提交列表后点进某个学生的提交记录填分数和评语状态从“已提交”变成“已批改”。这里我额外加了一个“退回”操作。比如学生交上来的附件打不开或者明显是乱写的教师点“退回修改”状态变成“已退回”。退回后学生那边会看到一个“作业被打回”的通知重新提交后才能再次进入批改队列。文件下载这里有一个特别容易踩的坑就是直接拿FileResponse返回文件时浏览器会乱码或者显示为一串乱码文件名。正确做法是用StreamingHttpResponse并显式设置Content-Type和Content-Dispositionfrom django.http import StreamingHttpResponse def download_assignment_file(request, submission_id): submission Submission.objects.get(idsubmission_id) file submission.attachment response StreamingHttpResponse( file.chunks(), content_typeapplication/octet-stream ) # 处理中文文件名编码避免浏览器下载时乱码 filename file.name.split(/)[-1] response[Content-Disposition] fattachment; filename*UTF-8{quote(filename)} return responsefilename*UTF-8这个写法是我特意标出来的直接用filename文件名遇到中文必定乱码这个坑我当年踩得很惨所以特别提醒一下。3.3 Django Admin后台定制与美化Django自带的Admin后台是这类项目的杀手锏。默认后台只能看个表格但我们完全可以改造成一个顺手的运营管理界面。在admin.py里给每个模型注册一个自定义的Admin类from django.contrib import admin from .models import Assignment, Submission admin.register(Assignment) class AssignmentAdmin(admin.ModelAdmin): list_display [title, course, teacher, due_date, created_at] list_filter [course, teacher, due_date] search_fields [title, course__name] date_hierarchy due_date ordering [-due_date]list_display控制表格显示哪些列list_filter让后台可以直接按课程、教师筛选作业search_fields支持关键字搜索作业标题、课程名date_hierarchy把截止时间变成日期层级筛选器。整套配置下来后台的可操作性甚至比一些自己写的前端页面还好用。再进一步可以整个换掉默认配色和布局套一个Django SimpleUI的第三方主题后台立刻变成现代管理系统的样子在答辩演示时观感提升相当明显。这个库的安装就是pip install django-simpleui然后在INSTALLED_APPS里把simpleui放到django.contrib.admin前面刷新页面就有奇效。4. 从零搭建运行环境的完整步骤4.1 Python环境安装和虚拟环境创建源码拿到手第一件事是把运行环境跑通。这部分我会写得极其详细照着复制命令就行。现在Windows和Linux上安装Python都很方便了官方下载或者包管理器装完记得勾选“Add Python to PATH”。装完之后验证一下python --version虚拟环境这一步千万别省。不同项目的依赖版本经常互相打架直接装到全局会让你后面想哭。创建一个项目专属的虚拟环境# 创建虚拟环境 python -m venv venv # Windows激活 venv\Scripts\activate # Linux/macOS激活 source venv/bin/activate激活之后命令行前面会出现(venv)说明虚拟环境已经生效。然后安装依赖pip install -r requirements.txtrequirements.txt的核心内容就是Django和Pillow处理上传图片用的通常还会带上mysqlclient或pymysql等数据库驱动看你用哪种数据库。装完了用pip list确认一下版本OK就可以继续了。4.2 数据库连接配置和mysqlclient安装避坑默认SQLite的话什么都不用改直接跳到migrate就行。但如果你想用MySQL需要在settings.py里改数据库配置DATABASES { default: { ENGINE: django.db.backends.mysql, NAME: student_assignment, USER: root, PASSWORD: 你的密码, HOST: 127.0.0.1, PORT: 3306, } }MySQL驱动这里坑特别多pip install mysqlclient经常因为缺少编译环境直接报错。替代方案是装pymysql然后在项目的__init__.py里加两行import pymysql pymysql.install_as_MySQLdb()就能让Django像操作MySQLdb一样操作MySQL了。实测这个方案在Windows上成功率最高不需要任何编译器环境。4.3 数据库初始化和启动项目环境配好后第一次启动要按顺序执行这几条命令# 1. 根据模型生成迁移脚本 python manage.py makemigrations # 2. 把迁移脚本同步到数据库 python manage.py migrate # 3. 创建超级管理员账号用于登录Admin后台 python manage.py createsuperuser # 4. 启动开发服务器 python manage.py runserver浏览器打开http://127.0.0.1:8000先看到的是系统的登录页。登录之后别急着走建议先去后台http://127.0.0.1:8000/admin用管理员账号登录然后在后台创建教师账号、学生账号、课程信息和班级信息。这些基础数据一旦就绪再去前台测试发布作业和提交作业就会顺畅很多比我直接在页面里点点点手工造数据效率高多了。5. 常见问题排查和避坑记录5.1 高频报错速查表我把这套系统在开发和部署阶段最常见的报错整理成了一张表方便快速排查问题现象可能原因解决方案ModuleNotFoundError: No module named django依赖没装好确认虚拟环境已激活执行pip install -r requirements.txtdjango.db.utils.OperationalError: no such table数据库没迁移执行python manage.py makemigrations和migrate上传图片到后台一直提示错误Pillow库缺失执行pip install pillow页面CSS丢失全部是裸HTML静态文件加载失败检查settings.py里的STATIC_URL和STATICFILES_DIRS配置前端提交作业后文件上传卡住media路由没配在项目urls.py里加上 static(settings.MEDIA_URL, document_rootsettings.MEDIA_ROOT)用户登录后跳转不对LOGIN_URL或redirect配置问题检查settings.py中登录/登出后的跳转地址5.2 几个典型的“坑”一定要单独说坑一删除对象时用错方法是真会删错数据。Django提供get()和filter()两种查询方式对应的删除行为也不太一样。Assignment.objects.get(id1).delete()只能删一条Assignment.objects.filter(course__id1).delete()会把这个课程下所有作业一次性删掉。后者的杀伤力极大尤其在有外键关联的情况下CASCADE会把作业对应的所有提交记录一起删掉想恢复只能靠数据库备份。所以我给自己定了一条规矩写删除逻辑的时候必须先用count()预览一下即将删除的数据量心里有数再执行。坑二重定向后怎么把提示信息带过去。很多时候操作完需要跳转到另一个页面同时还要告知操作结果。初学者常干的事是把提示信息拼在URL参数里/list/?msgsuccess这种做法既不安全又写起来麻烦。Django里其实有一套现成的解决方案messages框架。视图里一行messages.success(request, 作业发布成功)模板里循环读取并渲染轻松搞定。这个框架底层走的是session所以选用它来传递一次性提示刷新页面后消息也会自动消失不会一直残留。坑三中文文件名附件在下载时乱码。这个在前面已经提到过解决办法是设置Content-Disposition时用filename*UTF-8传编码后的文件名。别看只是多写了一行实测下来这道处理比直接传filename要稳定得多尤其在国内环境中文文件名几乎必中招。5.3 项目维护经验和后续扩展方向这套系统在实际运行中如果能一直用下去有几个方向很值得扩展。一个是作业查重。把学生提交的文本内容做分词和相似度比对能立刻识别出谁在互相复制粘贴。就算只做一个最简单的Jaccard相似度计算答辩的时候讲出来也比单纯交作业这个基础功能加分很多。一个是成绩统计分析。数据库里的成绩数据天然适合做图表展示用ECharts在前端画一个班级成绩分布柱状图、平均分走势折线图学生和教师都能直观看到学情趋势。另一个是对接接口给移动端。Django原生MTV渲染的页面在手机上体验一般如果后续有小程序或App的需求可以用Django REST Framework把这套逻辑封装成API应用层不变视图层改成APIView一份逻辑两端复用扩展成本极低。最后再分享一个真实的维护经验这套系统的附件存储走的是本地媒体文件夹小规模使用完全够但如果你真的部署到生产环境建议把文件存储切到OSS或云存储服务上否则时间一长服务器磁盘会被学生的作业附件堆满。文件存储路径要做好“按日期分目录”的策略不然单个目录下文件数量过万后一些文件系统访问速度会明显变慢。这个教训是我自己跑了大半年后总结出来的希望你能少走一段弯路。