Python+Django学生考勤管理系统:从数据表设计到签到统计完整实现

Python+Django学生考勤管理系统:从数据表设计到签到统计完整实现 简介面向计算机专业毕业设计、课程作业及Python实战练习的学生提供考勤管理系统完整项目资料基于动态网页技术构建包含前端页面、后端业务逻辑、数据库表设计与接口调用可帮助学习者掌握从需求分析、代码实现到部署上线的完整流程。资源包共445个文件压缩后大小约11.61MB主要以Python程序文件、Vue组件、SVG图标、JavaScript、CSS样式、SQL脚本、Word文档、配置文件等类型呈现其中源码经过严格调试可直接运行并配有详细的安装、运行、初始化等批处理脚本。项目经导师指导并通过评审评审分达98分附带完整部署教程与设计论文方便快速搭建环境、验证功能、撰写文档。目前已有178人学习下载内容难度适中适合需要完成毕设或系统学习项目开发的学生参考和复用。1. 基于 Python 的学生考勤管理系统毕设和实训项目都能直接落地临近毕业季学生考勤管理系统是 Python 方向出镜率最高的毕设选题之一。它的业务边界清晰学生、班级、课程、签到签退、请假、迟到早退、缺勤统计每一块都是标准的增删改查加统计报表既能展示后端基本功又方便答辩时现场演示。但这个题目也最容易踩坑很多人把系统写成了“只有登录和列表”答辩时被问倒或者签到接口能重复提交统计结果对不上账还有人在环境配置上卡了两天MySQL 装好了连不上、Django 迁移报错最后仓促交付。这篇博文不做空泛的架构推演而是从技术选型、数据表设计、考勤状态判定、统计查询到答辩论文的对应关系把一套可复现的实现方案拆开讲清楚。想用现成源码快速跑通的人也能在最后拿到一份检查清单和调参思路。2. 为什么选 Python Django 组合做考勤系统选型理由与数据表设计2.1 学生考勤管理系统选 Django 还是 Flask考勤管理系统这类 CRUD 为主的业务系统我一般优先选 Django而不是 Flask。理由很直接Django 自带 ORM、Admin 后台、认证系统和表单处理这些在项目里几乎全用得上。学生管理、课程管理、考勤记录本质上就是对几张数据表做增删改查Django Admin 甚至可以在一开始不写一行前端代码的情况下把后台管理界面跑起来这对快速搭建和中期检查都非常有利。Flask 的优势是轻量、自由但代价是你要自己拼 SQLAlchemy、Flask-Login、WTForms 这些组件。对毕设项目来说自由度高不一定好反而是框定好的结构更容易控制进度。对比维度DjangoFlask自带 ORM有支持关系映射和迁移需另装 SQLAlchemy管理后台自带 Admin开箱即用需要插件或自研用户认证内置 User 模型和权限需自行实现或扩展学习成本中高但功能齐备低但组装成本高适合作业/毕设演示适合后台直接可展示适合但自主拼装要求高我见过不少拿 Flask 做考勤系统的同学最后花在组装登录、权限、分页上的时间远超业务逻辑本身。建议除非你有明确理由比如要展示微服务拆分或纯手写 ORM否则直接选 Django。2.2 考勤管理系统的核心数据表设计一个合格的考勤系统表不要多但关系要清楚。常见做法是设计四张核心表学生表、课程表、考勤记录表再加一张学生选课关联表或者用 Django 的 ManyToManyField 实现。下面是 Django models 的写法可以作为重新建模或对照源码的基准from django.db import models from django.contrib.auth.models import User class Student(models.Model): name models.CharField(max_length50, verbose_name姓名) student_no models.CharField(max_length20, uniqueTrue, verbose_name学号) major models.CharField(max_length100, blankTrue, verbose_name专业) phone models.CharField(max_length11, blankTrue, verbose_name手机号) class Meta: verbose_name 学生 verbose_name_plural 学生 def __str__(self): return f{self.student_no} {self.name} class Course(models.Model): name models.CharField(max_length100, verbose_name课程名) course_code models.CharField(max_length20, uniqueTrue, verbose_name课程编号) teacher models.ForeignKey(User, on_deletemodels.CASCADE, verbose_name授课教师) students models.ManyToManyField(Student, through Attendance, related_namecourses, blankTrue) class Meta: verbose_name 课程 verbose_name_plural 课程 def __str__(self): return f{self.course_code} {self.name} class Attendance(models.Model): STATUS_CHOICES ( (present, 正常), (late, 迟到), (early, 早退), (absent, 缺勤), (leave, 请假), ) student models.ForeignKey(Student, on_deletemodels.CASCADE, verbose_name学生) course models.ForeignKey(Course, on_deletemodels.CASCADE, verbose_name课程) date models.DateField(verbose_name日期) check_in_time models.TimeField(nullTrue, blankTrue, verbose_name签到时间) status models.CharField(max_length10, choicesSTATUS_CHOICES, verbose_name状态) class Meta: unique_together (student, course, date) verbose_name 考勤记录 verbose_name_plural 考勤记录 def __str__(self): return f{self.student} {self.course} {self.date} {self.get_status_display()}重点说明unique_together (student, course, date)是这条数据的核心约束。它的作用是保证同一个学生在同一天、同一门课只有一条考勤记录从数据库层面防止重复签到。很多项目只在前端做了判重结果并发请求一来重复记录就进来了统计自然会出错。Attendance作为中间表承接Student和Course的多对多关系同时携带考勤状态和时间字段。check_in_time允许为空因为请假和缺勤状态没有签到时间强制非空会让数据插入变得别扭。2.3 初始化数据与系统管理入口建模之后第一步不是写业务代码而是把数据库建好、创建一个管理员账户。这是检查开发环境是否正常的最短路径pip install Django python manage.py makemigrations attendance python manage.py migrate python manage.py createsuperuser python manage.py runserver 0.0.0.0:8000makemigrations attendance只会为 attendance 这个 app 生成迁移文件避免把其他无关 app 的变动混进来migrate负责真正创建数据表。createsuperuser会交互式地要求输入用户名、邮箱和密码用于后续登录 Admin 后台。启动后浏览器访问http://127.0.0.1:8000/admin就能进入管理界面。在admin.py里只需要三行注册代码后台就有了针对学生的完整增删改查入口from django.contrib import admin from .models import Student, Course, Attendance admin.site.register(Student, admin.ModelAdmin) admin.site.register(Course, admin.ModelAdmin) admin.site.register(Attendance, admin.ModelAdmin)到这里系统骨架已经搭好了。接下来的重点是考勤业务逻辑也就是签到、签退和状态判定的实现。3. 签到与考勤状态判定迟到、早退、缺勤的正确打开方式3.1 考勤状态判定的业务规则设定考勤系统最容易被低估的部分是状态判定逻辑。常见误区是把“迟到”和“早退”写成两个 bool 字段实际上一个时间段里就只能有一个状态。更合理的做法是基于签到时间和下课签退时间推导出最终状态。规则可以这样定义场景条件状态未签到且未请假整节课无记录缺勤课前或课后宽限期内签到签到时间 ≤ 上课时间 宽限分钟正常超过宽限期签到签到时间 上课时间 宽限分钟迟到下课前离开签退时间 下课时间早退提前申请请假记录存在请假“宽限期”是一个重要参数。如果严格按上课时间判定任何一两分钟的延迟都会被记为迟到这在真实场景中并不合理也容易在答辩时被追问。常见做法是把宽限分钟数设为 10 到 15并且放进配置文件而不是写死。3.2 用 Python 实现考勤状态判定函数状态判定逻辑独立成一个小函数可以方便测试和复用。下面是一个考虑完整度的实现from datetime import datetime, time def determine_attendance_status( check_in: time | None, check_out: time | None, course_start: time, course_end: time, grace_minutes: int 15, has_leave: bool False ) - str: 根据签到签退时间和课程时间返回考勤状态。 参数说明: check_in: 学生签到时间None 表示未签到 check_out: 学生签退时间None 表示未签退 course_start: 课程开始时间如 time(8, 0) course_end: 课程结束时间如 time(9, 40) grace_minutes: 宽限分钟数默认 15 分钟 has_leave: 是否已获批请假 if has_leave: return leave if check_in is None: return absent grace_limit datetime.combine(datetime.today(), course_start) \ __import__(datetime).timedelta(minutesgrace_minutes) check_in_dt datetime.combine(datetime.today(), check_in) if check_in_dt grace_limit: return late if check_out is not None and check_out course_end: return early return present逻辑上有几点要注意。先判断请假和未签到因为这两个情况的优先级最高再做迟到判定最后看是否早退。如果签到时间在宽限期内且签退时间不正常会被判为早退。grace_limit的计算方式说明了宽限期的核心逻辑以课程开始时间加上固定分钟数作为门槛。datetime.combine用于把 date 和 time 拼成 datetime只有 datetime 才支持加减 timedelta。直接对 time 对象做加法会报错这是初学者最常见的坑。3.3 通过 Django 视图实现签到接口有了判定函数把它接入签到接口。这个视图要完成三件事确认是有效课程、检查是否已签到、写考勤记录并返回结果。from django.http import JsonResponse from django.views.decorators.http import require_POST from django.views.decorators.csrf import csrf_exempt from .models import Student, Course, Attendance from datetime import datetime require_POST csrf_exempt def check_in(request): student_id request.POST.get(student_id) course_id request.POST.get(course_id) now datetime.now().time() try: student Student.objects.get(pkstudent_id) course Course.objects.get(pkcourse_id) except (Student.DoesNotExist, Course.DoesNotExist): return JsonResponse({code: 1, msg: 学生或课程不存在}, status400) attendance, created Attendance.objects.get_or_create( studentstudent, coursecourse, datedatetime.today(), defaults{status: determine_attendance_status( check_innow, check_outNone, course_startcourse.start_time, # 需要 Course 模型增加开始时间字段 course_endcourse.end_time, grace_minutessettings.ATTENDANCE_GRACE_MINUTES, ), check_in_time: now} ) if not created: return JsonResponse({code: 2, msg: 已签到请勿重复操作}, status200) return JsonResponse({code: 0, msg: 签到成功, status: attendance.status})get_or_create是并发场景下的关键操作。它依据模型中的unique_together约束在数据库层面避免重复创建记录。如果记录已存在created为 False视图直接返回提示信息。注意course.start_time和course.end_time需要 Course 模型有这两个字段。如果原表没设计可以在迁移中加入# 在 Course models.py 中添加 start_time models.TimeField(defaulttime(8, 0), verbose_name开始时间) end_time models.TimeField(defaulttime(9, 40), verbose_name结束时间)3.4 时间边界与配置参数超时、时区和默认值签到接口一定要设置超时否则学生端网络慢会导致请求挂起同一请求被前端重发产生重复记录。Django 中可以用 session 或缓存做幂等也可以在前端做按钮禁用但最可靠的后端方案还是get_or_create配合唯一约束。另一个高频坑是时区问题。Django 默认的USE_TZ True会把时间按 UTC 存储而中国时区是 UTC8。如果直接datetime.now()取值保存到数据库的时间会差 8 小时。正确做法是在settings.py中设置TIME_ZONE Asia/Shanghai USE_TZ FalseUSE_TZ False表示不使用带时区的时间对象datetime.now()直接取本地时间。如果项目坚持要USE_TZ True那 DJango 里要用django.utils.timezone.localtime()来转换这一点在答辩描述项目时很容易被问到。4. 出勤统计与报表查询Django ORM 聚合与导出 Excel4.1 用 annotate 统计学生出勤率考勤管理的落脚点是统计报表。常见需求是“某学生当前课程出勤率”“某课程每日到课人数”。这类查询用 Django ORM 的聚合函数实现清晰且不易出错。from django.db.models import Count, Q from .models import Attendance, Student def student_attendance_rate(student_id: int, course_id: int) - dict: stats Attendance.objects.filter( student_idstudent_id, course_idcourse_id ).aggregate( totalCount(id), present_countCount(id, filterQ(statuspresent)), late_countCount(id, filterQ(statuslate)), leave_countCount(id, filterQ(statusleave)), ) total stats[total] # 出勤率计算正常 迟到迟到的本质是到课了 present stats[present_count] stats[late_count] rate round(present / total * 100, 2) if total else 0.0 return {total: total, rate: rate, **stats}Count(id, filterQ(...))是 Django 2.0 之后支持的用法它在一次查询内完成多条件计数避免写多条 ORM 再拼结果。这里要特别说明一个统计口径的问题迟到算不算出勤。严格意义上迟到是“到课但未按时到”所以出勤率的分子应该包含正常和迟到缺勤和请假不进分子。很多粗糙的实现把“正常”当作出勤导致迟到学生的数据失真答辩时被问到会很难解释。4.2 用 openpyxl 导出考勤报表统计完还要能导出。Excel 导出是毕设项目的常见加分项也常出现在中期检查或答辩演示中。用 openpyxl 写一个导出函数from openpyxl import Workbook from django.http import HttpResponse def export_attendance_report(course_id: int): wb Workbook() ws wb.active ws.title 考勤报表 ws.append([学号, 姓名, 日期, 签到时间, 状态]) records Attendance.objects.filter(course_idcourse_id).select_related(student) for rec in records: ws.append([ rec.student.student_no, rec.student.name, rec.date.strftime(%Y-%m-%d), rec.check_in_time.strftime(%H:%M:%S) if rec.check_in_time else , rec.get_status_display(), ]) response HttpResponse( content_typeapplication/vnd.openxmlformats-officedocument.spreadsheetml.sheet ) response[Content-Disposition] fattachment; filenameattendance_{course_id}.xlsx wb.save(response) return responseselect_related(student)在这里不是可有可无的优化句。它通过 SQL 的 JOIN 一次性把关联的学生表数据查出来避免循环中反复访问数据库产生 N1 问题。导出几百行数据时可能感受不深到几千行时速度差距非常明显。get_status_display()是 Django model 的固有方法返回选择字段的中文描述。比如status存储的是present该方法输出正常。4.3 统计查询常见的 3 个坑第一个坑是 N1 查询。上面代码中select_related专门处理ForeignKey如果是多对多关系或ManyToManyField需要换用prefetch_related。两者的区别是前者用 JOIN后者用两次查询再组装不能混用。第二个坑是聚合结果的类型。Count返回的是 Python 整数Avg返回的是 Decimal 或 float。如果直接做除法或字符串拼接容易踩类型错误。比如rate present / total * 100在 Python 3 中结果是 float但如果 total 是 0就会抛ZeroDivisionError所以代码里必须加if total else 0.0的保护。第三个坑是时区导致的日期偏移。学生签到时间是 23:50UTC 存储后变成第二天 07:50按date字段查询时会归入错误的一天。这个问题在上一章提到的USE_TZ False可以规避但如果接手的是现成项目要先检查settings.py里的时区配置再写统计逻辑。5. 从源码到答辩快速演示的检查清单与论文对应技巧5.1 拿到或写完系统后30 分钟内完成演示准备无论你是用现成源码还是自己写的系统答辩前的演示路径应该固定且顺畅。先在本地跑通再准备少量数据最后演练两条核心业务链路管理员录入学生和课程、学生完成签到并查看统计结果。第一依赖安装到位。项目根目录通常有requirements.txt执行pip install -r requirements.txt安装全部依赖。如果网络环境不佳可以分别安装 Django 和 openpyxl这两个是核心依赖。第二数据库迁移。执行python manage.py migrate前先确认settings.py中数据库配置和实际安装的数据库一致。如果用默认的 SQLite不需要额外安装服务和配置账号如果用 MySQL要先确保 MySQL 服务已启动并且配置了正确的用户名密码。第三创建超级管理员。createsuperuser创建的账号用于 Admin 后台。现场演示时建议提前建好账号和账号数据不要在现场敲命令建学生。第四预置演示数据。把 5 到 10 条学生记录、3 门课程、若干条不同的考勤状态正常、迟到、请假、缺勤都要有提前录好。这样演示报表时表格里不会全是空数据。5.2 论文里数据表设计和 ER 图这样写才不被问倒如果这篇博文正好对应你的毕业论文那么论文的技术部分重点不是贴代码而是讲清楚数据库设计决策和考勤判定规则。ER 图要画出的关系至少包括学生和课程之间的多对多通过考勤记录表连接、课程和教师之间的一对多、学生和考勤记录之间的一对多。这是三张表之间最核心的关系不要省。答辩提问环节老师大概率会指着 ER 图追问“为什么中间表要有唯一约束”此时可以用第 2 章中unique_together的解释来回答防止同一个学生在同一门课的同一日期出现多条记录保证统计口径一致。考勤规则部分建议用一张判定表呈现这比纯文字描述直观得多。把第 3 章中的规则表格直接放进论文再配合一小段判定函数的伪代码或 Python 代码。注意伪代码不要长篇大论能表达条件分支即可。5.3 一个加分技巧把考勤统计做成前端可调用的接口很多毕设项目止步于 Admin 后台缺少一个面向普通用户的界面或接口。如果你有余力做一个简单的 JSON 接口用于查询某学生的出勤率会让项目完整性上升一个档次。接口可以这样写from django.http import JsonResponse def student_rate_api(request, student_id): course_id request.GET.get(course_id) data student_attendance_rate(student_id, course_id) return JsonResponse(data)前端只要配合一个下拉框和表格就能实现“选课程 - 看出勤率”的交互。不需要复杂的前端框架一个原生 HTML 页面加fetch即可。这个接口的价值在于它把后端逻辑暴露为可测试的服务明显区别于单纯依赖 Django Admin 填表的方案也方便在演示时用浏览器直接访问http://localhost:8000/api/attendance/1/?course_id1验证结果。时间有余的话可以把export_attendance_report也绑定成一个按钮入口点击后下载 Excel这几乎是答辩时最能直观展示项目完成度的操作。本文还有配套的精品资源点击获取