简介:一套完整的计算机专业毕业设计项目方案,基于 Python 与 Django 构建公务员考试信息管理系统,包含论文、源代码和说明文档,覆盖职位查询、个性化推荐、在线报名、考试提醒与数据分析等核心功能。压缩包共739个文件,大小约19.83MB,主要包含 Vue 前端组件、Python 后端代码、JS/CSS 页面、SQL 数据库脚本和 docx 论文文档,结构清晰便于定位。系统采用前后端分离思路,后端基于 Django,数据库选用 MySQL,前端以 Vue 和 HTML/CSS/JavaScript 为主;信息查询、职位推荐、在线报名、考试提醒、数据分析等模块完整,能支撑毕业设计展示与功能扩展。源码带有较详细注释,说明文档对功能模块、部署流程和数据库设计作出梳理,可支撑毕业设计调试、论文撰写或二次开发扩展。目前已有143人学习,相关目录和模块结构完整,适合课程设计、毕业设计或公务员考试信息管理方向的实战参考。
1. 用 Python 做公务员考试信息管理系统,难度刚好处在“能独立完成”这一档
如果把“公务员考试信息管理系统”拆成业务,线并不长:招考单位发布公告和职位表,考生注册后查看信息并在线报名,管理员逐条做资格审核,笔试面试结束之后录入成绩,系统按排名生成公示名单。你把这个流程做成一个可演示的项目,就同时覆盖了两个角色、一组带状态流转的报名单、至少一个数据汇总场景,恰好踩中毕业设计评分表里“工作量、数据库设计、文档完整度”这三块。
选 Python 的价值不在语言本身,而在框架生态。用 Django 这类框架,admin 后台、登录认证、ORM 全是现成的,代码量能压到 3000 行以内,论文里也好交代——哪部分是框架复用,哪部分是业务扩展,一句话就能说明白。下面沿着一个真实项目的迭代顺序走:先定技术选型和数据模型,再把报名、审核、成绩三条流程写成代码,然后把代码转化成论文和说明文档素材,最后收在答辩演示的一个关键技巧上。
2. 选型与数据建模:为什么用 Django、五张核心表怎么落进 models.py
2.1 Django 与 Flask 的选型逻辑:管理类系统优先选 Django
毕业设计的选型,第一原则是用最小代价把“完成度”做出来。公务员考试信息管理系统是典型的 CRUD 密集型系统,叫得上名的模块无非“公告管理、职位管理、报名审核、成绩登记”,这类系统正好是 Django 的舒适区。
| 对比项 | Django | Flask |
|---|---|---|
| 后台管理 | 自带 admin,注册模型即可用 | 没有内置,需要自己写页面 |
| ORM 与迁移 | 内置,makemigrations/migrate 一条链 | 依赖 SQLAlchemy 等第三方库 |
| 认证与 Session | 内置 auth 应用 | 需要扩展或自行实现 |
| 适用场景 | 结构化明确的管理信息系统 | 轻量 API、单一功能页面 |
这里并不是全盘否定 Flask。如果你的选题更偏“接口服务”或页面极少,Flask 也做得完,而且更容易在篇幅上做减法。但对一个多角色、多状态的招录系统,Flask 的“自由”很快会变成重复造轮子:用户登录要配,后台列表要写,分页搜索要自己处理,这些都是答辩现场容易被追问的地方。Django 的自带功能不是注水,它换来的是你把精力放在业务逻辑上——论文里就写“基于 Django 框架的二次开发”,这是干净且真实的表述。
2.2 核心表设计:用户、公告、职位、报名、成绩这五张表就够了
做数据建模时有一个常见的返工原因:按页面来设计表,而不是按流程来设计。页面会改,流程相对固定。“发布公告 → 查看职位 → 提交报名 → 资格审核 → 录入成绩 → 结果公示”这条线里,需要持久化的实体只有五个:用户、招考公告、招聘职位、报名记录、考试成绩。
用户表需要区分角色,并保存考生身份信息;公告表保存标题和正文;职位表挂在公告下,还要有报名起止时间;报名记录是考生与职位的关联表,承载审核状态;成绩表与报名记录一对一,存放笔试、面试、总分。用一个models.py把它们落下来:
# users/models.py from django.db import models from django.contrib.auth.models import AbstractUser class User(AbstractUser): ROLE_CHOICES = ( ('admin', '管理员'), ('candidate', '考生'), ) role = models.CharField('角色', max_length=20, choices=ROLE_CHOICES, default='candidate') id_card = models.CharField('身份证号', max_length=18, blank=True) phone = models.CharField('联系电话', max_length=11, blank=True)# recruitment/models.py from django.db import models from django.conf import settings class Announcement(models.Model): title = models.CharField('公告标题', max_length=200) content = models.TextField('公告内容') publish_time = models.DateTimeField('发布时间', auto_now_add=True) is_published = models.BooleanField('是否发布', default=True) class Position(models.Model): STATUS_CHOICES = ( ('draft', '草稿'), ('open', '报名中'), ('closed', '已截止'), ) department = models.CharField('招考部门', max_length=100) title = models.CharField('职位名称', max_length=100) headcount = models.IntegerField('招录人数', default=1) education = models.CharField('学历要求', max_length=50) status = models.CharField('状态', max_length=10, choices=STATUS_CHOICES, default='draft') deadline = models.DateTimeField('报名截止时间') class Application(models.Model): AUDIT_CHOICES = ( ('pending', '待审核'), ('approved', '已通过'), ('rejected', '已退回'), ) candidate = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='applications' ) position = models.ForeignKey(Position, on_delete=models.CASCADE, related_name='applications') apply_time = models.DateTimeField('报名时间', auto_now_add=True) audit_status = models.CharField('审核状态', max_length=10, choices=AUDIT_CHOICES, default='pending') audit_remark = models.TextField('审核备注', blank=True) class Meta: constraints = [ models.UniqueConstraint(fields=['candidate', 'position'], name='unique_application') ] class ExamResult(models.Model): application = models.OneToOneField(Application, on_delete=models.CASCADE, related_name='result') xingce_score = models.DecimalField('行测成绩', max_digits=5, decimal_places=2) shenlun_score = models.DecimalField('申论成绩', max_digits=5, decimal_places=2) interview_score = models.DecimalField('面试成绩', max_digits=5, decimal_places=2, null=True, blank=True) total_score = models.DecimalField('总成绩', max_digits=5, decimal_places=2)有两处值得单独解释。第一,自定义 User 时必须通过settings.AUTH_USER_MODEL引用,而不是直接from django.contrib.auth.models import User,否则 Django 在迁移时会提示系统用户模型冲突,这几乎是从零开始就会踩的坑。第二,UniqueConstraint(fields=['candidate', 'position'])是数据库层面的唯一约束,它的作用是“同一个考生不能重复报同一个职位”,这一行在并发场景下比在视图里先查询再判断更可靠。
2.3 初始化项目并激活后台:跑起来再说
模型定义完成后,初始化 Django 项目,这一步顺序不要乱:
mkdir exam_system cd exam_system python -m venv venv venv\Scripts\activate # Windows source venv/bin/activate # macOS/Linux pip install django django-admin startproject exam_system . python manage.py startapp users python manage.py startapp recruitment项目创建后,在settings.py的INSTALLED_APPS里加入users和recruitment,同时配置AUTH_USER_MODEL = 'users.User'。接下来把模型注册到 admin 后台:
# recruitment/admin.py from django.contrib import admin from .models import Position, Application, ExamResult @admin.register(Position) class PositionAdmin(admin.ModelAdmin): list_display = ('department', 'title', 'headcount', 'status', 'deadline') list_filter = ('status', 'department') @admin.register(Application) class ApplicationAdmin(admin.ModelAdmin): list_display = ('candidate', 'position', 'audit_status', 'apply_time') list_filter = ('audit_status',)注册后运行迁移命令,再到浏览器里验证:
python manage.py makemigrations python manage.py migrate python manage.py createsuperuser python manage.py runservermakemigrations会根据模型生成迁移文件,migrate把迁移应用到数据库,createsuperuser创建后台管理员账号,runserver启动本地开发服务。这里有一个高频报错:如果用户在已有数据的表上新增了非空字段,makemigrations会要求你提供默认值或设置null=True,不要用删库来逃避,那会让论文里的“数据库设计”部分失去依据。
3. 业务代码实现:报名、审核、成绩,三条流程怎么串起来
3.1 考生端:职位列表、提交报名、防止重复报名
考生端的第一件事是看职位。先写一个职位列表视图,只展示状态为“报名中”的职位:
# recruitment/views.py from django.shortcuts import render, redirect, get_object_or_404 from django.contrib.auth.decorators import login_required from django.contrib import messages from .models import Position, Application @login_required def position_list(request): positions = Position.objects.filter(status='open') return render(request, 'recruitment/position_list.html', { 'positions': positions, })@login_required的作用是拦截未登录用户,Django 会把它重定向到登录页;filter(status='open')把“草稿”和“已截止”的职位排除掉,避免考生在报名已经结束之后还能看到入口。这个小过滤条件对应论文里的“非功能需求:数据有效性控制”,答辩时是能展开讲的点。
提交报名是核心逻辑,代码要同时处理两件事:职位是否可报、是否已经报过。
@login_required def apply_position(request, pk): position = get_object_or_404(Position, pk=pk, status='open') if Application.objects.filter(candidate=request.user, position=position).exists(): messages.error(request, '你已经报过该职位,不能重复提交') return redirect('position_list') Application.objects.create( candidate=request.user, position=position, audit_status='pending' ) messages.success(request, '报名提交成功,等待管理员审核') return redirect('position_list')get_object_or_404(Position, pk=pk, status='open')做的是两件事:按主键查职位,同时校验职位状态。如果职位不存在或已截止,用户看到的直接是 404 页面,这样“已截止的职位仍能提交”这类脏数据出现的概率会低很多。重复报名的校验用了exists(),它只判断记录是否存在,比先get()再捕获异常更直观。不过注意:视图层的校验只是用户体验的一部分,真正防止两个请求同时到达导致数据重复的,是 2.2 节里那条数据库唯一约束——这两层要在论文的“系统设计”中分开写。
3.2 管理员端:报名审核与批量操作
审核流程的状态流转交代清楚,整个系统的骨架就立住了:
| 环节 | 触发动作 | 前置状态 | 目标状态 | 说明 |
|---|---|---|---|---|
| 提交报名 | 考生点击报名 | — | 待审核 | 唯一约束防止重复 |
| 审核通过 | 管理员操作 | 待审核 | 已通过 | 可填写审核备注 |
| 审核退回 | 管理员操作 | 待审核 | 已退回 | 考生可查看退回原因 |
| 成绩录入 | 管理员录入 | 已通过 | — | 通过后才有资格录入 |
| 结果公示 | 管理员发布 | 已有成绩 | — | 按总分降序排列 |
管理员审核单个报名记录,一个函数视图即可:
@login_required def audit_application(request, pk): application = get_object_or_404(Application, pk=pk) if request.user.role != 'admin': messages.error(request, '只有管理员可以执行审核操作') return redirect('position_list') new_status = request.POST.get('audit_status') if new_status in ('approved', 'rejected'): application.audit_status = new_status application.audit_remark = request.POST.get('audit_remark', '') application.save(update_fields=['audit_status', 'audit_remark']) messages.success(request, '审核结果已保存') return redirect('application_list')这里request.user.role != 'admin'是简单的角色校验,基于 2.2 节定义的User.role字段;update_fields参数让save()只更新指定的两列,避免把整条记录的其他字段重写一遍。对审计类表单来说,这算是一个职业习惯——数据表里如果还有apply_time、create_time这类字段,全量保存时很容易因为覆盖问题产生脏数据。
审核列表场景通常存在批量操作。后台一次打开几十条待审核记录,一条一条点确认并不现实,用 Django ORM 的批量更新可以在一条 SQL 里完成:
@login_required def audit_batch(request): application_ids = request.POST.getlist('application_ids') action = request.POST.get('action') if action in ('approve', 'reject') and application_ids: new_status = 'approved' if action == 'approve' else 'rejected' Application.objects.filter(id__in=application_ids).update( audit_status=new_status ) return redirect('application_list')filter(id__in=application_ids).update(...)在数据库层直接执行 UPDATE,不会触发每一行的模型save()逻辑,速度更快,也避免了 for 循环里逐条保存的重复代码。你的导师如果追问“这样做和循环 save 有什么区别”,回答要点是:update()不走 ORM 信号,适合纯状态修改;如果需要同时记录审核人、审核时间等字段,就回到循环里逐个save()。这两种写法分别在论文“系统设计”和“系统实现”里出现,工作量也好看。
3.3 成绩模块:Excel 导入、查询、排名
成绩录入的方式有两种:管理后台手工填写,以及从 Excel 表格批量导入。现实场景里,几十上百个考生的成绩靠手工录入不现实,用openpyxl读取 xlsx 文件是常见做法:
# recruitment/services.py from openpyxl import load_workbook from .models import ExamResult def import_scores(file_path): wb = load_workbook(file_path) ws = wb.active for row in ws.iter_rows(min_row=2, values_only=True): application_id, xingce, shenlun = row[0], row[1], row[2] ExamResult.objects.update_or_create( application_id=application_id, defaults={ 'xingce_score': xingce, 'shenlun_score': shenlun, } ) return '导入完成'iter_rows(min_row=2, values_only=True)从第二行开始读,跳过表头;update_or_create做的事情是:按application_id查找,找不到则创建,找到了则更新成绩。这样同一个 Excel 文件重复导入多次,结果也是幂等的,不会出现成绩被重复插入的情况。
成绩排名和查询是考生端跑得最多的一个页面:
from django.db.models import F @login_required def result_list(request): results = ExamResult.objects.select_related( 'application__candidate' ).order_by('-total_score') return render(request, 'recruitment/result_list.html', { 'results': results, })order_by('-total_score')按总分降序排列;select_related把成绩关联的报名记录和考生信息提前 join 出来,避免在模板里逐条访问result.application.candidate.username时产生 N+1 次查询。这个优化写在论文的“系统性能设计”一节里非常合适。
4. 论文与说明文档:代码写完后,怎么把工作量翻译成看得见的交付物
4.1 论文目录直接跟着系统模块走
毕设论文看的是“逻辑闭环”,目录最好直接对应系统模块,不要先写一堆技术介绍再回头找功能。推荐骨架是:
- 第一章 绪论:研究背景、意义、现状、论文结构
- 第二章 相关技术:Python、Django、MySQL、前端框架
- 第三章 系统分析:可行性分析、需求分析、用例图
- 第四章 系统设计:架构设计、模块划分、数据库 E-R 图
- 第五章 系统实现:按公告管理、职位管理、报名管理、审核管理、成绩管理逐节写
- 第六章 系统测试:测试环境、测试用例、测试结果
这套结构的优势在于:一、二章是模板,三四章有建模内容支撑,第五章就是代码注释的改写,工作量分布均匀。避免把论文写成“Python 教程”,因为评审老师要看的是“你怎么解决业务问题”,而不是“你会不会用 requests 库”。
4.2 从代码生成 E-R 图和用例图
数据库 E-R 图不用手画。Django 有一个方便的命令,能从现有模型直接生成图文件,适合在论文第四章引用:
pip install pydot django-extensions # settings.py 的 INSTALLED_APPS 里加 django_extensions python manage.py graph_models recruitment users -o er_model.pnggraph_models recruitment users指定要扫描的应用,输出er_model.png。生成的图会显示每个模型的字段和表间外键关系。需要注意,E-R 图的美观度有限,字段多时箭头会交叉,论文里建议在 draw.io 里稍微重排一下布局,或者只截取核心部分。
用例图按两个角色画:考生涉及注册登录、浏览公告、查询职位、在线报名、查看审核状态、查询成绩;管理员涉及登录、发布公告、管理职位、审核报名、录入成绩、生成公示。把参与者画清楚,期末答辩老师第一眼扫过去就知道系统角色划分是对的。状态图不要用网上模板,直接用 3.2 节那张状态流转表去画,更贴合你的代码实现。
4.3 说明文档的定位:写部署和演示步骤,不写教程
说明文档要能解决“拿到代码的人怎么跑起来”的问题。一个够用的 README 模板:
# 公务员考试信息管理系统 ## 1. 环境要求 - Python 3.10 及以上 - Django 4.x / 5.x - MySQL 8.0 或 SQLite 3 ## 2. 部署步骤 1. python -m venv venv 创建虚拟环境并激活 2. pip install -r requirements.txt 3. 修改 settings.py 中的数据库连接配置 4. python manage.py makemigrations 5. python manage.py migrate 6. python manage.py createsuperuser 创建管理员 7. python manage.py runserver 启动服务 ## 3. 默认账号 - 管理员:admin / 自行设置 - 考生:在注册页面注册即可实际操作中,很多同学的说明文档只有寥寥几句“修改配置”。正确的做法是把pip install -r requirements.txt能跑通作为底线,把“第一次启动后点哪些菜单、创建什么数据”写进去。演示时按文档操作一遍,顺带也验证了文档的正确性——这一步在答辩评分里比重不低。
测试部分的说明文档用功能测试表,每一条对应一个可验证的操作:
| 用例编号 | 用例名称 | 前置条件 | 操作步骤 | 预期结果 |
|---|---|---|---|---|
| TC-01 | 考生注册 | 未登录 | 进入注册页填写信息 | 注册成功并跳转登录 |
| TC-02 | 职位报名 | 已登录,职位报名中 | 点击报名 | 提示报名成功,状态为待审核 |
| TC-03 | 重复报名 | 已报名同一职位 | 再次点击报名 | 提示不能重复报名 |
| TC-04 | 管理员审核 | 存在待审核报名 | 审核通过 | 考生端状态变为已通过 |
这里有个小技巧:测试表放在论文第六章,但说明文档里也放一份,可以起到“自测清单”的作用。答辩前的调试让说明文档发挥价值,远比现场翻代码高效。
5. 答辩演示前的一个关键技巧:用 fixture 固定“干净状态”
答辩演示翻车,大多不是功能不存在,而是系统状态太乱。比如演示时点开报名列表,里面是几天前乱建的数据,或者演示到一半被一条脏数据拦住,这时候现场理数据非常被动。解决办法是:不要手工清数据库,用 Django 的 fixture 做“数据快照”。
用dumpdata把业务表导出为 JSON 文件:
python manage.py dumpdata recruitment.Announcement recruitment.Position \ recruitment.Application recruitment.ExamResult users.User \ --indent 2 -o fixtures/demo_data.jsondumpdata后面跟的是“应用.模型”,这条命令会把五个业务表的数据按缩进格式导出到demo_data.json。接下来在答辩演示前执行恢复操作:
python manage.py flush --noinput python manage.py loaddata fixtures/demo_data.jsonflush会清空所有表数据并保留表结构,loaddata再把demo_data.json里的数据重新导入。两条命令一执行,系统就回到“一个干净但数据完整”的起点。
针对这个项目,建议导出两组 fixture:一组是“报名进行中”状态,包含若干已报名但未审核的数据,用于演示审核流程;另一组是“成绩已发布”状态,用于演示成绩查询和排名。演示顺序固定为“登录 → 发布公告 → 报名 → 审核 → 录成绩 → 查排名”,每一步在 fixture 里都能对应到具体数据,讲解时会更从容。另外有个调试相关的小提醒:如果答辩现场要在 PyCharm 或 VSCode 里打断点演示源码,先确认是以 Debug 模式启动服务,否则会碰到“当前不会命中断点”的提示,这种问题看起来很基础,但在台上解释起来很尴尬。演示前把这一轮流程走一遍,确认调试器和数据状态都正常,再谈发挥。
本文还有配套的精品资源,点击获取