Django在线考试系统源码解析:从环境搭建到自动判分
发布时间:2026/9/25 6:39:05来源:尧图网络
简介这是一套基于Python与Django框架开发的在线考试系统完整项目面向计算机相关专业的毕业设计、课程作业以及需要快速搭建考试平台的开发者。系统采用管理员、教师、学生三角色分级权限设计覆盖用户注册登录、班级课程关联、题库维护、智能组卷、考试发布与成绩统计等核心流程支持单选、多选、判断、填空、简答及可在线运行的Python编程题并提供Excel/Word批量导入与多条件检索能力。资源包共607个文件约21.63MB以Vue组件、JavaScript脚本、Python后端代码、SVG图标与图片素材为主另含SQL建库脚本、配置文件及数据库文档前端界面与后端逻辑分层清晰便于二次开发与部署调试。目前已有66人学习下载适合作为毕业设计参考或课程实践模板帮助读者快速理解在线考试系统的整体架构与业务实现。1. 从一份 Django 在线考试系统源码说起它到底能跑通什么如果你正在做毕业设计或者课程作业大概率会遇到这个场景题目定了「基于 Python 的在线考试系统」技术栈选了 Django但真正动手时发现——题库怎么建模、考试倒计时怎么控制、交卷后客观题怎么自动判分、数据库文档到底要写到什么颗粒度每一步都是坑。这份资源就是一套已经跑通的 Django 在线考试系统源码附带数据库设计文档覆盖了从用户角色、题库管理、组卷策略到考试记录、成绩统计的完整链路。它适合三类人一是毕设选题卡在「有功能但不成体系」的同学需要一份结构完整的参考实现二是刚学完 Django 基础、想找一个真实项目练手的入门者三是需要快速搭出考试模块原型的开发者。核心价值不在于代码多高级而在于它把「考试」这个业务场景里的关键约束——限时、防重复提交、客观题自动判分、主观题人工批阅——都用 Django 的常规能力落地了没有引入过重的中间件本地跑起来门槛低。2. 环境搭建与项目启动从 Python 安装到 runserver 跑通2.1 依赖版本与虚拟环境选择拿到源码包后第一件事不是急着pip install而是先确认 Python 版本和 Django 版本的匹配关系。这套项目基于 Django 3.x 或 4.x 的常规写法如果你本地装的是 Python 3.12 而 Django 锁在 3.2部分依赖会编译失败。常见做法是建一个独立虚拟环境把版本钉死。# 创建虚拟环境python3 -m venv 是标准做法 python -m venv venv # Windows 激活 venv\Scripts\activate # macOS / Linux 激活 source venv/bin/activate # 安装依赖建议先看 requirements.txt 里 Django 的版本号 pip install -r requirements.txt这里的关键参数是requirements.txt里的 Django 版本。如果文件里写的是Django3.2.18就不要手动升级到 4.x因为 4.x 里url()被re_path()替代、django.conf.urls的导入方式也有变化直接跑会报ImportError。虚拟环境的作用是把项目依赖和系统 Python 隔开避免你之前装的爬虫库、数据分析库干扰 Django 的版本解析。2.2 数据库配置与迁移项目默认可能用 SQLite也可能用 MySQL。数据库文档里一般会给出表结构但代码里的settings.py才是真正生效的地方。打开settings.py找到DATABASES配置段# settings.py 数据库配置示例 DATABASES { default: { ENGINE: django.db.backends.mysql, # 如果用 MySQL NAME: exam_system, # 数据库名需提前建好 USER: root, PASSWORD: your_password, HOST: 127.0.0.1, PORT: 3306, OPTIONS: { charset: utf8mb4, # 支持中文和 emoji } } }参数说明NAME对应的数据库必须先在 MySQL 里CREATE DATABASE exam_system DEFAULT CHARSETutf8mb4;建好Django 不会自动建库。utf8mb4而不是utf8是因为考试系统里题干可能包含特殊符号utf8在三字节以上字符会截断。配好后执行迁移python manage.py makemigrations python manage.py migrate python manage.py createsuperuser # 创建后台管理员 python manage.py runservermakemigrations是根据models.py生成迁移文件migrate才是真正在数据库里建表。如果这一步报django.db.utils.OperationalError九成是数据库没建、密码错、或者 MySQL 服务没启动。createsuperuser创建的账号用来登录 Django admin 后台题库录入、用户管理都在那里操作。2.3 静态文件与模板路径排查启动后如果页面样式全丢、图片裂开通常是静态文件路径没配对。Django 在DEBUGTrue时由django.contrib.staticfiles自动服务静态文件但前提是INSTALLED_APPS里有这个 app且模板里用{% static %}标签而不是硬编码/static/xxx。!-- 模板中正确引用静态文件的方式 -- {% load static %} link relstylesheet href{% static css/exam.css %}如果你在 VS Code 里写img src/static/img/logo.png发现显示不了先检查settings.py里STATIC_URL是不是/static/再确认图片实际放在app/static/img/目录下。Django 的静态文件查找是按 app 逐个扫描static子目录不是项目根目录下的static除非你额外配了STATICFILES_DIRS。这个点新手翻车率极高血泪经验是路径没错但 404八成是目录层级放错了。3. 核心数据模型拆解题库、试卷、考试记录怎么建3.1 题库模型与题型字段设计在线考试系统的地基是题库模型。这套源码里通常会把题目和选项拆成两张表或者用 JSON 字段存选项。拆表的好处是查询灵活、便于统计每个选项的选择分布JSON 字段的好处是结构简单、一次查询拿全。数据库文档里一般会标注字段类型和约束下面是一个典型的题目模型# models.py 题目模型 class Question(models.Model): QUESTION_TYPE ( (single, 单选题), (multiple, 多选题), (judge, 判断题), (essay, 简答题), ) content models.TextField(verbose_name题干) q_type models.CharField(max_length10, choicesQUESTION_TYPE) score models.IntegerField(default5, verbose_name分值) answer models.TextField(verbose_name正确答案) analysis models.TextField(blankTrue, verbose_name解析) category models.ForeignKey(Category, on_deletemodels.SET_NULL, nullTrue)参数说明q_type用choices约束取值范围避免脏数据score默认 5 分组卷时按总分动态调整answer对于多选题存成A,B,C这种逗号分隔字符串判分时拆分比对category外键指向分类表on_deleteSET_NULL表示分类删除后题目保留但分类置空比级联删除安全。数据库文档里会把这些字段的注释、索引、默认值列成表格写毕设时直接引用即可。3.2 试卷与考试记录模型试卷模型的核心是「组卷策略」——是固定题目还是随机抽题。固定试卷把题目 ID 存成关联表随机试卷则在创建考试记录时按分类和数量抽题快照。考试记录模型要记录考生、试卷、开始时间、交卷时间、得分、状态。# 考试记录模型 class ExamRecord(models.Model): user models.ForeignKey(User, on_deletemodels.CASCADE) paper models.ForeignKey(Paper, on_deletemodels.CASCADE) start_time models.DateTimeField(auto_now_addTrue) submit_time models.DateTimeField(nullTrue, blankTrue) score models.IntegerField(default0) status models.CharField(max_length10, defaultongoing) # ongoing/finishedauto_now_addTrue让开始时间在记录创建时自动写入不需要前端传。submit_time允许为空因为考试进行中还没有交卷。status字段用来控制「同一份试卷不能重复交卷」——交卷时先查status是否为ongoing是才允许更新否则返回「已交卷」。这个逻辑在视图里用select_for_update()加行锁更稳妥防止并发交卷导致分数覆盖。3.3 数据库文档的颗粒度要求数据库文档不是把models.py翻译一遍就完事。合格的文档至少包含表名、字段名、字段类型、是否为空、默认值、注释、索引、外键关系。毕设答辩时老师常问「这个字段为什么这么设计」文档里最好附一句设计理由。比如answer字段用 TextField 而不是 CharField是因为简答题答案可能很长score用 IntegerField 而不是 FloatField是因为考试分数一般取整避免浮点误差。字段名类型允许空默认值说明contenttext否无题干内容q_typevarchar(10)否无题型枚举scoreint否5题目分值answertext否无标准答案analysistext是空答案解析这张表可以直接放进论文的「数据库设计」章节比截图 ER 图更清晰。4. 考试流程实现组卷、倒计时、交卷判分的代码落地4.1 随机组卷与题目快照随机组卷的常见做法是考生点击「开始考试」时后端按试卷配置的分类和数量从题库里order_by(?)随机抽取然后把抽到的题目 ID 写入考试记录关联表。这样每份试卷的题目顺序和内容都是独立的避免相邻考生互相偷看。# 组卷逻辑 import random from .models import Question, ExamRecord, RecordQuestion def generate_paper(paper, user): record ExamRecord.objects.create(useruser, paperpaper) for rule in paper.rules.all(): # rules 存分类和抽题数量 questions list(Question.objects.filter( categoryrule.category, q_typerule.q_type )) selected random.sample(questions, min(rule.count, len(questions))) for q in selected: RecordQuestion.objects.create(recordrecord, questionq) return recordrandom.sample而不是order_by(?)是因为后者在数据量大时会对全表排序性能差。min(rule.count, len(questions))防止题库题目不够时sample报错。抽题后写入RecordQuestion关联表相当于给这次考试做了一份题目快照——即使后续题库修改已生成的试卷不受影响。这个设计在数据库文档里体现为一张中间表字段包括record_id和question_id。4.2 倒计时与自动交卷倒计时有两种实现前端 JS 计时、后端记录截止时间。只做前端计时的话考生刷新页面或改系统时间就能重置所以必须后端兜底。常见做法是在ExamRecord里加一个deadline字段开始考试时写入start_time paper.duration。# 后端校验是否超时 from django.utils import timezone def check_timeout(record): if timezone.now() record.deadline: if record.status ongoing: auto_submit(record) # 自动交卷只判已答题目 return True return False前端用 JS 每秒刷新剩余时间到 0 时自动提交表单。但后端在每次保存答案和交卷时都要调check_timeout防止前端计时被绕过。timezone.now()而不是datetime.now()是因为 Django 开启了时区支持后数据库存的是 UTC 时间直接用datetime.now()会差 8 小时这个坑非常隐蔽。4.3 客观题自动判分与主观题标记交卷时遍历该考试记录的所有题目客观题比对答案主观题标记为待批阅。判分逻辑要处理多选题的「少选得分」还是「少选不得分」这套源码一般按「完全匹配才得分」处理简单可靠。# 自动判分 def auto_grade(record): total 0 for rq in record.recordquestion_set.all(): q rq.question if q.q_type in (single, judge): if rq.user_answer q.answer: total q.score elif q.q_type multiple: user_set set(rq.user_answer.split(,)) correct_set set(q.answer.split(,)) if user_set correct_set: total q.score else: rq.need_manual True # 主观题标记人工批阅 rq.save() record.score total record.status finished record.submit_time timezone.now() record.save()set比对多选题答案避免顺序不同导致误判。主观题不自动给分而是打上need_manual标记后台管理员在 admin 里逐条批阅。这里要注意user_answer如果为空字符串split(,)会得到[]和正确答案比对不相等所以未作答的题目自然得 0 分不需要额外判断。5. 避坑与排查那些跑起来才会暴露的问题5.1 迁移报错 No changes detected现象执行makemigrations提示No changes detected但明明改了models.py。原因通常是 app 没有加入INSTALLED_APPS或者models.py里的类没有继承models.Model。解决检查settings.py的INSTALLED_APPS是否包含该 app 名称再确认模型类定义正确。如果 app 是后来创建的还要确保apps.py里的name和实际路径一致。5.2 静态文件 404 但路径看着没错现象浏览器控制台报GET /static/css/exam.css 404但文件确实存在。原因多半是DEBUGFalse时 Django 不再自动服务静态文件或者STATICFILES_DIRS没配。解决开发阶段保持DEBUGTrue如果必须DEBUGFalse用python manage.py collectstatic把静态文件收集到STATIC_ROOT再由 Nginx 或 WhiteNoise 托管。VS Code 里写 img 标签显示不了先确认{% load static %}有没有加。5.3 交卷后分数为 0 或答案丢失现象考生答完题交卷成绩是 0或者部分答案没保存。原因可能是前端表单字段名和后端request.POST.get()的 key 不一致或者保存答案时没有关联到正确的RecordQuestion。解决在视图里打印request.POST看实际提交的键名确认每道题的 input name 带上了题目 ID比如nameq_12后端按q_前缀解析。另一个常见原因是 CSRF token 没带POST 被 403 拦截答案根本没到后端。5.4 时区导致倒计时差 8 小时现象后端记录的deadline和前端显示剩余时间对不上或者自动交卷提前/延后触发。原因settings.py里USE_TZTrue时数据库存 UTC而datetime.now()返回本地时间。解决统一用django.utils.timezone.now()模板里用{{ deadline|date:Y-m-d H:i:s }}会自动转成TIME_ZONE设置的时区。如果不想折腾时区把USE_TZFalse也能跑但数据库文档里要注明这个取舍。5.5 并发交卷导致分数覆盖现象同一考生开两个标签页同时交卷最终分数是其中一次的另一次答案丢失。原因两个请求同时读到statusongoing都执行了判分和更新。解决交卷视图里用ExamRecord.objects.select_for_update().get(idrecord_id)加行锁或者在更新时用filter(statusongoing).update(...)判断受影响行数为 0 则说明已被其他请求处理。这个点在小规模考试里不明显但数据库文档里最好把status字段的并发控制写进设计说明。6. 二次开发与验证把这份源码改成你自己的毕设拿到源码后直接交差只能算及格真正让答辩老师眼前一亮的是你能说清楚「我改了什么、为什么改」。一个具体技巧是先跑通原始流程然后用 Django 的shell逐条验证数据流转。python manage.py shell# 在 shell 里验证组卷和判分 from exam.models import * from exam.views import generate_paper, auto_grade user User.objects.first() paper Paper.objects.first() record generate_paper(paper, user) print(record.recordquestion_set.count()) # 确认抽题数量 # 模拟作答 for rq in record.recordquestion_set.all(): rq.user_answer rq.question.answer # 全部答对 rq.save() auto_grade(record) print(record.score) # 应该等于试卷总分这段脚本能在不启动前端的情况下验证核心逻辑比点页面快得多。如果record.score不等于预期总分就逐题打印rq.question.q_type和rq.user_answer定位是哪类题型判分出了问题。二次开发的方向通常有三个一是加 WebSocket 实现后台有数据时前端实时推送比如管理员发布新考试考生页面自动提示Django Channels 是常见方案二是加 RBAC 权限控制把「出题人」「阅卷人」「考生」角色分开Django 自带的Group和Permission够用三是把数据库文档补全成论文格式加上 ER 图和字段设计理由。我自己的习惯是每次改完models.py先makemigrations看迁移文件内容确认没有误删字段再migrate。从那以后我每次动数据库结构都强制走一遍「备份 → 迁移 → 验证」三步再也没出现过数据丢失。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网