考勤系统大概是Python方向毕设里出场率最高的选题之一。它没有推荐系统那种算法深度不像电商系统那样业务复杂但麻雀虽小五脏俱全组织架构、打卡记录、请假审批、月度统计、报表导出每个模块都能单独拎出来讲清楚。这套基于Django的考勤系统源码配合毕设文档和逐行代码讲解正好是一条“程序文档讲解”的完整交付链适合正在选题的应届生、想补Django实战的新手也适合中小企业低成本搭建内部考勤工具。这篇我会把项目从头到尾拆开讲表结构怎么设计、打卡和迟到判定怎么写、考勤统计怎么算、答辩前有哪些坑必须先排掉全部基于我已经跑通的真实源码不是概念演示。1. 项目整体框架为什么考勤系统是毕设的“稳妥牌”1.1 需求拆解一个考勤系统要管哪几件事先别急着写代码把需求想明白后面全顺。一个能拿得出手的考勤系统最少要覆盖五件事员工信息与部门归属。没有组织架构考勤就是无源之水每日打卡签到签退。这是最核心的业务动作请假与补卡流程。真实世界里总有例外情况月考勤统计。出勤天数、迟到、早退、缺勤、请假天数汇总管理后台与报表导出。给主管和人事看也是答辩演示的门面。这五件事落到Django项目里分别对应模型设计、视图逻辑、模板渲染、Admin后台配置和文件导出。你会发现它天然就是一套完整的业务闭环有增删改查、有权限控制、有时间处理、有统计汇总导师想扣分都难找理由。我做这套源码就是按这个结构来的没有堆砌无关功能每个模块都有明确的存在意义写进毕业论文里也能一一对应。1.2 技术选型Django为什么是这里的最优解当初我也犹豫过要不要用Flask或者干脆上SpringBoot。但综合对比一轮下来Django在这个场景几乎是标准答案。原因很直接Django自带Admin后台管理员工、部门、考勤记录不用重新写一套页面答辩演示时直接进后台点几下就完事ORM对新手极其友好复杂查询可以用Q对象和annotate聚合几天就能上手Django自带的用户认证系统把登录、会话、权限都处理好了省掉最容易被忽略的安全细节生态资料丰富几乎任何报错都能搜到现成答案对时间紧张的毕业生来说“坑少”就是最大的优势。如果你的导师偏好Java方向你也能横向对比一句SpringBoot入门门槛偏高配置复杂度也高考勤这种中小型项目用Django能在同样时间内多做一个模块性价比更划算。这不是说Django天下第一而是在“有限时间完成可演示项目”这个约束下Django确实更稳。对比维度DjangoFlaskSpringBoot上手速度快一体化框架快但组件要自己拼较慢配置多自带管理后台有无无ORM能力完整且顺手主要靠SQLAlchemyJPA/MyBatis认证权限体系内置完整需自己实现Spring Security较重毕设场景推荐度高中中低1.3 数据模型设计五张核心表搞定业务闭环数据模型是整套系统的骨架。我当时画了ER图给导师看其实拆开就是五张表模型关键字段作用Departmentname, manager部门信息员工归属Employeeuser(OneToOne), department(FK), emp_no员工档案关联登录账号AttendanceRecordemployee(FK), date, check_in_time, check_out_time, status每日打卡记录LeaveRequestemployee(FK), leave_type, start_date, end_date, reason, status请假审批流程WorkRulework_start, work_end, late_threshold考勤规则迟到阈值这里有个最关键的设计决定员工表不要单独做一套账号体系而是用OneToOne关联Django内置的User模型。这样做的好处是登录、密码加密、权限分组全部复用Django现成能力只需要在Employee表里挂一个外键。另一个常见做法是直接继承AbstractUser自定义用户模型比如在accounts应用里写from django.contrib.auth.models import AbstractUser class User(AbstractUser): emp_no models.CharField(max_length20, uniqueTrue, blankTrue, nullTrue)然后settings.py里写一行AUTH_USER_MODEL accounts.User。两种方案都行但对于考勤系统这种规模我更推荐OneToOne扩展User少一个自定义用户模型的迁移坑。后面第4章会详细说为什么。2. 核心开发要点用户模型、打卡逻辑与统计报表怎么实现2.1 自定义用户模型第一步做错后面全是坑如果你决定用AbstractUser有件事必须听话在第一次执行makemigrations之前就把AUTH_USER_MODEL配好。为什么因为Django迁移系统会把数据库结构的变化记录在app的migrations目录里一旦你先跑过默认User模型的迁移后面半路改成自定义用户模型会陷入“迁移历史错乱、表结构对不上”的泥潭新手运气不好得删库重来。我见过不少同学卡在这最后只能删掉db.sqlite3和migrations目录下除__init__.py之外的所有文件重新迁移。对本地毕设开发来说这招“删库跑路”不算大事但对已经有测试数据的人来说就非常头疼。所以我的习惯是项目一初始化不管用不用得上抽象用户模型先把AUTH_USER_MODEL指过去哪怕里面只加一个emp_no字段后面想扩展就自由了。如果你用OneToOne方案代码是这样from django.contrib.auth.models import User class Employee(models.Model): user models.OneToOneField(User, on_deletemodels.PROTECT, related_nameemployee) emp_no models.CharField(max_length20, uniqueTrue) department models.ForeignKey(Department, on_deletemodels.PROTECT, nullTrue, blankTrue) hire_date models.DateField(auto_now_addTrue)注意外键的on_delete我用了PROTECT而不是CASCADE这样删除User时如果名下还有员工记录会抛出ProtectedError防止误删。这条经验在第4章会展开讲。2.2 打卡与迟到判定别拿字符串比时间打卡功能的业务逻辑很简单上班点一次下班点一次系统记录时间并判断状态。但实现上有一个特别容易翻车的点很多人都直接拿字符串比较时间想着“08:30”比“08:15”大就当作迟到判定依据。字符串比较在格式统一时勉强能跑可一旦时间格式变成“8:3”或者混入秒结果就完全不对了。正确做法是统一用datetime.time对象比较。打卡视图的核心流程我贴一下from django.utils import timezone def punch(request): employee request.user.employee today timezone.localdate() now timezone.localtime().time() att, _ AttendanceRecord.objects.update_or_create( employeeemployee, datetoday, defaults{check_out_time: now}, ) if not att.check_in_time: rule WorkRule.objects.first() if rule and now rule.work_start: att.status late else: att.status normal att.check_in_time now att.save(update_fields[check_in_time, status]) return JsonResponse({code: 0, msg: 打卡成功})这里面有两件事值得讲清楚。第一我用了update_or_create而不是先get再create因为同一个人一天可能打卡多次第一次调用创建记录并记check_in_time第二次调用只更新check_out_time不用自己写分支判断省事。第二日期和时间都通过timezone.localdate()和timezone.localtime()取而不是date.today()和datetime.now()原因同样是时区Django开启了USE_TZ后直接用系统时间很可能拿到UTC差8小时。迟到判定我做了个简化版本超过work_start就算late没超就normal早退判断在check_out_time小于work_end时标记。更严格的企业规则还有“迟到多少分钟内算迟到超过算半天缺勤”这些都可以塞进WorkRule表里用参数控制而不是写死在代码里。2.3 考勤统计与补签ORM聚合查询的正确姿势月度考勤统计是这类项目的“技术分水岭”也是导师最爱问细节的地方。它的核心不是算总和而是怎么把“应出勤”和“实际打卡”对齐。很多新手写统计逻辑时直接数AttendanceRecord表里有多少条记录认为数出来就是出勤天数。这有两个问题一是请假和缺勤的人根本没有记录统计结果会漏二是周末没有记录如果不处理日历会把周六周日也算成缺勤。正确思路是先构造一个“当月应出勤日期列表”再拿打卡记录去匹配。构造日期列表很简单Python标准库的calendar就能搞定import calendar import datetime def get_workdays(year, month): days [] total calendar.monthrange(year, month)[1] for day in range(1, total 1): d datetime.date(year, month, day) if d.weekday() 5: # 周一至周五 days.append(d) return days拿到这个列表再对每个员工逐日查打卡状态缺失的记录计为缺勤。这种方式逻辑直观答辩时也容易解释。统计迟到次数这类指标用Django ORM的聚合非常顺手from django.db.models import Count, Q stats AttendanceRecord.objects.filter( date__yearyear, date__monthmonth, ).values(employee__emp_no).annotate( late_countCount(pk, filterQ(statuslate)), normal_countCount(pk, filterQ(statusnormal)), )Django 2.0之后聚合函数支持filter参数一个Count就能按条件统计不用先取出来再在Python里循环数。如果你还在用for循环写这种统计建议改成这种写法代码会清爽很多答辩时也更显专业。补签功能是隐藏加分项。员工确实会因为各种原因漏打卡系统必须支持管理员补录。实现上就是后台表单里选员工、选日期、选状态写入一条记录。我额外做了一步补签时记录操作人和时间单独存一个字段。为什么这么做因为答辩时一旦有老师问“这些数据能不能被篡改”你就能答“补签会留痕考勤记录可审计”这一句话就能把数据可靠性问题挡回去。2.4 Admin后台与导出报表毕设演示的隐形加分项Admin后台千万别忽略。我见过太多学生把精力全花在写前端页面上结果答辩现场网络一卡、页面一崩就慌了而Admin后台只要数据库有数据演示就永远不会冷场。它其实是毕设里的“隐形加分项”。在admin.py里注册模型时花十分钟做几个配置效果完全不一样from django.contrib import admin from .models import AttendanceRecord admin.register(AttendanceRecord) class AttendanceRecordAdmin(admin.ModelAdmin): list_display [employee, date, check_in_time, check_out_time, status] list_filter [status, date] search_fields [employee__user__username, employee__emp_no] date_hierarchy date list_per_page 20解释一下这些配置的价值list_filter会在右侧生成状态和日期筛选器演示“查这个月迟到的人”就是点两下鼠标的事search_fields支持跨表搜索按工号或用户名找人很快date_hierarchy在列表页顶部生成日期层级导航可以按年、月逐级下钻非常直观。报表导出我建议直接用Python内置的csv模块配合StreamingHttpResponse几行代码就能下载import csv from django.http import StreamingHttpResponse def export_monthly(request): rows [...] response StreamingHttpResponse(csv_generator(rows), content_typetext/csv) response[Content-Disposition] attachment; filenamemonthly.csv return responsecsv导出轻量够用不依赖openpyxl这类第三方库。如果真的需要Excel格式再加openpyxl但考勤统计场景其实CSV完全够Excel能打开就行。3. 完整实操从创建项目到跑通打卡流程3.1 环境准备Python版本、虚拟环境与Django安装先把环境搭好心急吃不了热豆腐。我推荐Python 3.10配合Django 4.2 LTS这个组合在兼容性和资料丰富度上都很稳Python 3.8到3.12之间问题都不大但别用老掉牙的Python 2.x也别一上来装Django 5.x找刺激。第一步创建虚拟环境。为什么要虚拟环境因为每个项目依赖的Django版本、第三方库版本可能不同全都装到系统Python里迟早版本冲突到时候排错排到怀疑人生。命令很简单python -m venv venv # Windows venv\Scripts\activate # macOS / Linux source venv/bin/activate激活后安装依赖pip install django4.2 pillowpillow主要是给员工头像上传用的也可以先不装用到MEDIA时再说。下一步创建项目和应用django-admin startproject attendance_system cd attendance_system python manage.py startapp accounts python manage.py startapp attendance按模块拆成两个应用accounts管用户和员工档案attendance管打卡、请假和统计。模块分得清楚不仅写代码时大脑不累文档里的架构图也好画。3.2 settings.py关键配置时区、语言、数据库一次配齐创建完项目第一件事不是写业务代码而是先把settings.py里几个坑位填好。时区和语言是考勤系统最容易出问题的两个配置。实测下来推荐这样写LANGUAGE_CODE zh-hans TIME_ZONE Asia/Shanghai USE_TZ TrueLANGUAGE_CODE设为zh-hansAdmin后台就会显示中文菜单答辩演示时看着舒服很多。TIME_ZONE和USE_TZ配合使用确保所有时间在存储时使用UTC展示时转为北京时间。有的人喜欢把USE_TZ设成False图省事但一旦以后接小程序端或App端UTC转换会麻烦死你所以建议一开始就坚持标准做法。数据库方面毕设项目我用的是默认SQLite理由很简单零配置、单文件、换台电脑拷走就能跑。答辩现场如果用MySQL还得准备数据库服务万一导师电脑上没装MySQL就得现场出丑。如果你非要用MySQL在settings.py里把DATABASES改成MySQL配置即可并安装pymysql做驱动但真没必要。3.3 核心功能实现从URL路由到页面渲染全链路前面第2章贴了打卡视图这里我把完整链路串一遍URL路由收到POST请求视图函数先通过request.user拿到当前登录用户再关联出Employee对象用update_or_create写打卡记录最后返回JSON给前端。前端我用了一个简单的fetch发送POST前带上CSRF token确保Django默认的CSRF校验能过。这里提一句千万别图省事在视图上加csrf_exempt毕设也许没人细看但这是明显的安全短板被懂行的老师问住会很尴尬。正确做法是在页面里拿到csrftoken请求头加X-CSRFToken字段几行代码而已。除了打卡考勤统计也要做一个页面入口。月度统计视图的思路是接收前端传来的年、月参数用前面说的get_workdays生成应出勤日期查询该月所有请假记录标记请假日期查询该月所有打卡记录逐个员工按日期填充缺勤日期补0汇总成列表返回模板渲染。这套逻辑用Python写大概两三百行代码量不大但它是整个系统里业务味最浓的部分。答辩时能把这5步讲清楚比把Django源码背一遍有用得多。3.4 迁移、测试数据与一条龙演示路径代码写得再好没数据等于白搭。我通常会用Django的management command写一个种子数据脚本一条命令生成完整demo数据python manage.py seed_data脚本内部会创建5个部门、10个员工并从月初到今天随机生成打卡记录还会造几条请假单和补卡记录。为什么专门做这个因为答辩现场最忌讳的就是临时手动造数据点半天页面还是空的气氛瞬间尴尬。一键生成漂亮数据演示起来顺畅自己也有底气。种子脚本里还会自动创建超级管理员账号登录Admin后台就能看到所有模块的数据。如果你拿这套源码做的是“程序文档代码讲解”的交付那README文档里一定要把这几个命令写清楚pip install -r requirements.txt python manage.py migrate python manage.py seed_data python manage.py runserver你能跑通这四行项目就能立起来。文档不需要写多华丽但流程图、ER图、页面截图要配全因为毕设论文里这些图是硬指标。4. 常见问题与排查技巧实录时区、迁移、删除级联一次说清4.1 打卡时间差8小时时区配置背锅这是考勤系统里出现频率最高的怪现象用户上午9点打了卡后台一看记录时间却是凌晨1点。很多人第一反应是代码写错了其实不是是时区没处理好。Django开启USE_TZTrue后所有DateTimeField在数据库里都以UTC存储这是设计如此。如果你在视图里直接用datetime.now()拿到的是服务器本地时间服务器时区可能是UTC于是存进去的是UTC时间展示时又没有转回Asia/Shanghai就差了8小时。解决办法统一用django.utils.timezone里的localtime/localdate而不要混用datetime.datetime问题就消失了。排查方法也分享下打开Django shell分别打印timezone.now()和timezone.localtime()对比一下。如果两者差8小时那说明当前激活时区配置没问题问题出在业务代码里哪个函数用了裸的datetime。顺着这个方向很快就能定位。4.2 迁移冲突与数据表残留本地重置的正确姿势很多新手会卡在migrate报错“table already exists”或“Detected a new model without migrations”。本质上是数据库实际结构和迁移历史对不上。常见的诱发原因手动改了models.py、手动删过数据库、从别人那里拷贝了项目但带了旧的db.sqlite3。本地开发阶段最干脆的解法是重置删掉db.sqlite3文件在migrations目录里保留__init__.py删除其余文件然后依次执行python manage.py makemigrations python manage.py migrate如果你已经有一些想保留的数据那就别删改完模型后在原迁移基础上再生成一次迁移或者编辑冲突的迁移文件。但实话实说毕设阶段数据结构还没定型直接重置成本最低总比跟迁移文件搏斗一晚上强。顺带提醒一句这类错误是答辩前最容易让人心态爆炸的所以动手写代码前就把AUTH_USER_MODEL、模型结构定好不要频繁改来改去。前期多花十分钟后期少熬两个夜。4.3 删除员工时把考勤记录也删没了Django执行删除的级联机制这个坑我要单独重点讲因为不少人的项目这么设计之后数据说没就没。现象是管理员在后台把一个离职员工删掉结果该员工的所有考勤记录、请假记录全部被连带删除整个历史数据变得不完整。原因就在外键的on_delete选项。如果关联考勤记录时写的是on_deletemodels.CASCADEDjango执行删除操作时会沿着外键关系把所有关联对象一起删除。这机制本身很好用比如删除一个部门时想连带清理所有员工但放在考勤业务里就是灾难。我做过一个实验在shell里执行employee.delete()返回结果是一个字典类似{attendance.Employee: 1, attendance.AttendanceRecord: 156, attendance.LeaveRequest: 4}看到这个输出你就明白一次删除动作牵动了多少数据。所以我的建议是考勤记录、请假记录这些历史数据外键一律用on_deletemodels.PROTECT有数据就不允许删除上级对象保证数据安全员工离职不要物理删除用软删除思路给Employee加is_active字段查询时过滤exclude(is_activeFalse)后台默认也只显示在职员工实在要删先看delete()返回的字典心里有数再动手。这一条经验答辩时如果主动讲给老师听非常加分因为它说明你理解数据生命周期而不只是会写CRUD。4.4 Admin后台样式丢失与静态文件404还有一个高频问题本地开发时Admin后台一切正常某天你把DEBUGFalse一改后台页面突然没有CSS光秃秃的。原因是Django在DEBUG模式下会自动服务静态文件一旦关闭DEBUG这个自动服务就停了你必须先收集静态文件到指定目录并提供静态文件服务。毕设答辩我其实推荐维持DEBUGTrue省心。但如果你为了演示“生产环境部署”非要把DEBUGFalse那记住几步settings.py里设置STATIC_ROOT BASE_DIR / staticfiles执行python manage.py collectstatic把所有静态文件收集到staticfiles目录ALLOWED_HOSTS里加上你的域名或IP临时演示时可以用python manage.py runserver --insecure让开发服务器强制服务静态文件。这个操作不算复杂但原理要懂Django框架本身不擅长处理静态文件生产环境通常交给Nginx这类服务器或者用whitenoise库。毕设阶段能说出这层原理就够了。最后整理一个万能速查表按现象查方案现象大概率原因快速处理打卡时间差8小时USE_TZ与TIME_ZONE配置/代码混用datetime视图统一用timezone.localtime()migrate报table already exists迁移历史与库结构不一致本地重置迁移和数据库删除员工连带删考勤外键用CASCADE改PROTECT或加软删除Admin没样式DEBUGFalse且未collectstatic临时开DEBUG或collectstatic中文乱码 / 后台英文LANGUAGE_CODE未设置设为zh-hans提示no such table数据库路径错或没migrate执行migrate检查DATABASES配置5. 从毕设到工程考勤系统的三个升级方向5.1 前后端分离用Django REST Framework改造成API如果你的考勤系统只是模板渲染的那套答辩过关没问题但如果想锦上添花往工程化方向靠最自然的升级就是前后端分离后端提供JSON API前端用Vue或小程序展示。说实话这一层改造对毕设来说是锦上添花但对找工作面试很有用。改造的核心是引入djangorestframework步骤不复杂安装依赖、写ModelSerializer、把函数视图换成APIView或ViewSet、加个认证方案。比如打卡接口改成class PunchView(APIView): def post(self, request): employee request.user.employee # 打卡逻辑同前 return Response({msg: 打卡成功})之前我做小程序端交付时就是把后端直接API化小程序端只发请求渲染页面PC管理端还保留Admin一套后端服务器两个前端入口效果很唬人。如果你时间充裕这个方向是做“完整项目”的绝佳加分项。5.2 智能打卡人脸识别与地理位置校验想给考勤系统加“智能感”人脸识别和地理围栏是最常见的两个方向。人脸识别可以用OpenCV或face_recognition库做一个简单的“拍照打卡”用户拍照上传后端对照片做人脸检测比对通过就记录打卡。地理围栏则是调用地图SDK在服务端校验打卡坐标是否在公司半径内。这两个功能在毕设里做到“demo能跑”的程度就够了千万别承诺过高的准确率也别在论文里写“99%识别率”这种容易被质疑的话。人脸数据和坐标数据都涉及隐私敏感现阶段作为技术演示即可更规范的企业落地还需要考虑合规设计。给导师演示的时候能讲清技术链路比跑出一个完美效果更有价值。5.3 性能与体验缓存、异步任务、通知提醒真要把这套东西推到企业场景性能优化和体验改造就开始显价值了。月度考勤统计如果员工上千每次都要实时遍历30天数据响应会越来越慢这时候把统计结果缓存到Redis或者用定时任务提前生成月度快照是更合理的设计。报表导出也一样数据量大时可以先用Celery异步生成Excel完成后给个下载链接而不是让请求一直卡在那里。通知提醒这块也有意思员工请假审批通过后系统自动发邮件或者通过IM的webhook推送到群里考勤异常了自动提醒人事复核。这些功能本身不复杂但能让整个系统从“能用”变成“好用”。不过我要提醒一句这些都是加分项不是核心交付物时间紧的时候把基础流程跑顺是第一优先级。我做考勤系统也有几轮了每次带学生做这类项目都会强调同一件事毕设考的不是技术名词堆得多少而是你把一个业务闭环讲清楚的能力。把管理员后台配置好、把演示数据造好看、把“为什么这样设计”的几条主线想明白比塞一堆高大上框架靠谱得多。最后再分享一个实用小技巧交付源码分享型项目时README里把运行命令、默认账号、演示路径写清楚并附带一份源码阅读顺序笔记接收方第一印象会大好这也是“程序文档代码讲解”这条龙服务里最容易被忽略但最值钱的一部分。如果你也在做类似的考勤系统碰到的问题欢迎在评论区交流我会尽量回。
阅读完成 · 觉得有帮助?