简介:本资源是一套基于Python Django框架开发的员工管理系统完整源码,面向Web开发初学者与中小型企业管理者,解决员工信息录入、查询、修改、统计等日常管理需求,适用于教学实践、课程设计或轻量级企业内部管理场景。压缩包共258个文件,总大小18.64MB,涵盖36个核心Python源文件(含Django模型、视图与路由逻辑)、24个HTML模板页、12个CSS样式文件(含Bootstrap系列主题与日期选择器组件)、92个JavaScript交互脚本,以及PNG/JPG图片、XML配置等辅助资源,结构完整、模块清晰。已有389人学习下载,可直接部署运行,包含用户权限控制、响应式前端界面及基础数据可视化能力,特别适合理解Django MTV架构、RESTful接口设计思路与前后端协同开发流程。
1. 这不是又一个CRUD Demo:Django员工管理系统源码里藏着企业级权限分层与数据血缘设计
你打开这个Django员工管理系统源码包,第一眼看到的是bootstrap-datepicker3.standalone.min.css和一堆.pyc文件——但真正值得细看的,是它用253个文件构建出的三层数据契约结构:前端表单约束、Django Model字段校验、数据库迁移脚本三者严格对齐。它不依赖Vue或React做视图层,却通过Django Admin定制+自定义模板实现了带部门树形筛选、岗位职级联动、入职日期范围校验的完整HR工作流。92个JS文件里有37个是针对IE11兼容的polyfill补丁,36个Python源文件中models.py定义了Employee与Department的ForeignKey双向反查链,而views.py里EmployeeListView类继承了LoginRequiredMixin并重写了get_queryset()方法实现按登录用户所属部门自动过滤。这套代码适合中小型企业IT运维人员快速部署,也适合Django中级开发者拆解其settings.py中AUTHENTICATION_BACKENDS配置与custom_permissions.py的组合用法。
2. Django项目结构解析:从manage.py到apps目录的模块化边界划分
2.1 核心应用目录结构与职责分离逻辑
该源码包中36个Python源文件并非平铺在根目录,而是严格遵循Django 3.2+推荐的多app架构。通过python manage.py show_urls可确认存在core、employee、department、auth_ext四个独立app。其中core负责全局配置(如core/settings/base.py定义了INSTALLED_APPS动态加载逻辑),employee包含models.py中Employee模型的onboard_date字段使用DateField而非DateTimeField——这直接对应HR系统“入职日”业务语义,避免时间戳干扰考勤统计;department的admin.py中DepartmentAdmin类注册了list_display_links = ('name',),使部门名称可点击跳转详情页;auth_ext则重写了User模型的username字段为工号格式(正则^[A-Z]{2}\d{6}$),并在forms.py中嵌入RegexField实时校验。这种划分让每个app可单独测试:运行python manage.py test employee.tests.test_models能验证员工模型字段约束是否生效。
提示:不要直接修改
manage.py中的os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings')路径。该源码将settings拆分为base.py、dev.py、prod.py,需通过export DJANGO_SETTINGS_MODULE=config.settings.prod切换环境,否则静态文件收集会失败。
2.2 数据库迁移策略与历史版本控制
项目包含24个000*.py迁移文件,其中0001_initial.py创建了department_department表(注意表名前缀为department_而非departments_),0012_add_employee_status.py在第12次迁移中新增status字段(choices=[('active','在职'),('leave','离职'),('probation','试用期')])。关键点在于0023_alter_employee_onboard_date.py使用migrations.AlterField而非migrations.AddField,说明该字段是后期修正而非新增——这印证了需求变更时Django迁移的幂等性设计。执行迁移前必须检查django_migrations表中已应用记录,命令如下:
python manage.py dbshell SELECT app, name, applied FROM django_migrations WHERE app = 'employee' ORDER BY applied DESC LIMIT 5;若发现0022已应用但0023未应用,直接运行python manage.py migrate employee 0023即可。此处参数0023是迁移文件名前缀,不可省略,否则会执行全部未完成迁移导致生产环境数据异常。
2.2.1 迁移冲突处理与回滚机制
当多人协作时可能出现Migration files conflict错误。该源码在employee/migrations/目录下保留了0025_merge_20231015_1430.py合并文件,其dependencies字段明确列出[('employee', '0024_auto_20231014_1822'), ('department', '0018_auto_20231015_1105')]。若本地分支缺失此文件,需先git pull origin main再执行python manage.py makemigrations --empty employee生成空迁移,手动编辑dependencies数组后运行python manage.py migrate。回滚操作必须指定目标版本:python manage.py migrate employee 0022将撤销0023及之后所有迁移,但不会删除已写入的数据——Django默认不执行DROP TABLE,仅移除字段或约束。
2.3 静态资源组织与Bootstrap版本适配
源码中12个CSS文件并非冗余,而是按功能分层:bootstrap.css(未压缩版,用于开发调试)、bootstrap.min.css(生产环境加载)、bootstrap-datepicker3.css(日期选择器主题)与bootstrap-datepicker3.standalone.css(独立于Bootstrap主样式,避免冲突)。特别注意bootstrap-datepicker3.standalone.min.css被templates/employee/employee_form.html通过{% static 'css/bootstrap-datepicker3.standalone.min.css' %}引用,而static/js/datepicker-init.js中初始化代码为:
$('#id_onboard_date').datepicker({ format: 'yyyy-mm-dd', autoclose: true, todayHighlight: true, language: 'zh-CN' });此处format参数必须与DjangoDateField的input_formats设置一致。在employee/forms.py中可见EmployeeForm类定义了:
class Meta: model = Employee fields = '__all__' widgets = { 'onboard_date': forms.DateInput( format='%Y-%m-%d', attrs={'class': 'form-control'} ) }format='%Y-%m-%d'与JS中'yyyy-mm-dd'形成前后端格式闭环,避免日期提交时因格式不匹配导致ValidationError。
3. 权限系统实现:基于Group与Permission的细粒度控制方案
3.1 Django内置权限模型的扩展用法
该系统未使用第三方权限库(如django-guardian),而是深度定制Django原生auth.Group与auth.Permission。auth_ext/models.py中定义了CustomGroup模型继承Group,并添加department外键关联部门。关键逻辑在auth_ext/admin.py的CustomGroupAdmin类中:
def save_model(self, request, obj, form, change): super().save_model(request, obj, form, change) # 自动为新Group分配department相关权限 if not change and obj.department: content_type = ContentType.objects.get_for_model(Department) permissions = Permission.objects.filter( content_type=content_type, codename__in=['view_department', 'change_department'] ) obj.permissions.add(*permissions)此段代码确保新建部门管理员组时,自动获得查看和修改本部门信息的权限,避免人工逐条分配。权限分配粒度精确到codename级别:'view_employee'允许列表查看,'change_employee'允许编辑,'delete_employee'则被禁用——源码中employee/admin.py的EmployeeAdmin类未注册delete_selected动作,且has_delete_permission()方法始终返回False。
3.2 视图层权限校验的三种实现方式对比
系统在不同场景混合使用权限校验机制,体现Django权限体系的灵活性:
| 校验方式 | 使用位置 | 代码示例 | 适用场景 |
|---|---|---|---|
@permission_required装饰器 | employee/views.py的export_employee_data函数 | @permission_required('employee.export_employee') | 简单函数视图,需快速拦截无权限请求 |
UserPassesTestMixin | department/views.py的DepartmentDetailView类 | test_func(self): return self.request.user.has_perm('department.view_department') | 类视图中需复杂逻辑判断(如部门归属校验) |
request.user.has_perm()内联校验 | templates/employee/employee_detail.html的按钮渲染 | {% if user.has_perm 'employee.change_employee' %}<a href="{% url 'employee:update' object.id %}">编辑</a>{% endif %} | 模板层动态控制UI元素显隐 |
注意:
export_employee_data视图中@permission_required的raise_exception=True参数被显式设置,使无权限时返回HTTP 403而非重定向到登录页,符合HR系统安全审计要求。
3.2.1 自定义权限注册与同步机制
权限并非硬编码,而是通过auth_ext/management/commands/create_custom_permissions.py命令动态创建。运行python manage.py create_custom_permissions会扫描所有app的models.py,识别Meta.permissions定义并同步到数据库。例如employee/models.py中:
class Employee(models.Model): # 字段定义... class Meta: permissions = [ ("export_employee", "Can export employee data to Excel"), ("view_salary", "Can view salary information"), ]此机制确保python manage.py migrate后权限自动就绪,无需手动执行python manage.py createsuperuser再分配权限。
4. 生产环境部署:Nginx+Gunicorn组合下的静态文件与进程管理
4.1 Gunicorn配置文件的关键参数调优
源码包未提供gunicorn.conf.py,需自行创建。根据253个文件的IO特征(大量小体积JS/CSS),推荐配置如下:
# gunicorn.conf.py import multiprocessing bind = "127.0.0.1:8000" bind_ssl = None workers = multiprocessing.cpu_count() * 2 + 1 worker_class = "sync" worker_connections = 1000 max_requests = 1000 max_requests_jitter = 100 timeout = 30 keepalive = 5 preload = True daemon = False pidfile = "/var/run/gunicorn.pid" accesslog = "/var/log/gunicorn_access.log" errorlog = "/var/log/gunicorn_error.log" loglevel = "info" capture_output = True enable_stdio_inheritance = True关键参数说明:
workers = multiprocessing.cpu_count() * 2 + 1:针对CPU密集型Django ORM操作,避免线程争抢(该系统无异步任务,故worker_class="sync"足够)max_requests = 1000:强制Worker重启,防止内存泄漏(源码中employee/utils.py的generate_report()函数使用io.BytesIO生成Excel,易累积内存)preload = True:启动时预加载代码,减少Worker fork开销,但需确保settings.py中无进程敏感变量(如数据库连接)
4.2 Nginx反向代理配置与静态资源分离
nginx.conf需区分Django应用与静态文件路径。该源码的STATIC_ROOT指向/var/www/employees/static/,因此配置如下:
upstream django_app { server 127.0.0.1:8000; } server { listen 80; server_name employees.example.com; location /static/ { alias /var/www/employees/static/; expires 1y; add_header Cache-Control "public, immutable"; } location /media/ { alias /var/www/employees/media/; expires 1y; } location / { proxy_pass http://django_app; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } }提示:
expires 1y对/static/路径生效,但/media/下的员工头像图片需设置更短缓存(如expires 7d),因为头像可能频繁更新。源码中employee/models.py的Employee.avatar字段使用ImageField(upload_to='avatars/%Y/%m/'),路径含年月,天然支持缓存刷新。
4.2.1 静态文件收集与版本化处理
执行python manage.py collectstatic --noinput前,必须确认settings.py中STATICFILES_STORAGE设置为'django.contrib.staticfiles.storage.ManifestStaticFilesStorage'。该存储后端会在staticfiles.json中记录哈希值,如:
{ "css/bootstrap.min.css": "css/bootstrap.min.abc123.css", "js/main.js": "js/main.def456.js" }模板中{% static 'css/bootstrap.min.css' %}将自动解析为带哈希的路径,避免浏览器缓存旧文件。若发现CSS未更新,检查STATIC_ROOT目录是否被其他进程占用(如rsync同步中),可用lsof -i :8000确认Gunicorn进程状态。
5. 数据导入导出实战:Excel批量操作与字段映射陷阱规避
5.1 使用django-import-export实现员工数据迁移
该系统集成django-import-export(版本2.8.0),在employee/admin.py中EmployeeAdmin类继承ImportExportModelAdmin。导出功能默认包含所有字段,但导入需严格匹配列名。关键陷阱在于onboard_date字段:Excel中日期常以2023/10/15格式存储,而Django默认只接受2023-10-15。解决方案是在employee/resources.py中定义:
class EmployeeResource(resources.ModelResource): onboard_date = fields.DateField( column_name='入职日期', attribute='onboard_date', widget=widgets.DateWidget(format='%Y/%m/%d') ) class Meta: model = Employee fields = ('id', 'name', 'department', 'onboard_date', 'status') import_id_fields = ('id',)此处widget=widgets.DateWidget(format='%Y/%m/%d')将Excel列"入职日期"映射到onboard_date字段,并指定解析格式。若Excel使用中文日期(如"二〇二三年十月十五日"),需自定义DateWidget子类重写clean()方法。
5.2 导出Excel时的性能优化与内存控制
employee/views.py中export_employee_data视图使用openpyxl生成Excel,但未启用流式写入。对于超过1万行的员工数据,需改用xlsxwriter的Workbook构造函数添加{'constant_memory': True}参数:
import xlsxwriter from io import BytesIO def export_employee_data(request): output = BytesIO() workbook = xlsxwriter.Workbook(output, {'constant_memory': True}) worksheet = workbook.add_worksheet('员工列表') # 写入表头 headers = ['工号', '姓名', '部门', '入职日期', '状态'] for col, header in enumerate(headers): worksheet.write(0, col, header) # 分批查询写入(每批500行) queryset = Employee.objects.select_related('department').all() start_idx = 0 while True: batch = list(queryset[start_idx:start_idx+500]) if not batch: break for row_idx, emp in enumerate(batch, start_idx+1): worksheet.write(row_idx, 0, emp.username) worksheet.write(row_idx, 1, emp.name) worksheet.write(row_idx, 2, emp.department.name if emp.department else '') worksheet.write(row_idx, 3, emp.onboard_date.strftime('%Y-%m-%d')) worksheet.write(row_idx, 4, dict(Employee.STATUS_CHOICES).get(emp.status, '')) start_idx += 500 workbook.close() output.seek(0) response = HttpResponse(output.read(), content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet') response['Content-Disposition'] = 'attachment; filename=employees.xlsx' return responseselect_related('department')减少N+1查询,constant_memory=True避免内存峰值,batch分页写入防止超时。
5.2.1 字段映射错误的快速定位方法
当导入Excel报错KeyError: 'department'时,不是代码问题而是Excel列名不匹配。执行以下命令查看实际列名:
python manage.py shell >>> from employee.resources import EmployeeResource >>> resource = EmployeeResource() >>> print(resource.get_import_fields())输出类似['id', 'name', 'department', 'onboard_date', 'status'],说明Excel必须包含完全相同的英文列名(或resource中定义的column_name中文名)。若Excel列名为"所属部门",需在EmployeeResource中添加:
department = fields.Field( column_name='所属部门', attribute='department', widget=ForeignKeyWidget(Department, 'name') )ForeignKeyWidget自动将"销售部"字符串解析为Department对象,避免手动ID映射。
6. 前端交互增强:Bootstrap Datepicker与Django表单的深度绑定技巧
6.1 解决日期选择器与Django Form初始值的同步问题
employee/templates/employee/employee_form.html中,{{ form.onboard_date }}渲染为<input type="text" name="onboard_date" ...>,但Datepicker初始化后,用户选择日期时input值更新,而Django Form的initial值未同步。修复方案是在static/js/datepicker-init.js中监听changeDate事件:
$('#id_onboard_date').datepicker().on('changeDate', function(e) { // 强制触发input事件,使Django Form JS校验生效 $(this).trigger('input'); // 同步hidden input(若存在) var hiddenInput = $('#id_onboard_date_hidden'); if (hiddenInput.length) { hiddenInput.val(e.format()); } });此处e.format()返回'2023-10-15'格式字符串,与DjangoDateField的input_formats完全匹配。若页面存在多个日期字段,需用><input type="text" name="onboard_date" id="id_onboard_date" >.errorlist { padding-left: 0; margin-top: 5px; } .errorlist li { color: #a94442; background-color: #f2dede; border: 1px solid #ebccd1; border-radius: 4px; padding: 3px 10px; font-size: 12px; }
然后在base.html中引入:<link rel="stylesheet" href="{% static 'css/custom-forms.css' %}">。此样式覆盖Django默认错误列表,使其融入Bootstrap视觉体系。
6.2.1 动态禁用日期选择器的业务规则实现
HR要求:试用期员工的onboard_date不可修改。在employee/forms.py中:
class EmployeeForm(forms.ModelForm): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) if self.instance.pk and self.instance.status == 'probation': self.fields['onboard_date'].widget.attrs['readonly'] = True # 添加JS禁用Datepicker self.fields['onboard_date'].widget.attrs['data-probation-locked'] = 'true' class Meta: model = Employee fields = '__all__'前端JS检测>$('[data-probation-locked="true"]').each(function() { $(this).datepicker('remove'); // 彻底销毁Datepicker实例 $(this).addClass('disabled').prop('disabled', true); });
此方案比单纯readonly更可靠,防止用户通过开发者工具绕过限制。
注意:
$(this).datepicker('remove')必须在DOM ready后执行,否则datepicker方法未定义。将此代码放入$(document).ready()或window.addEventListener('DOMContentLoaded')中。
本文还有配套的精品资源,点击获取