
阅读提示感谢你阅读本章。本文所有代码和注释均来自真实项目为了能让更多认真学习的读者持续获得优质内容本文将在7天后转为“粉丝可见”。如果你觉得本文有价值欢迎收藏并关注我本系列博客将继续保持同样详细的注释风格。感谢理解与支持一、业务场景上一章我们完成了员工信息模型60字段。现在需要支持批量导入企业一次可能录入数百名员工手工逐条录入不现实。导入功能要满足提供标准 Excel 模板带颜色标注、填写说明。数据验证区分不合格必填字段为空、日期错误、部门无权限和警告可选字段为空、手机号不规范等。不合格的行不能导入警告行可导入。根据员工编码判断是更新还是新建存在则更新不存在则新建。更新时Excel 中为空的字段不覆盖数据库中已有的值防止清空已有数据。每行数据独立事务一行失败不影响其他行。与导出格式完全兼容导出的 Excel 文件稍作修改例如调整某列内容即可原样导回。二、导入功能整体流程用户点击“数据导入”菜单 ↓ 【第一步】下载模板或直接上传Excel ↓ 【第二步】上传 Excel → 验证数据validators.py ↓ 展示验证结果合格/警告/不合格 【第三步】确认导入只有无不合格行时允许 ↓ 【第四步】执行导入core.py逐行事务 ↓ 【第五步】显示导入结果成功/失败统计Admin 入口通过 DataImport 代理模型注册重写 changelist_view 实现自定义界面。三、模板生成器template.py模板生成器负责生成带格式的 Excel 模板包含两个 Sheet· 数据导入33列第1行表头第2行示例数据第3行起由用户填写。· 填写说明说明必填字段、日期格式、导入规则等。3.1 关键代码逐行注释# employee_info/template.pyimportpandasaspdfromioimportBytesIOfromdjango.httpimportHttpResponsefromurllib.parseimportquoteimportopenpyxlfromopenpyxl.stylesimportFont,PatternFill,Alignment,Border,Sidefromopenpyxl.utilsimportget_column_letterdefdownload_enhanced_template()-HttpResponse:生成并下载增强版Excel模板精简版# ---------- 1. 定义模板数据33列----------template_data{# 核心必填字段3个- 红色标注员工编码:[EMP001,※必填※ 员工唯一编码],员工姓名:[张三,※必填※ 员工姓名],单位ID:[1,※必填※ 部门ID整数],# 个人基本信息8个身份证号码:[110101199001011234,15位或18位身份证号码],民族:[汉族,可选汉族/回族/蒙古族/...],性别:[男,可选男/女/其他],出生日期:[1990-05-23,※格式需正确※ 支持yyyy-MM-dd, yyyy/MM/dd, yyyyMMdd],籍贯:[北京,籍贯],婚姻状况:[未婚,可选未婚/已婚/离异/丧偶/其他],联系电话:[13800138000,手机号或座机号],常住地址:[北京市朝阳区,常住地址],# ... 其他字段共33列此处省略}# 创建 DataFrame两行数据表头 示例dfpd.DataFrame(template_data)# df 只有一行示例数据# ---------- 2. 写入 Excel 并设置样式 ----------outputBytesIO()withpd.ExcelWriter(output,engineopenpyxl)aswriter:# 写入数据到 Sheet1数据导入df.to_excel(writer,sheet_name数据导入,indexFalse)workbookwriter.book worksheetwriter.sheets[数据导入]# 设置列宽略# 设置表头样式深蓝色背景、白色加粗字体、居中header_fillPatternFill(start_color366092,end_color366092,fill_typesolid)header_fontFont(colorFFFFFF,boldTrue)forcellinworksheet[1]:cell.fillheader_fill cell.fontheader_font cell.alignmentAlignment(horizontalcenter,verticalcenter)# 设置示例行第2行样式浅绿色背景、斜体example_fillPatternFill(start_colorE2EFDA,end_colorE2EFDA,fill_typesolid)forcellinworksheet[2]:cell.fillexample_fill cell.fontFont(italicTrue)# 必填字段A、B、C列字体标红required_columns[A,B,C]forcolinrequired_columns:worksheet[f{col}1].fontFont(colorFF0000,boldTrue)worksheet[f{col}2].fontFont(colorFF0000,boldTrue,italicTrue)# 日期字段列标蓝date_columns[H,L,M,S]# 出生日期、毕业时间、参工时间、加入组织时间forcolindate_columns:worksheet[f{col}1].fontFont(color0070C0,boldTrue)worksheet[f{col}2].fontFont(color0070C0,italicTrue)# 添加边框thin_borderBorder(leftSide(stylethin),rightSide(stylethin),topSide(stylethin),bottomSide(stylethin))forrowinworksheet.iter_rows(min_row1,max_rowworksheet.max_row):forcellinrow:cell.borderthin_border# ---------- 3. 创建第二个 Sheet填写说明----------field_sheetworkbook.create_sheet(title填写说明)instructions[[员工数据导入模板 - 填写说明],[],[一、必填字段3个必须填写],[ 1. 员工编码员工唯一编码不可重复],[ 2. 员工姓名员工姓名],[ 3. 单位ID部门ID必须是有效的整数],[],[二、日期字段如果填写格式必须正确],[ 支持格式yyyy-MM-dd、yyyy/MM/dd、yyyyMMdd、yyyy年MM月dd日],[],[三、导入规则],[ 1. 员工编码存在 → 更新数据空值不覆盖现有值],[ 2. 员工编码不存在 → 新建员工],[ 3. 有不合格数据 → 必须修正后重新上传],[ 4. 只有警告数据 → 可以继续导入],[],[四、与导出的闭环, 导出的Excel文件稍作修改后可直接导入系统实现数据批量更新。]]# 写入说明内容并设置样式...# ---------- 4. 返回 HTTP 响应下载----------output.seek(0)responseHttpResponse(output.getvalue(),content_typeapplication/vnd.openxmlformats-officedocument.spreadsheetml.sheet)filename企业员工基础信息导入模板.xlsxresponse[Content-Disposition]fattachment; filename*UTF-8{quote(filename)}returnresponse亮点必填字段红色标注、日期字段蓝色标注、示例行浅绿背景、自带填写说明 Sheet。用户拿到模板即可直接填写。四、数据验证器validators.py验证器负责读取上传的 Excel逐行验证区分不合格error、警告warning、合格success。4.1 验证结果类 ValidationResultclassValidationResult:def__init__(self,row_num,employee_number):self.row_numrow_num# Excel 行号从2开始self.employee_numberemployee_number self.full_name# 员工姓名用于显示self.department_id# 单位IDself.statuspending# success / warning / errorself.critical_errors[]# 不合格错误必须修正self.warnings[]# 警告可忽略4.2 核心验证逻辑 validate_dataframedefvalidate_dataframe(self,df): 验证 DataFrame返回 (can_import, validation_results, clean_df) # 1. 检查必填列是否存在forcolin[员工编码,员工姓名,单位ID]:ifcolnotindf.columns:returnFalse,[错误结果],pd.DataFrame()# 2. 清理数据空值处理、日期规范化df_cleanself._clean_dataframe(df)# 3. 逐行验证foridx,rowindf_clean.iterrows():resultself._validate_single_row(idx2,row)# 2 转为 Excel 行号# 验证部门权限使用第8章的权限函数ifresult.department_id:dept_idint(result.department_id)ifself.viewable_dept_idsanddept_idnotinself.viewable_dept_ids:result.critical_errors.append(f单位ID{dept_id}不在您的部门权限范围内)# 根据 critical_errors 和 warnings 决定 statusifresult.critical_errors:result.statuserrorelifresult.warnings:result.statuswarningelse:result.statussuccessself.validation_results.append(result)can_importall(r.status!errorforrinself.validation_results)returncan_import,self.validation_results,df_clean4.3 日期规范化支持多种格式def_normalize_date_string(self,date_value):将各种日期格式统一转为 yyyy-MM-ddifpd.isna(date_value):returndate_strstr(date_value).strip()forfmtin[%Y-%m-%d,%Y/%m/%d,%Y%m%d,%Y年%m月%d日]:try:returndatetime.strptime(date_str,fmt).strftime(%Y-%m-%d)except:continuereturndate_str# 无法解析则原样返回后续会报错4.4 部门权限校验只能导入自己部门及下级的数据安全亮点导入时系统会严格检查 Excel 中填写的“单位ID”是否属于当前用户有权管理的部门范围。用户只能导入自己所在部门及其所有下级部门的员工数据不能导入同级部门、上级部门或其他无关部门的员工。实现原理· 验证器初始化时通过 get_viewable_departments(request.user) 获取当前用户有权查看的部门ID列表该函数返回用户自己部门及所有子孙部门的ID。· 对每一行数据在 _validate_single_row 中检查 department_id 是否在此列表中。如果不在则记录为“不合格”错误critical_errors该行数据将无法导入。安全价值· 防止用户越权导入其他部门的敏感数据确保数据归属正确。· 符合企业数据隔离要求每个管理员只能管理自己管辖范围内的员工。代码位置见 validate_dataframe 方法中的部门权限校验段落。五、导入器core.py5.1 整体结构classEnhancedImporter:def__init__(self,request):self.requestrequest self.userrequest.user self.results{total:0,created:0,updated:0,skipped:0,errors:[]}defimport_data(self,validated_data):# 遍历每行每行独立事务forrow_datainvalidated_data:withtransaction.atomic():self._import_single_row(row_data)returnself.results5.2 单行导入更新或新建def_import_single_row(self,row_data):employee_numberrow_data.get(employee_number)department_idrow_data.get(department_id)# 部门验证departmentDepartment.objects.get(iddepartment_id,is_activeTrue)# 根据员工编码查找try:employeeEmployeeBasicInfo.objects.get(employee_numberemployee_number)# 更新模式空值不覆盖returnself._update_employee(employee,row_data,department)exceptEmployeeBasicInfo.DoesNotExist:returnself._create_employee(row_data,department)5.3 更新逻辑空值不覆盖def_update_employee(self,employee,row_data,department):更新员工信息只有上传的字段非空时才更新has_changesFalseupdate_fields[]# 字段映射字典略forexcel_field,(model_field,converter)infield_mapping.items():new_valuerow_data.get(excel_field)ifnew_valuein(None,,NaN):# 空值跳过continueold_valuegetattr(employee,model_field)convertedconverter(new_value)ifold_value!converted:setattr(employee,model_field,converted)update_fields.append(model_field)has_changesTrue# 更新部门如果部门变化ifdepartmentandemployee.department_id!department.id:employee.departmentdepartment employee.department_namedepartment.name update_fields.extend([department,department_name])has_changesTrueifhas_changes:employee.updated_byself.user employee.save(update_fieldsupdate_fields)returnupdatedelse:returnskipped关键if new_value in (None, ‘’, ‘NaN’): continue 确保空值不会覆盖现有数据。这避免了用户误清空字段。六、Admin 集成DataImportAdmin通过代理模型 DataImport 注册重写 changelist_view 实现自定义界面。admin.register(DataImport)classDataImportAdmin(admin.ModelAdmin):defchangelist_view(self,request,extra_contextNone):# 权限检查ifnotrequest.user.has_perm(employee_info.add_employeebasicinfo):returnHttpResponseForbidden(没有导入权限)context{**self.admin_site.each_context(request),title:员工数据导入,opts:self.model._meta,}# 从 session 中取出验证结果如果有ifenhanced_validation_resultsinrequest.session:context.update(validation_data)returnrender(request,admin/employee_info/enhanced_import.html,context)defget_urls(self):urlssuper().get_urls()custom_urls[path(download-template/,self.download_template_view,namedownload_enhanced_template),path(validate/,self.validate_view,namevalidate_enhanced_data),path(confirm/,self.confirm_view,nameenhanced_import_confirm),path(execute/,self.execute_view,nameexecute_enhanced_import),]returncustom_urlsurls七、与导出的闭环-设计特色在导出模块第16章中generator.py 的 FIELD_DEFINITIONS 与 template.py 的列顺序完全一致。这意味着用户导出员工数据33列 Excel。用户在该 Excel 中修改部分字段比如调整岗位层级、联系方式。用户将修改后的 Excel 直接上传到导入功能。系统根据员工编码自动更新对应记录且空值不覆盖如果某个单元格被无意清空不会删除数据库中的现有值。闭环规则总结· 必须保留的列前3列员工编码、员工姓名、单位ID。如果删除这3列中的任何一列验证器会报错“缺少必要列”。· 其他30列均可删除例如导出时不需要“头像”列删掉不影响导入因为验证器只检查必填列和字段映射缺失的列会被视为空值而空值不会覆盖现有数据。· 列名必须与模板完全一致大小写、空格敏感。八、注意事项踩坑经验序号注意事项踩坑总结1空值不覆盖_update_employee 中检查 new_value in (None, ‘’, ‘NaN’) 跳过更新这是防止误清空的关键2部门权限导入前验证 viewable_dept_ids避免用户导入无权部门的员工3日期解析支持多种格式但建议统一 yyyy-MM-dd避免歧义4事务处理每行独立事务一行错误不会导致整个文件回滚5大文件性能目前逐行保存上千行数据可能较慢可考虑批量更新优化但鉴于企业员工数量通常不超过数千可接受6必填列检查验证器会检查列是否存在若不存在直接返回错误不给导入机会7特殊标记字段布尔字段支持“是/否、1/0、true/false”等多种表示转换函数 _convert_boolean 已做兼容九、小结本章完成了员工数据导入的全流程· 模板生成器带样式、填写说明· 三步流程上传→验证→确认· 验证器区分不合格/警告/合格含部门权限· 导入器逐行事务更新时空值不覆盖· Admin 入口集成· 导出→导入完美闭环列对齐、空值安全、唯一标识为员工编码下一章员工数据导出13个筛选条件、33列 Excel 生成、预览流程。互动问题在批量导入中你是否遇到过“意外清空已有字段”的问题我们采用的“空值不覆盖”策略是否解决了你的痛点欢迎评论区交流。