166 lines
5.2 KiB
Python
166 lines
5.2 KiB
Python
# -*- coding: utf-8 -*-
|
||
# @version : 1.0
|
||
# @Create Time : 2021/12/5 8:45
|
||
# @File : import_manage.py
|
||
# @IDE : PyCharm
|
||
# @desc : 数据导入管理
|
||
|
||
from typing import List
|
||
from fastapi import UploadFile
|
||
from core.exception import CustomException
|
||
from utils import status
|
||
from .excel_manage import ExcelManage
|
||
from utils.file.file_manage import FileManage
|
||
from .write_xlsx import WriteXlsx
|
||
from ..tools import list_dict_find
|
||
from enum import Enum
|
||
|
||
|
||
class FieldType(Enum):
|
||
list = "list"
|
||
str = "str"
|
||
|
||
|
||
class ImportManage(ExcelManage):
|
||
"""
|
||
数据导入管理
|
||
|
||
只支持 XLSX 类型文件:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
|
||
|
||
1. 判断文件类型
|
||
2. 保存文件为临时文件
|
||
3. 获取文件中的数据
|
||
4. 逐行检查数据,通过则创建数据
|
||
5. 不通过则添加到错误列表
|
||
6. 统计数量并返回
|
||
"""
|
||
|
||
file_type = ["application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"]
|
||
|
||
def __init__(self, file: UploadFile, headers: List[dict]):
|
||
super().__init__()
|
||
self.__table_data = None
|
||
self.__table_header = None
|
||
self.errors = []
|
||
self.success = []
|
||
self.success_number = 0
|
||
self.error_number = 0
|
||
self.check_file_type(file)
|
||
self.file = file
|
||
self.headers = headers
|
||
|
||
@classmethod
|
||
def check_file_type(cls, file: UploadFile) -> None:
|
||
"""
|
||
验证文件类型
|
||
:param file: 上传文件
|
||
:return:
|
||
"""
|
||
if file.content_type not in cls.file_type:
|
||
raise CustomException(msg="文件类型必须为xlsx类型", code=status.HTTP_ERROR)
|
||
|
||
async def get_table_data(
|
||
self,
|
||
file_path: str = None,
|
||
sheet_name: str = None,
|
||
header_row: int = 1,
|
||
data_row: int = 2
|
||
) -> None:
|
||
"""
|
||
获取表格数据与表头
|
||
:param file_path:
|
||
:param sheet_name:
|
||
:param header_row: 表头在第几行
|
||
:param data_row: 数据开始行
|
||
:return:
|
||
"""
|
||
if file_path:
|
||
__filename = file_path
|
||
else:
|
||
__filename = await FileManage.async_save_temp_file(self.file)
|
||
self.open_sheet(sheet_name=sheet_name, file=__filename, read_only=True)
|
||
self.__table_header = self.get_header(header_row, len(self.headers), asterisk=True)
|
||
self.__table_data = self.readlines(min_row=data_row, max_col=len(self.headers))
|
||
self.close()
|
||
|
||
def check_table_data(self) -> None:
|
||
"""
|
||
检查表格数据
|
||
:return:
|
||
"""
|
||
for row in self.__table_data:
|
||
result = self.__check_row(row)
|
||
if not result[0]:
|
||
row.append(result[1])
|
||
self.errors.append(row)
|
||
self.error_number += 1
|
||
else:
|
||
self.success_number += 1
|
||
self.success.append(result[1])
|
||
|
||
def __check_row(self, row: list) -> tuple:
|
||
"""
|
||
检查行数据
|
||
|
||
检查条件:
|
||
1. 检查是否为必填项
|
||
2. 检查是否为选项列表
|
||
3. 检查是否符合规则
|
||
:param row: 数据行
|
||
:return:
|
||
"""
|
||
data = {}
|
||
for index, cell in enumerate(row):
|
||
value = cell
|
||
field = self.headers[index]
|
||
label = self.__table_header[index]
|
||
if not cell and field.get("required", False):
|
||
return False, f"{label}不能为空!"
|
||
elif field.get("options", []) and cell:
|
||
item = list_dict_find(field.get("options", []), "label", cell)
|
||
if item:
|
||
value = item.get("value")
|
||
else:
|
||
return False, f"请选择正确的{label}"
|
||
elif field.get("rules", []) and cell:
|
||
rules = field.get("rules")
|
||
for validator in rules:
|
||
try:
|
||
validator(str(cell))
|
||
except ValueError as e:
|
||
return False, f"{label}:{e.__str__()}"
|
||
if value:
|
||
field_type = field.get("type", FieldType.str)
|
||
if field_type == FieldType.list:
|
||
data[field.get("field")] = [value]
|
||
elif field_type == FieldType.str:
|
||
data[field.get("field")] = str(value)
|
||
else:
|
||
data[field.get("field")] = value
|
||
data["old_data_list"] = row
|
||
return True, data
|
||
|
||
def generate_error_url(self) -> str:
|
||
"""
|
||
成功错误数据的文件链接
|
||
:return:
|
||
"""
|
||
if self.error_number <= 0:
|
||
return ""
|
||
em = WriteXlsx()
|
||
em.create_excel(sheet_name="用户导入失败数据", save_static=True)
|
||
em.generate_template(self.headers, max_row=self.error_number)
|
||
em.write_list(self.errors)
|
||
em.close()
|
||
return em.get_file_url()
|
||
|
||
def add_error_data(self, row: dict) -> None:
|
||
"""
|
||
增加错误数据
|
||
:param row: 错误的数据行
|
||
:return:
|
||
"""
|
||
self.errors.append(row)
|
||
self.error_number += 1
|
||
self.success_number -= 1
|