From d381be978bb7ed1054bfb1117f406ce150d87472 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9C=A8=E5=AD=90-=E6=9D=8E?= <1537080775@qq.com> Date: Tue, 25 Feb 2025 10:09:59 +0000 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E5=86=99=E5=BC=82=E6=AD=A5=E5=AF=BC?= =?UTF-8?q?=E5=87=BA=E5=8A=9F=E8=83=BD=EF=BC=9A=20=E6=89=80=E6=9C=89?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E5=A4=84=E7=90=86=E9=83=BD=E5=9C=A8celery?= =?UTF-8?q?=E4=B8=AD=E5=AE=8C=E6=88=90=EF=BC=8C=E5=87=8F=E5=B0=91=E5=90=8C?= =?UTF-8?q?=E6=AD=A5=E9=98=BB=E5=A1=9E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 木子-李 <1537080775@qq.com> --- backend/dvadmin/system/tasks.py | 99 +++++--------------- backend/dvadmin/utils/import_export_mixin.py | 76 +++++---------- backend/dvadmin/utils/request_util.py | 16 ++++ backend/requirements.txt | 3 +- 4 files changed, 63 insertions(+), 131 deletions(-) diff --git a/backend/dvadmin/system/tasks.py b/backend/dvadmin/system/tasks.py index 0245e63a..c72bb310 100644 --- a/backend/dvadmin/system/tasks.py +++ b/backend/dvadmin/system/tasks.py @@ -1,95 +1,42 @@ from hashlib import md5 from io import BytesIO -from datetime import datetime from time import sleep -from openpyxl import Workbook -from openpyxl.worksheet.table import Table, TableStyleInfo -from openpyxl.utils import get_column_letter +import pandas as pd from django.core.files.base import ContentFile from application.celery import app from dvadmin.system.models import DownloadCenter +from dvadmin.utils.request_util import load_class -def is_number(num): - try: - float(num) - return True - except ValueError: - pass - - try: - import unicodedata - unicodedata.numeric(num) - return True - except (TypeError, ValueError): - pass - return False - -def get_string_len(string): - """ - 获取字符串最大长度 - :param string: - :return: - """ - length = 4 - if string is None: - return length - if is_number(string): - return length - for char in string: - length += 2.1 if ord(char) > 256 else 1 - return round(length, 1) if length <= 50 else 50 @app.task -def async_export_data(data: list, filename: str, dcid: int, export_field_label: dict): - instance = DownloadCenter.objects.get(pk=dcid) +def async_export_data(kwargs): + instance = DownloadCenter.objects.get(id=kwargs.get("id")) instance.task_status = 1 instance.save() sleep(2) try: - wb = Workbook() - ws = wb.active - header_data = ["序号", *export_field_label.values()] - hidden_header = ["#", *export_field_label.keys()] - df_len_max = [get_string_len(ele) for ele in header_data] - row = get_column_letter(len(export_field_label) + 1) - column = 1 - ws.append(header_data) - for index, results in enumerate(data): - results_list = [] - for h_index, h_item in enumerate(hidden_header): - for key, val in results.items(): - if key == h_item: - if val is None or val == "": - results_list.append("") - elif isinstance(val, datetime): - val = val.strftime("%Y-%m-%d %H:%M:%S") - results_list.append(val) - else: - results_list.append(val) - # 计算最大列宽度 - result_column_width = get_string_len(val) - if h_index != 0 and result_column_width > df_len_max[h_index]: - df_len_max[h_index] = result_column_width - ws.append([index + 1, *results_list]) - column += 1 - #  更新列宽 - for index, width in enumerate(df_len_max): - ws.column_dimensions[get_column_letter(index + 1)].width = width - tab = Table(displayName="Table", ref=f"A1:{row}{column}") # 名称管理器 - style = TableStyleInfo( - name="TableStyleLight11", - showFirstColumn=True, - showLastColumn=True, - showRowStripes=True, - showColumnStripes=True, - ) - tab.tableStyleInfo = style - ws.add_table(tab) + # 获取变量 + filename = kwargs.get('filename') + export_field_label = kwargs.get('export_field_label') + model_class = load_class(kwargs.get('model_class')) + export_serializer_class = load_class(kwargs.get('export_serializer_class')) + sql = kwargs.get('sql') + params = kwargs.get('params') + # 查询数据 + queryset = model_class.objects.raw(sql, params) + data = export_serializer_class(queryset, many=True).data + # 处理数据列 + success_df = pd.DataFrame(data) + success_df = success_df.rename(columns=export_field_label) + columns_to_keep = [i for i in export_field_label.values()] + success_df = success_df[columns_to_keep] + # 保存文件 stream = BytesIO() - wb.save(stream) - stream.seek(0) + success_df.to_excel(stream, index=False) + stream.seek(0) # 移动到文件开头 + # 保存数据库 s = md5() while True: chunk = stream.read(1024) diff --git a/backend/dvadmin/utils/import_export_mixin.py b/backend/dvadmin/utils/import_export_mixin.py index e188ff5f..c0448bfd 100644 --- a/backend/dvadmin/utils/import_export_mixin.py +++ b/backend/dvadmin/utils/import_export_mixin.py @@ -16,7 +16,7 @@ from dvadmin.utils.json_response import DetailResponse, SuccessResponse from dvadmin.utils.request_util import get_verbose_name from dvadmin.system.tasks import async_export_data from dvadmin.system.models import DownloadCenter - +from dvadmin.utils.request_util import get_verbose_name, get_class_path class ImportSerializerMixin: """ @@ -303,56 +303,24 @@ class ExportSerializerMixin: queryset = self.filter_queryset(self.get_queryset()) assert self.export_field_label, "'%s' 请配置对应的导出模板字段。" % self.__class__.__name__ assert self.export_serializer_class, "'%s' 请配置对应的导出序列化器。" % self.__class__.__name__ - data = self.export_serializer_class(queryset, many=True, request=request).data - try: - async_export_data.delay( - data, - str(f"导出{get_verbose_name(queryset)}-{datetime.datetime.now().strftime('%Y%m%d%H%M%S')}.xlsx"), - DownloadCenter.objects.create(creator=request.user, task_name=f'{get_verbose_name(queryset)}数据导出任务', dept_belong_id=request.user.dept_id).pk, - self.export_field_label - ) - return SuccessResponse(msg="导入任务已创建,请前往‘下载中心’等待下载") - except: - pass - # 导出excel 表 - response = HttpResponse(content_type="application/msexcel") - response["Access-Control-Expose-Headers"] = f"Content-Disposition" - response["content-disposition"] = f'attachment;filename={quote(str(f"导出{get_verbose_name(queryset)}.xlsx"))}' - wb = Workbook() - ws = wb.active - header_data = ["序号", *self.export_field_label.values()] - hidden_header = ["#", *self.export_field_label.keys()] - df_len_max = [self.get_string_len(ele) for ele in header_data] - row = get_column_letter(len(self.export_field_label) + 1) - column = 1 - ws.append(header_data) - for index, results in enumerate(data): - results_list = [] - for h_index, h_item in enumerate(hidden_header): - for key,val in results.items(): - if key == h_item: - if val is None or val=="": - results_list.append("") - else: - results_list.append(val) - # 计算最大列宽度 - result_column_width = self.get_string_len(val) - if h_index !=0 and result_column_width > df_len_max[h_index]: - df_len_max[h_index] = result_column_width - ws.append([index + 1, *results_list]) - column += 1 - #  更新列宽 - for index, width in enumerate(df_len_max): - ws.column_dimensions[get_column_letter(index + 1)].width = width - tab = Table(displayName="Table", ref=f"A1:{row}{column}") # 名称管理器 - style = TableStyleInfo( - name="TableStyleLight11", - showFirstColumn=True, - showLastColumn=True, - showRowStripes=True, - showColumnStripes=True, - ) - tab.tableStyleInfo = style - ws.add_table(tab) - wb.save(response) - return response + sql, params = queryset.query.sql_with_params() # 获取sql语句和参数 + instance = DownloadCenter.objects.create( + creator=request.user, + task_name=f'{get_verbose_name(queryset)}数据导出任务', + dept_belong_id=request.user.dept_id) + kwargs = { + "id": instance.id, + "export_field_label": self.export_field_label, # 导出字段 + "model_class": get_class_path(queryset.model), # 实例模型类所指向的完整路径 + "export_serializer_class": get_class_path(self.export_serializer_class), # 实例导出类所指向的完整路径 + "sql": sql, # sql语句 + "params": params, # sql 查询参数 + "filename": f"导出{get_verbose_name(queryset)}-{datetime.datetime.now().strftime('%Y%m%d%H%M%S')}.xlsx", + } + if sys.platform.startswith('win'): + async_export_data(kwargs) + return DetailResponse(data=[], msg=f"导出任务已创建,请前往‘下载中心’等待下载") + elif sys.platform.startswith('linux'): + async_export_data.delay(kwargs) + return DetailResponse(data=[], msg=f"导出任务已创建,请前往‘下载中心’等待下载") + diff --git a/backend/dvadmin/utils/request_util.py b/backend/dvadmin/utils/request_util.py index a87ac27c..2fc2b001 100644 --- a/backend/dvadmin/utils/request_util.py +++ b/backend/dvadmin/utils/request_util.py @@ -144,6 +144,22 @@ def get_os(request, ): ua_string = request.META['HTTP_USER_AGENT'] user_agent = parse(ua_string) return user_agent.get_os() + + +def get_class_path(cls): + """ + 获取 类 所指向的完整路径。 + """ + return f"{cls.__module__}.{cls.__name__}" + + +def load_class(class_path): + """ + 动态导入类。 + """ + module_path, class_name = class_path.rsplit('.', 1) + module = importlib.import_module(module_path) + return getattr(module, class_name) def get_verbose_name(queryset=None, view=None, model=None): diff --git a/backend/requirements.txt b/backend/requirements.txt index 3395719b..f6104d7c 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -28,4 +28,5 @@ uvicorn==0.30.3 gunicorn==22.0.0 gevent==24.2.1 Pillow==10.4.0 -pyinstaller==6.9.0 \ No newline at end of file +pyinstaller==6.9.0 +pandas~=2.2.3 \ No newline at end of file -- Gitee