From ba909c0f41c49a68496e8e0acb20c27afd9cc11c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BD=AD=E6=A3=AE?= Date: Mon, 17 Jun 2024 00:31:49 +0800 Subject: [PATCH] =?UTF-8?q?commit=20=E4=B8=8A=E4=BC=A0excel-=E4=BA=BA?= =?UTF-8?q?=E5=91=98=E5=9F=BA=E6=9C=AC=E4=BF=A1=E6=81=AF=E9=83=A8=E5=88=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- application/fac_mgnt/models.py | 2 +- application/fac_mgnt/views.py | 15 ++-- application/hrm_mgnt/views.py | 7 +- application/pjt_mgnt/views.py | 2 - common/urls.py | 1 + common/views.py | 81 ++++++++++++------ ...力资源管理-人员基本信息-Excel上传模板.xlsx | Bin 10147 -> 9974 bytes 7 files changed, 64 insertions(+), 44 deletions(-) diff --git a/application/fac_mgnt/models.py b/application/fac_mgnt/models.py index b537893..fe9a631 100644 --- a/application/fac_mgnt/models.py +++ b/application/fac_mgnt/models.py @@ -19,7 +19,7 @@ class ExpenseType(models.Model): # 费用明细表 class ExpenseDetail(models.Model): detail_id = models.AutoField(primary_key=True, verbose_name='明细ID') - type_id = models.ForeignKey(ExpenseType, on_delete=models.CASCADE, verbose_name='费用类型ID') + type_id = models.ForeignKey(ExpenseType, on_delete=models.CASCADE, verbose_name='费用类型名称') expense_detail = models.CharField(max_length=255, unique=True, verbose_name='费用明细') class Meta: diff --git a/application/fac_mgnt/views.py b/application/fac_mgnt/views.py index 720726a..1e74483 100644 --- a/application/fac_mgnt/views.py +++ b/application/fac_mgnt/views.py @@ -9,6 +9,7 @@ from django.urls import reverse from django.views.decorators.csrf import csrf_protect from django.views.decorators.http import require_POST, require_http_methods +from application import pjt_mgnt from application.fac_mgnt.forms import * from application.fac_mgnt.models import * from application.hrm_mgnt.models import PerformanceEvaluation @@ -53,9 +54,7 @@ def exp_type_list_view(request): "parse_url": reverse("common_excel_parse"), "save_url": reverse("save_excel_table_data"), "fields_preview_config": { - "type_id": {"type": "text", "width": "180px"}, "expense_type": {"type": "text", "width": "180px"}, - "actions": {"type": "actions", "width": "100px"} } }, "query_params": query_params, @@ -152,8 +151,8 @@ def exp_detail_list_view(request): "table_exclude_field_name": ['detail_id'], "excel_upload_config": { "template_url": reverse("download_template", kwargs={'template_name': template_name}), - "parse_url": reverse("common_excel_parse_exp"), - "save_url": reverse("save_excel_table_data_exp"), + "parse_url": reverse("common_excel_parse"), + "save_url": reverse("save_excel_table_data"), "fields_preview_config": { "type_id": {"type": "text", "width": "180px"}, "expense_detail": {"type": "text", "width": "180px"}, @@ -280,8 +279,8 @@ def gpb_list_view(request): "table_exclude_field_name": ['budget_id'], "excel_upload_config": { "template_url": reverse("download_template", kwargs={'template_name': template_name}), - "parse_url": reverse("common_excel_parse_gab"), - "save_url": reverse("save_excel_table_data_gab"), + "parse_url": reverse("common_excel_parse"), + "save_url": reverse("save_excel_table_data"), "fields_preview_config": { "primary_department": {"type": "text", "width": "180px"}, "year": {"type": "number", "width": "100px"}, @@ -2184,8 +2183,8 @@ def common_excel_parse_pjt(request): project_name = instance_data.get('project_name') if project_name: try: - project_instance = ProjectLedger.objects.get(project_name=project_name) - instance_data['project_id'] = project_instance + project_instance = pjt_mgnt.models.ProjectLedger.objects.get(project_name=project_name) + instance_data['project'] = project_instance except ProjectLedger.DoesNotExist: return JsonResponse({'error': f'找不到名称为 {project_name} 的项目台账记录。'}, status=400) diff --git a/application/hrm_mgnt/views.py b/application/hrm_mgnt/views.py index d4a8d85..db8be7c 100644 --- a/application/hrm_mgnt/views.py +++ b/application/hrm_mgnt/views.py @@ -109,14 +109,13 @@ def emp_list_view(request): "political_affiliation": {"type": "text", "width": "120px"}, "entry_date": {"type": "date", "width": "110px"}, "regularization_date": {"type": "date", "width": "110px"}, - "departure_date": {"type": "date", "width": "110px"}, "employment_type": {"type": "text", "width": "100px"}, "status": {"type": "text", "width": "80px"}, "primary_department": {"type": "text", "width": "180px"}, "secondary_department": {"type": "text", "width": "180px"}, "position": {"type": "text", "width": "180px"}, - "grade": {"type": "text", "width": "120px"}, - "contract_end_date": {"type": "date", "width": "110px"}, + "rank": {"type": "text", "width": "180px"}, + "contract_end_date": {"type": "text", "width": "180px"}, "mobile_number": {"type": "text", "width": "150px"}, "email": {"type": "text", "width": "200px"}, "mailing_address": {"type": "text", "width": "280px"}, @@ -131,8 +130,6 @@ def emp_list_view(request): "base_salary": {"type": "text", "width": "120px"}, "salary_account_number": {"type": "text", "width": "220px"}, "bank_of_salary_account": {"type": "text", "width": "220px"}, - "resignation_type": {"type": "text", "width": "120px"}, - "resignation_reason": {"type": "textarea", "width": "300px"} } }, # 上下文查询参数 diff --git a/application/pjt_mgnt/views.py b/application/pjt_mgnt/views.py index eb632ce..d461cc1 100644 --- a/application/pjt_mgnt/views.py +++ b/application/pjt_mgnt/views.py @@ -59,7 +59,6 @@ def proj_ledger_list_view(request): "parse_url": reverse("common_excel_parse"), "save_url": reverse("save_excel_table_data"), "fields_preview_config": { - "project_id": {"type": "text", "width": "180px"}, "project_name": {"type": "text", "width": "180px"}, "start_date": {"type": "date", "width": "180px"}, "end_date": {"type": "date", "width": "180px"}, @@ -88,7 +87,6 @@ def proj_ledger_list_view(request): "actual_net_income": {"type": "number", "width": "180px"}, "outstanding_net_income": {"type": "number", "width": "180px"}, "notes": {"type": "text", "width": "180px"}, - "actions": {"type": "actions", "width": "100px"} } }, "query_params": query_params, diff --git a/common/urls.py b/common/urls.py index c931fb9..fbc5718 100644 --- a/common/urls.py +++ b/common/urls.py @@ -1,6 +1,7 @@ from django.urls import path from common.views import * + urlpatterns = [ path('error_page/', error_page, name='error_page'), path('download_excel_template//', download_template, name='download_template'), diff --git a/common/views.py b/common/views.py index 579623b..ccde708 100644 --- a/common/views.py +++ b/common/views.py @@ -22,6 +22,7 @@ from application.org_mgnt.models import SecondaryDepartment def error_page(request): return render(request, 'error_page.html') + @login_required def download_template(request, template_name): """ @@ -164,34 +165,45 @@ def common_excel_parse(request): fields_map = dict(zip(model_fields, header_row)) fields_map_nf = dict(zip(header_row, model_fields)) - # 检查表头是否与模型字段名对应 - # if not set(header_row).issubset(model_verbose_name): - # return JsonResponse({'error': '表头不匹配,请使用正确的Excel上传模板。'}, status=400) - # 创建一个映射,将Excel表头映射到模型字段名 header_to_field_map = {header: fields_map_nf[header] for header in header_row} header_fields = [header_to_field_map[header] for header in header_row] + # 动态处理外键关系 + def get_related_instance(model, field_name, value): + field = model._meta.get_field(field_name) + if field.is_relation: + related_model = field.related_model + related_field_name_list = related_model._meta.fields # 获取关联模型的第一个字段名 + for related_field_name in related_field_name_list: + try: + related_instance = related_model.objects.get(**{related_field_name.name: value}) + value = related_instance + return value + except Exception: + continue + return None + else: + return value + for row in sheet.iter_rows(min_row=2, values_only=True): if not all(value is None for value in row): - # 使用映射来确保每个Excel单元格的数据对应到正确的模型字段 instance_data = {header_to_field_map[header]: value for header, value in zip(header_row, row)} + preview_data = instance_data.copy() + for field_name, value in instance_data.items(): + instance_data[field_name] = get_related_instance(model, field_name, value) + instance = model(**instance_data) try: instance.full_clean() - data.append(instance) + data.append(preview_data) except ValidationError as e: return JsonResponse({'error': f'数据校验错误: {e.message_dict}'}, status=400) - # 动态获取序列化器 - serializer_class = create_dynamic_serializer(model, include=header_fields) - - serializer = serializer_class(data, many=True) - # 清理,删除上传的文件 os.remove(file_path) - return JsonResponse({"table_data": serializer.data, "fields_map": fields_map}, safe=False) + return JsonResponse({"table_data": data, "fields_map": fields_map}, safe=False) except Exception as e: # 清理,删除上传的文件 os.remove(file_path) @@ -219,34 +231,47 @@ def save_excel_table_data(request): except (ValueError, LookupError): return JsonResponse({'error': '无效的 model_config'}, status=400) - # 创建模型实例列表 instances = [] for row_data in table_data: + instance_data = {} + for field_name, value in row_data.items(): + field = Model._meta.get_field(field_name) + if field.is_relation: + related_model = field.related_model + related_field_name_list = related_model._meta.fields + for related_field_name in related_field_name_list: + try: + related_instance = related_model.objects.get(**{related_field_name.name: value}) + value = related_instance + instance_data[field_name] = value + break + except Exception: + continue + else: + instance_data[field_name] = value + instance = Model(**instance_data) try: - instance = Model(**row_data) - instance.full_clean() # 验证数据 + instance.full_clean() instances.append(instance) except ValidationError as e: return JsonResponse({'error': f'数据校验错误: {e.message_dict}'}, status=400) - except Exception as e: - return JsonResponse({'error': f'创建实例时出错: {str(e)}'}, status=500) - # 批量创建模型实例 - try: - Model.objects.bulk_create(instances) - except Exception as e: - return JsonResponse({'error': f'批量保存数据时出错: {str(e)}'}, status=500) + # 批量保存数据 + Model.objects.bulk_create(instances) - return JsonResponse({'message': '表格数据保存成功'}, status=200) + return JsonResponse({'success': '数据保存成功'}, status=201) except json.JSONDecodeError: - return JsonResponse({'error': '无效的JSON格式'}, status=400) + return JsonResponse({'error': '无效的JSON数据'}, status=400) except Exception as e: - return JsonResponse({'error': f'服务器内部错误: {str(e)}'}, status=500) - return JsonResponse({'error': '无效的请求方法'}, status=400) + return JsonResponse({'error': f'保存数据时出错: {str(e)}'}, status=500) + + return JsonResponse({'error': '请求错误'}, status=400) @login_required def load_secondary_departments(request): primary_department_id = request.GET.get('primary_department_id') - secondary_departments = SecondaryDepartment.objects.filter(primary_department_id=primary_department_id).order_by('secondary_department_name') - return JsonResponse(list(secondary_departments.values('secondary_department_id', 'secondary_department_name')), safe=False) + secondary_departments = SecondaryDepartment.objects.filter(primary_department_id=primary_department_id).order_by( + 'secondary_department_name') + return JsonResponse(list(secondary_departments.values('secondary_department_id', 'secondary_department_name')), + safe=False) diff --git a/static/excels/人力资源管理-人员基本信息-Excel上传模板.xlsx b/static/excels/人力资源管理-人员基本信息-Excel上传模板.xlsx index 3ccfa1e59b0bf19fdae0b5c5fca02ce0888bf54d..3965c6ed67e5a1464cd916740b980e9886cd6f1f 100644 GIT binary patch delta 6018 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 delta 6183 zcmZ8lWl$VSvt8UZi@SSp3GN|Sa0x8#9^4mq2(Y+o@IcUD0TvGg4ess)T^wHSz2AGc zzBfNko$2a2Gd0y+Q+;46#qSgO(bUC4&^X}$0HGvt60npa0ylB^O^6rjLQ5tg9!n-r zsy|+@uI*DMc(Gd<|KbI^#`@!y^cnJ(j`)y4E?xv-ZQWC&3rfy4I@j+NY_pQGN1!pL ztvFtn0ixL&*T7CuGK!B8C7?pK-_&~slH$4#ey(@{K?;P4rRb4wj9-Ok;@Zrr*^@IZ zF_c|>odu^Pd}Q#~|A4@>x;(S4pxkh*)*aDJpcV(!rTI-}jrG*hc=maxH+x5b-h|JRj@s!winR3q#OS0>%t>>VSYG(6cWQIvi2>}rZ002+{k*b@LK2b&NN`GF`2$Bm& z0#24v?d8IiyHQxCxe)&fNztgLPX&0$l`3+;e{DFnB})=Yb5w96!tQPrLQ|cJ(5f>z zxC5^~lDx^J`T_MralBCc%#4Y8A$g&=?V4UR_!F8>Gp@&o#KwxoV%J^ZbwgqbeUH$D>S(NdW|0@EQ^OG8y{MYdwgZF;EMRipRTa=_8!GEYC9T_W#uG=|j7 z8dK$?J#z@@rFSxgoEefXT1&9JKVNyW3a*cmeU+~q;MznZ$cN2B!+EmY@GJl_X8tt& zB89o7;;q7@N%~X1?-litYyL~rG1-5=b9Yk`IV%Jnfd!0fC6Q_1#`Z3z71$RZ(qw=( zT!Lj`1syH<(C6i)ShER@e~q)ZxAidoPPf+V3fnMSY8Vrj3gOd;jz{RS<+^P{1ov5- zqs3gD;sqvoD_qs%J!`;(-R2_pvNtx33Nma~RVhrleXof7yMa>6K`LRqNAW7n%#F+l z+ss&qQY@HxfX2?+JT+*V2s_7TB=m``Uh`yl%t3H)_+a;d>2d!WiVZ?utt_an1fV3& zzC%M}L{iqnmm)V;#`y3Bl6_C%Jl$qkEH5{3lFwv&OOMh;ng?WBa{PZN25%yqq7CL);gw*9zQ^#Pd-Cxi1c>WvJF z?>dM>O=4+Zo{u&@;jm1GwwI~fNaB zj@TtJ1@Ep*?&V|gWKlMd&@2NYGq8S=?7drS&Xl0Tvm#Vcz~p$9C7;o2Dcq!=j4wI{ z28K6J+j*RzqI_kcxIbW}bmu|yua|l9ku`g*qJeLQecqs-$hG4sY&7hgvx#|Mxycg# zjn05DupAW2iT;?vBfOFJp3Wdb=l4!pr}lyMHh3N)oR1qTTwF9(eFK*ff#mylB`3{x51ZKm99aKc@OUD2yJShC$5~*x6 z#1IKLtXJXS;(rwv*0*PY*=j~gi;}=BMhF!vw?n%$q-Os*9{Q?Y1?plWj?C|uy0(2z z1TM$P*YRtZK8o>Mh|NwNIUH#Bfu|@L$WT5L-CpAJCyBdruyM+)JZ1SwnQS7wg{y6# zch48L*4u)IL|dam2QA~VxLZot@f?X%xM;w=AS($RtNiv()!cL&DDN^BL(Sf(FRsJq zia{qtB;OKO4kGTkW28g9&-&tlPFLUy0Z(4X;j+^t3joop@^-F%4928Pn61wuCjdVx z;1bNtYMXnBv6QGqOHcN&)i{~VXjd21F~aidLkRqSmQ$l#;JK&Kd5>JV|3ZzC#v?e? z|2ga;=82MJsWoEp&C=)%D~YVB*l|KuP*EMF=i8ms24&Pz9S9TNlf9bT^ldd#UJ>oSBg zmt#LO=`{PJ?&9d4uTx=ZWRaX{Ag)-T5!O1Cunxh^5#>pxnJ#P42((yJTeO_5O}oGM9^|Wy)PZs! zDo_vVdufSLNprK(E!B@Q|puqR#j!e(PKM5IDKtmXR3_*^LfZ2x1 znm<%a74-zHQcv!GPfX?^dRy$L=k<}VjgyR?bmcecU+@>B`oqAWT2p*sQlnZ^Qejf# zT3cGXpiVQ^G^UPXJQyisaTN_L=0Fejk$=F5n}&@PtA37)0Tf?9pR*5{0+}_N25TCi zwr<9ou88!R0<6DE@c9dWGM#iW@u?CZd1Mc6dEqiy=5ryQIyQ>hGH=xzvuP$`a{K6j^q4BciH?#9I_dXC=NGiz@44yoi5c{p0nZOy^#^-vD{Jj zmw~IUfm=kguyJi}qsNOS3GhqDN1wpJJ6P{P*&GRy?| zxbA+~EZclK1K=Z7*)%|&p8geaV%kjTj%ccZkWY_o@-)3f5xM6VpO45dl(^nnP3*Yu znRgE1J^kw)1#5wRKl;~R6@%dtDH~p)H6w#Z*~h4x&;bBd4FCY^&sXd1!0G1h<6!Cb z+S}1##_+^>S%Kswq3^|{H3TQB&%XACA%T|as-t~F;c?ll% z9$4-0+;08hC_*e0*i~_O#Gw84fuif^>L&Zp%lU)7hc-#rNnp##eL#B! zUd7VN-KN~{q#x(cgncr+PuAb|_BMeNep58dSdV|qJrrUvv`=mN@g<5U!hgG#Wpc*CajE=BRTP=@Y^7uy2sKV&^1hrU1|5WZN;cmdJXo z9t&t|>ZT6ur>!CJJF%>(eeu8ikvhe(!}{jAhgAIGUPMNsqmA&Xm}B)@Y54~*JNhL* z^bGLzbmyFi< zWzKVV{x>~5$Aw$w>s-99e*GIoBo1q$d{bgf((4hdB}~(J!jfI36C~bnZMMsnxbPzl zjD*qhk#Ho6*O(@TJKvmmz>g8=1fYe*unI{VlmHATw&UB~bDFq@doFt#kSbkDX3b3~ z?5op6R`Ms4^>)>Co3Cro;u;W!D@p%P%Jz+`c32YDbSNvuq^-h6$B)t>+IOWhv;wcC zb*g4;*cH-Ddw!c)fNB%h`iJbay=rDp%`D(}>|!BJ?JF6A*!nt!-~gKhm-l_lg))ry zrPm*dC(Dm!d^=aUM(#IvbR`3e4V|^H)Qb6>%T}3r?4GmoX-u)duKq$MLv(ih~ zvvZceNXG|T(Sun`jV5pbX)bA`e{bY3jCn`hr7<-3#U%EjzWLDEx^>$?e@FZ2CO;Zw zdIam2phui18J?P}-ie#_f}_Tpr@*Cf8IDa_4X89tHJZT88DREm^Ev^>b+zCcDL>D9 zn(0`9trb4{xxlL0(rRfa17i`INA5(novsCLP>%h~!gHL(I8^VHMOzzh62w0B6-TW7 z2(yzuaX5z>U7|;}nT(T}_TZ8+(=(I5L5r77p8vwf=+aCCQHaO+TsyjhMizJ{9fj%| zfvav95&szNYea@8Cr*YZrAmg^U$r=Y9a;A!e?3VKpF5=_T{+8Eg!dhNn$^^H6Mr$d zYiLK0kX>);8d+h4=pcP6kM`=uAv=7x;g?A%0;~F<=L%Vgg@0{&3LV6uLFlkB0H=;F zyjm7gL$HlYxmDj=jwr{pE*Lyi@&K$aPl0U|`Z>UP{SXmGfO~YvCR7RxT=9TCI{E0KX`6B-Q^c4Fr8+_N(aN&i zjmH(sW7=pBj4!?r_rEO3IFDnqx822hQPqq0juTF_CJvNj*Q5|j)Xr*qN z)8zi7F{R0eLV5`3^$qms5f&!E457BC-}?oB{GtjS7WPr^D*6emmq;NlCRG-Mgl9??W6@Rq75^+lg? zalyU;ZV&DFpo`*1;v|lzhVTi=u-FG5sJ^fAO+<+;qGfe+8R`@g$#fV82g>7fXM+#R z>w8S4Qf2$GuwyGqq&j}MQdWTJkY?~xF*JmAB$dh;{_wYk8Drh?B=2ne3dIb&OSz<( zQ__gjr9EC(r%AWIPDv|JPL4)5@If>rY`!8UPVcV~Q&8HR+uZvD1QYe&no!<$`~iFY z<;BnohT{}tBtya$*m@LE3(SNg*1(VJ^&dz~+u59@1fKJd6M-B5iM#Zr8~O2d>{8Zr z7x&*BhR?317U%in<>}>pb;Y5Ht0;bFBDzw%L=wgj026pV4xeHP+$1Gg{;c`aokhVK zkrNK2$y0=d$o+f2$H;$xK`wc+*a9@q?y`60G6N>d$LuQ^dH!n5KLv<>3H07!{vGQqYc|; zP?a%m&nzLKS@~{!Z^|AYr}>5<0AilZqk%J-s1BwpE38FQo_yR}y$jvjGM0$hN#rIk zix#)z+K8C6&dI#+2fUuiVvFBn;zk2Ci{FkX!)WO-S?J~S za~V(M_=q+5a@Ot^V$0s+)Wg&;EO2g|%bq+H zIP=)O!f3;R@kc7q>o2m7dc9Xqo-nQV4j<>6lnHgeb)~Kl8D4`Wi(bylqr>i;d#olnI^xJLb6q5CjsriM~?eE%%IFB-=)@* z$P_uvok8a$uS$~~c;c2%P;d+K87=TpHzIgi3*6BTvOhllFz}(X5oF9e;%Ql&1JWv~L2)GP7)vH`aD*Lh{v_CnbrO~&&7yS= z)rvU+p6%7>5(AUtH`R(QZ=c5JFhnXqoIlvH(yqb#=BZOPSEQt#?6++rzGr8E_6P2n zyyQz(zDV|PVf42IX+55T?AmoA(aYQ;&>b}KCwboPp8_LM z)1kKM$V3T#+|4jx&nWG8qRt~Zr%}AyR4@;5YY)$}uo4@JpS9j>p<#FyRulTXjPL37 z`v~wz`?rHyTj?uUTuyF-JzW)^p@+@kM7^H;0A;gH1a@l%%J=b2ljg{5CSW%VDc6L1 zi*d|S$mXkz%RL25yp9#5)PW#+3mhe#OsDgoH)R?slu2N!B7L!D&X_rXpN;WiUnZZh z!sRq-YJP8HS+;V<1EW|jS39X5hT$0K&C}*ZDP)i#A+e-vxe#LtCh%+Hw8kW^4+M?0k%e?Y*}K?tZKs+@QsJeG z+geGYVQ#q>2_5j1h((T@X*&16@|RxAPAWleq?tZ1Au#0n_p6`_|8QLun=(wLaVJX8 zgSm8n$Mt1TZJudLZ=RHK=JTa%)S-eqs`BUBwdqy)Ht8YMRvvVS^ARG_+3n5Ns}1(# z3_=r|^m3RJ#B|kY(YVHI42d}NI_5C~mpZ;+N zdx^sFeb2^|ltamgU;DfH;Y9ZI#ERi%i#u1*$a%{`EVpa*WOnT)+j;BNetrb4m|Lg8 z*7oGBz5JhfleD3iQW6pF6oN=6iKs&b0696715p3LFbESpJ<>lWr5Zf}+%v?AUJ^zBkNx&%c7+%*l0$~+vEhrTAq({U zaKjKB1`hIn`yBxI%TNAm{Ub&}2GL_+B>(?Y@V^OSxR5M{H}GC)kQoMkxI733BP03$ ul?4I-fb+N5|H>IsKvWn-;Cvy`j5K8bx`F@z@qeX#hV(PCAxF^t75pFGQGX@?