fastgenerateapi 0.0.27__py2.py3-none-any.whl → 1.1.6__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of fastgenerateapi might be problematic. Click here for more details.
- fastgenerateapi/__init__.py +2 -2
- fastgenerateapi/__version__.py +1 -1
- fastgenerateapi/api_view/base_view.py +17 -7
- fastgenerateapi/api_view/create_view.py +1 -1
- fastgenerateapi/api_view/delete_filter_view.py +1 -1
- fastgenerateapi/api_view/delete_tree_view.py +3 -3
- fastgenerateapi/api_view/delete_view.py +3 -3
- fastgenerateapi/api_view/get_all_view.py +21 -17
- fastgenerateapi/api_view/get_one_view.py +1 -1
- fastgenerateapi/api_view/get_relation_view.py +1 -1
- fastgenerateapi/api_view/get_tree_view.py +1 -1
- fastgenerateapi/api_view/mixin/base_mixin.py +11 -7
- fastgenerateapi/api_view/mixin/dbmodel_mixin.py +30 -20
- fastgenerateapi/api_view/mixin/response_mixin.py +79 -32
- fastgenerateapi/api_view/mixin/tool_mixin.py +1 -357
- fastgenerateapi/api_view/mixin/utils/__init__.py +0 -0
- fastgenerateapi/api_view/mixin/utils/docx_util.py +399 -0
- fastgenerateapi/api_view/mixin/utils/file_util.py +30 -0
- fastgenerateapi/api_view/mixin/utils/pdf_util.py +76 -0
- fastgenerateapi/api_view/mixin/utils/xlsx_util.py +336 -0
- fastgenerateapi/api_view/mixin/utils/zip_util.py +50 -0
- fastgenerateapi/api_view/switch_view.py +11 -11
- fastgenerateapi/api_view/update_relation_view.py +3 -3
- fastgenerateapi/api_view/update_view.py +1 -1
- fastgenerateapi/cache/cache_decorator.py +1 -1
- fastgenerateapi/controller/filter_controller.py +68 -26
- fastgenerateapi/controller/router_controller.py +9 -9
- fastgenerateapi/controller/rpc_controller.py +1 -1
- fastgenerateapi/controller/ws_controller.py +1 -1
- fastgenerateapi/deps/filter_params_deps.py +34 -4
- fastgenerateapi/deps/paginator_deps.py +4 -4
- fastgenerateapi/deps/tree_params_deps.py +4 -4
- fastgenerateapi/example/models.py +3 -2
- fastgenerateapi/example/schemas.py +1 -1
- fastgenerateapi/example/views.py +1 -1
- fastgenerateapi/fastapi_utils/__init__.py +0 -0
- fastgenerateapi/fastapi_utils/all.py +5 -0
- fastgenerateapi/fastapi_utils/param_utils.py +37 -0
- fastgenerateapi/fastapi_utils/response_utils.py +344 -0
- fastgenerateapi/model/__init__.py +0 -0
- fastgenerateapi/model/base_model.py +56 -0
- fastgenerateapi/my_fields/enum_field.py +8 -8
- fastgenerateapi/my_fields/pk_field.py +1 -1
- fastgenerateapi/my_fields/soft_delete_field.py +4 -3
- fastgenerateapi/my_fields/validator.py +60 -0
- fastgenerateapi/pydantic_utils/base_model.py +54 -21
- fastgenerateapi/pydantic_utils/base_settings.py +16 -0
- fastgenerateapi/pydantic_utils/json_encoders.py +2 -1
- fastgenerateapi/schemas_factory/common_function.py +1 -1
- fastgenerateapi/schemas_factory/common_schema_factory.py +4 -4
- fastgenerateapi/schemas_factory/create_schema_factory.py +4 -4
- fastgenerateapi/schemas_factory/filter_schema_factory.py +6 -6
- fastgenerateapi/schemas_factory/get_all_schema_factory.py +5 -5
- fastgenerateapi/schemas_factory/get_one_schema_factory.py +4 -3
- fastgenerateapi/schemas_factory/get_relation_schema_factory.py +3 -3
- fastgenerateapi/schemas_factory/get_tree_schema_factory.py +3 -3
- fastgenerateapi/schemas_factory/response_factory.py +5 -10
- fastgenerateapi/schemas_factory/sql_get_all_schema_factory.py +3 -3
- fastgenerateapi/schemas_factory/update_schema_factory.py +4 -4
- fastgenerateapi/settings/__init__.py +6 -0
- fastgenerateapi/settings/all_settings.py +91 -0
- fastgenerateapi/settings/{settings.py → app_settings.py} +31 -28
- fastgenerateapi/settings/db_settings.py +69 -0
- fastgenerateapi/settings/file_settings.py +24 -0
- fastgenerateapi/settings/jwt_settings.py +23 -0
- fastgenerateapi/settings/otlp_settings.py +69 -0
- fastgenerateapi/settings/redis_settings.py +16 -0
- fastgenerateapi/settings/sms_settings.py +25 -0
- fastgenerateapi/settings/system_settings.py +30 -0
- fastgenerateapi/utils/auto_discover.py +61 -0
- fastgenerateapi/utils/file_utils.py +76 -0
- fastgenerateapi/utils/pwd_utils.py +49 -0
- fastgenerateapi/utils/ramdom_utils.py +48 -0
- fastgenerateapi/utils/snowflake.py +23 -20
- fastgenerateapi/utils/str_util.py +120 -0
- fastgenerateapi/utils/swagger_to_js.py +26 -0
- {fastgenerateapi-0.0.27.dist-info → fastgenerateapi-1.1.6.dist-info}/METADATA +61 -24
- fastgenerateapi-1.1.6.dist-info/RECORD +109 -0
- {fastgenerateapi-0.0.27.dist-info → fastgenerateapi-1.1.6.dist-info}/WHEEL +1 -1
- {fastgenerateapi-0.0.27.dist-info → fastgenerateapi-1.1.6.dist-info}/top_level.txt +1 -0
- script/__init__.py +2 -0
- fastgenerateapi/settings/register_settings.py +0 -6
- fastgenerateapi/utils/parse_str.py +0 -36
- fastgenerateapi-0.0.27.dist-info/RECORD +0 -82
- {fastgenerateapi-0.0.27.dist-info → fastgenerateapi-1.1.6.dist-info}/LICENSE +0 -0
|
@@ -1,23 +1,3 @@
|
|
|
1
|
-
import importlib
|
|
2
|
-
import io
|
|
3
|
-
import operator
|
|
4
|
-
import os
|
|
5
|
-
import time
|
|
6
|
-
import uuid
|
|
7
|
-
from pathlib import Path
|
|
8
|
-
from typing import List, Union, Dict, Type, Tuple, Optional
|
|
9
|
-
|
|
10
|
-
from fastapi import UploadFile
|
|
11
|
-
from pydantic import create_model, ValidationError, BaseModel
|
|
12
|
-
from starlette._utils import is_async_callable
|
|
13
|
-
from starlette.background import BackgroundTask
|
|
14
|
-
from starlette.responses import StreamingResponse, FileResponse
|
|
15
|
-
from tortoise.models import Model
|
|
16
|
-
from tortoise.queryset import QuerySetSingle
|
|
17
|
-
|
|
18
|
-
from fastgenerateapi.schemas_factory.common_schema_factory import common_schema_factory
|
|
19
|
-
|
|
20
|
-
|
|
21
1
|
class ToolMixin:
|
|
22
2
|
|
|
23
3
|
@staticmethod
|
|
@@ -26,346 +6,10 @@ class ToolMixin:
|
|
|
26
6
|
字典key,value互转
|
|
27
7
|
"""
|
|
28
8
|
result = {}
|
|
29
|
-
for key, val in data:
|
|
9
|
+
for key, val in data.items():
|
|
30
10
|
result[val] = key
|
|
31
11
|
return result
|
|
32
12
|
|
|
33
|
-
async def export_xlsx(
|
|
34
|
-
self,
|
|
35
|
-
model_list: Model,
|
|
36
|
-
headers: List[str],
|
|
37
|
-
fields: List[str],
|
|
38
|
-
fields_handler: dict,
|
|
39
|
-
file_save_path: Optional[str] = None,
|
|
40
|
-
# rpc_param: Union[Dict[str, Dict[str, List[str]]], Type[RPCParam], None] = None,
|
|
41
|
-
title: str = None,
|
|
42
|
-
modules: str = "openpyxl"
|
|
43
|
-
) -> StreamingResponse:
|
|
44
|
-
limit_modules = ["openpyxl", "xlsxwriter"]
|
|
45
|
-
if modules not in limit_modules:
|
|
46
|
-
return self.error(msg=f"export xlsx modules only import {'、'.join(limit_modules)}")
|
|
47
|
-
try:
|
|
48
|
-
wb = importlib.import_module(modules).Workbook()
|
|
49
|
-
except Exception:
|
|
50
|
-
return self.error(msg=f"please pip install {modules}")
|
|
51
|
-
if modules == "openpyxl":
|
|
52
|
-
def write(sh, row, col, value):
|
|
53
|
-
sh.cell(row, col).value = value
|
|
54
|
-
|
|
55
|
-
start_col = 1
|
|
56
|
-
start_row = 1
|
|
57
|
-
else:
|
|
58
|
-
def write(sh, row, col, value):
|
|
59
|
-
sh.write(row, col, value)
|
|
60
|
-
|
|
61
|
-
start_col = 0
|
|
62
|
-
start_row = 0
|
|
63
|
-
try:
|
|
64
|
-
sh = wb.active
|
|
65
|
-
sh.title = title if title else f'{self.model_class._meta.table_description}'
|
|
66
|
-
|
|
67
|
-
for col, header in enumerate(headers, start_col):
|
|
68
|
-
write(sh, start_row, col, header)
|
|
69
|
-
|
|
70
|
-
for row, model in enumerate(model_list, start_row + 1):
|
|
71
|
-
model = await self.getattr_model(model=model, fields=fields)
|
|
72
|
-
# model = await self.setattr_model_rpc(self.model_class, model, rpc_param)
|
|
73
|
-
|
|
74
|
-
for col, field in enumerate(fields, 1):
|
|
75
|
-
info = getattr(model, field, "")
|
|
76
|
-
handler = fields_handler.get(field)
|
|
77
|
-
if handler and hasattr(handler, "__call__"):
|
|
78
|
-
if is_async_callable(handler):
|
|
79
|
-
info = await handler(info)
|
|
80
|
-
else:
|
|
81
|
-
info = handler(info)
|
|
82
|
-
write(sh, row, col, info)
|
|
83
|
-
finally:
|
|
84
|
-
if file_save_path:
|
|
85
|
-
wb.save(file_save_path)
|
|
86
|
-
return self.success(msg="请求成功")
|
|
87
|
-
bytes_io = io.BytesIO()
|
|
88
|
-
wb.save(bytes_io)
|
|
89
|
-
bytes_io.seek(0)
|
|
90
|
-
|
|
91
|
-
return StreamingResponse(
|
|
92
|
-
bytes_io,
|
|
93
|
-
media_type="application/vnd.ms-excel;charset=UTF-8",
|
|
94
|
-
)
|
|
95
|
-
|
|
96
|
-
async def export_pdf(
|
|
97
|
-
self,
|
|
98
|
-
model: Model,
|
|
99
|
-
fields_list: List[List[Union[str, Tuple[str]]]],
|
|
100
|
-
data: List[List[str]],
|
|
101
|
-
# rpc_param: Union[Dict[str, Dict[str, List[str]]], Type[RPCParam], None] = None,
|
|
102
|
-
font: str = "msyh",
|
|
103
|
-
font_path: str = None,
|
|
104
|
-
modules: str = "fpdf"
|
|
105
|
-
) -> StreamingResponse:
|
|
106
|
-
"""
|
|
107
|
-
fields_list: [["名字", ("name", "名字"), (数据库字段, 字段中文名)], [第二行]]
|
|
108
|
-
|
|
109
|
-
"""
|
|
110
|
-
limit_modules = ["fpdf"]
|
|
111
|
-
if modules not in limit_modules:
|
|
112
|
-
return self.error(msg=f"export xlsx modules only import {'、'.join(limit_modules)}")
|
|
113
|
-
try:
|
|
114
|
-
pdf = importlib.import_module(modules).FPDF()
|
|
115
|
-
except Exception:
|
|
116
|
-
return self.error(msg=f"please pip install {modules}")
|
|
117
|
-
pdf.add_page()
|
|
118
|
-
pdf.add_font(font, '', font_path if font_path else f"../font/{font}.ttf", True)
|
|
119
|
-
pdf.set_font(font, '', 8)
|
|
120
|
-
if data:
|
|
121
|
-
for data_row in data:
|
|
122
|
-
data_row_width = 180 / len(data_row)
|
|
123
|
-
for data_col in data_row:
|
|
124
|
-
pdf.cell(data_row_width, 8, data_col)
|
|
125
|
-
pdf.ln(10)
|
|
126
|
-
else:
|
|
127
|
-
async def write(model_single_obj):
|
|
128
|
-
fields_data = []
|
|
129
|
-
for fields in fields_list:
|
|
130
|
-
for field in fields:
|
|
131
|
-
if type(field) == tuple:
|
|
132
|
-
fields_data.append(field[0])
|
|
133
|
-
model_data = await self.getattr_model(model_single_obj, fields_data)
|
|
134
|
-
# model_data = await self.setattr_model_rpc(self.model_class, model_data, rpc_param)
|
|
135
|
-
for fields in fields_list:
|
|
136
|
-
cell_width = 180 / len(fields)
|
|
137
|
-
for field in fields:
|
|
138
|
-
if type(field) == str:
|
|
139
|
-
msg = f"{field[1]}"
|
|
140
|
-
else:
|
|
141
|
-
msg = f"{field[1]} {getattr(model_data, field[0]) if getattr(model_data, field[0]) else ''}"
|
|
142
|
-
pdf.cell(cell_width, 8, msg)
|
|
143
|
-
pdf.ln(10)
|
|
144
|
-
|
|
145
|
-
if type(model) == QuerySetSingle:
|
|
146
|
-
await write(model)
|
|
147
|
-
else:
|
|
148
|
-
for model_obj in model:
|
|
149
|
-
await write(model_obj)
|
|
150
|
-
pdf.add_page()
|
|
151
|
-
byte_string = pdf.output(dest="S").encode('latin-1')
|
|
152
|
-
bytes_io = io.BytesIO(byte_string)
|
|
153
|
-
|
|
154
|
-
return StreamingResponse(
|
|
155
|
-
bytes_io,
|
|
156
|
-
media_type="application/pdf"
|
|
157
|
-
)
|
|
158
|
-
|
|
159
|
-
async def import_xlsx(
|
|
160
|
-
self,
|
|
161
|
-
file: UploadFile,
|
|
162
|
-
file_save_path: str,
|
|
163
|
-
headers: List[str],
|
|
164
|
-
# [
|
|
165
|
-
# "name",
|
|
166
|
-
# ("is_male", {"男": True, "女": False} 或者 方法, {"额外字段": 方法}, ...),
|
|
167
|
-
# ]
|
|
168
|
-
# 方法(默认传excel的值)
|
|
169
|
-
fields: List[Union[str, dict, tuple, list]],
|
|
170
|
-
combine_fields: Optional[List[Dict[str, any]]] = None,
|
|
171
|
-
model_class: Optional[Type[Model]] = None,
|
|
172
|
-
create_schema: Optional[Type[BaseModel]] = None,
|
|
173
|
-
# storage_path: Union[str, Path],
|
|
174
|
-
# rpc_param: Union[Dict[str, Dict[str, List[Union[str, tuple]]]], Type[RPCParam]] = None,
|
|
175
|
-
is_delete: Optional[bool] = True,
|
|
176
|
-
modules: str = "openpyxl",
|
|
177
|
-
) -> StreamingResponse:
|
|
178
|
-
"""
|
|
179
|
-
fields: 方法(默认传excel的值)
|
|
180
|
-
例如:
|
|
181
|
-
[
|
|
182
|
-
"name", # 传入值是 name 字段的值
|
|
183
|
-
("is_male", {"男": True, "女": False} 或者 方法, {"额外字段": 方法}, ...),
|
|
184
|
-
# 值 "男" 获取为bool值,不在字典里为None, 页可以自定义 同步或异步方法 获取值
|
|
185
|
-
]
|
|
186
|
-
"""
|
|
187
|
-
limit_modules = ["openpyxl"]
|
|
188
|
-
if modules not in limit_modules:
|
|
189
|
-
return self.error(msg=f"export xlsx modules only import {'、'.join(limit_modules)}")
|
|
190
|
-
|
|
191
|
-
if not file:
|
|
192
|
-
return self.fail(msg=f"请先选择合适的文件")
|
|
193
|
-
|
|
194
|
-
if not model_class:
|
|
195
|
-
model_class = self.model_class
|
|
196
|
-
if not create_schema:
|
|
197
|
-
create_schema = common_schema_factory(model_class, name=f"{model_class.__name__}ExcelImportSchema")
|
|
198
|
-
res = await file.read()
|
|
199
|
-
with open(file_save_path, 'wb') as destination:
|
|
200
|
-
destination.write(res)
|
|
201
|
-
try:
|
|
202
|
-
wb = importlib.import_module(modules).load_workbook(file_save_path, read_only=True, data_only=True)
|
|
203
|
-
except Exception:
|
|
204
|
-
return self.error(msg=f"please pip install {modules}")
|
|
205
|
-
try:
|
|
206
|
-
ws = wb.active
|
|
207
|
-
|
|
208
|
-
header_row = ws[1]
|
|
209
|
-
header_list = []
|
|
210
|
-
for msg in header_row:
|
|
211
|
-
header_list.append(str(msg.value).replace(" ", ''))
|
|
212
|
-
|
|
213
|
-
if len(header_list) != len(headers):
|
|
214
|
-
return self.fail(message="文件首行长度校验错误")
|
|
215
|
-
|
|
216
|
-
if not operator.eq(header_list, headers):
|
|
217
|
-
return self.fail(message="文件首行内容校验错误")
|
|
218
|
-
|
|
219
|
-
# if ws.max_row < 2:
|
|
220
|
-
# return self.fail(msg="导入数据不能为空")
|
|
221
|
-
|
|
222
|
-
create_list = []
|
|
223
|
-
effective_row = 0
|
|
224
|
-
for row in range(2, ws.max_row + 1):
|
|
225
|
-
data = {}
|
|
226
|
-
# data_schema = {}
|
|
227
|
-
row_data = ws[row]
|
|
228
|
-
if await self.excel_row_is_empty(row_data):
|
|
229
|
-
continue
|
|
230
|
-
effective_row += 1
|
|
231
|
-
for col, field_input in enumerate(fields):
|
|
232
|
-
if type(field_input) in [str, int]:
|
|
233
|
-
data[field_input] = row_data[col].value
|
|
234
|
-
# data_schema[field_input] = (type(row_data[col].value), ...)
|
|
235
|
-
|
|
236
|
-
if type(field_input) == tuple or type(field_input) == list:
|
|
237
|
-
key = field_input[0]
|
|
238
|
-
val = field_input[1]
|
|
239
|
-
required_doc = {}
|
|
240
|
-
if len(field_input) > 2:
|
|
241
|
-
required_doc = field_input[2]
|
|
242
|
-
if required_doc == "required":
|
|
243
|
-
required_doc = {"required": True}
|
|
244
|
-
if type(val) == dict:
|
|
245
|
-
model_val = val.get(row_data[col].value)
|
|
246
|
-
if not model_val and required_doc.get("required"):
|
|
247
|
-
return self.fail(
|
|
248
|
-
msg=required_doc.get("error",
|
|
249
|
-
"") or f"第{row}行{self.get_field_description(key)}不能为空")
|
|
250
|
-
data[key] = model_val
|
|
251
|
-
# data_schema[key] = (type(model_val), ...)
|
|
252
|
-
elif hasattr(val, "__call__"):
|
|
253
|
-
if is_async_callable(val):
|
|
254
|
-
model_val = await val(row_data[col].value)
|
|
255
|
-
else:
|
|
256
|
-
model_val = val(row_data[col].value)
|
|
257
|
-
data[key] = model_val
|
|
258
|
-
# data_schema[key] = (type(model_val), ...)
|
|
259
|
-
else:
|
|
260
|
-
raise NotImplemented
|
|
261
|
-
else:
|
|
262
|
-
raise NotImplemented
|
|
263
|
-
for combine_field in combine_fields:
|
|
264
|
-
field = combine_field.get("field", None)
|
|
265
|
-
value = combine_field.get("value", None)
|
|
266
|
-
function = combine_field.get("function", None)
|
|
267
|
-
args = combine_field.get("args", None)
|
|
268
|
-
if not field or (not function and not value):
|
|
269
|
-
continue
|
|
270
|
-
if value:
|
|
271
|
-
data[field] = value
|
|
272
|
-
else:
|
|
273
|
-
if not args:
|
|
274
|
-
if is_async_callable(function):
|
|
275
|
-
model_val = await function()
|
|
276
|
-
else:
|
|
277
|
-
model_val = function()
|
|
278
|
-
else:
|
|
279
|
-
args_list = []
|
|
280
|
-
for arg in args:
|
|
281
|
-
args_list.append(data.get(arg, ""))
|
|
282
|
-
if is_async_callable(function):
|
|
283
|
-
model_val = await function(*args_list)
|
|
284
|
-
else:
|
|
285
|
-
model_val = function(*args_list)
|
|
286
|
-
data[field] = model_val
|
|
287
|
-
try:
|
|
288
|
-
create_obj = model_class(**create_schema(**data).dict(exclude_unset=True))
|
|
289
|
-
except ValidationError as e:
|
|
290
|
-
error_field = e.errors()[0].get('loc')[0]
|
|
291
|
-
description = self.get_field_description(error_field)
|
|
292
|
-
if not data.get(error_field):
|
|
293
|
-
return self.fail(message=f"第{row}行{description}不能为空")
|
|
294
|
-
return self.fail(message=f"第{row}行{description}填写错误")
|
|
295
|
-
await self.check_unique_field(create_obj, model_class=model_class)
|
|
296
|
-
create_list.append(create_obj)
|
|
297
|
-
|
|
298
|
-
await model_class.bulk_create(create_list)
|
|
299
|
-
finally:
|
|
300
|
-
wb.close()
|
|
301
|
-
if effective_row == 0:
|
|
302
|
-
return self.fail(message="导入数据不能为空")
|
|
303
|
-
if is_delete:
|
|
304
|
-
return self.success(
|
|
305
|
-
msg='创建成功',
|
|
306
|
-
background=BackgroundTask(lambda: os.remove(file_save_path))
|
|
307
|
-
)
|
|
308
|
-
return self.success(msg='创建成功')
|
|
309
|
-
|
|
310
|
-
@staticmethod
|
|
311
|
-
async def excel_row_is_empty(row_list) -> bool:
|
|
312
|
-
is_empty = True
|
|
313
|
-
for row in row_list:
|
|
314
|
-
if row.value is not None:
|
|
315
|
-
return False
|
|
316
|
-
|
|
317
|
-
return is_empty
|
|
318
|
-
|
|
319
|
-
async def excel_model(
|
|
320
|
-
self,
|
|
321
|
-
headers: List[str] = None,
|
|
322
|
-
model_class: Optional[Model] = None,
|
|
323
|
-
excel_model_path: Optional[str] = None,
|
|
324
|
-
modules: str = "openpyxl",
|
|
325
|
-
title: Optional[str] = None,
|
|
326
|
-
) -> Union[FileResponse, StreamingResponse]:
|
|
327
|
-
if excel_model_path:
|
|
328
|
-
return FileResponse(
|
|
329
|
-
path=excel_model_path,
|
|
330
|
-
filename="导入模板.xlsx",
|
|
331
|
-
media_type="xlsx",
|
|
332
|
-
)
|
|
333
|
-
|
|
334
|
-
limit_modules = ["openpyxl", "xlsxwriter"]
|
|
335
|
-
if modules not in limit_modules:
|
|
336
|
-
return self.error(msg=f"export xlsx modules only import {'、'.join(limit_modules)}")
|
|
337
|
-
try:
|
|
338
|
-
wb = importlib.import_module(modules).Workbook()
|
|
339
|
-
except Exception:
|
|
340
|
-
return self.error(msg=f"please pip install {modules}")
|
|
341
|
-
if modules == "openpyxl":
|
|
342
|
-
def write(sh, row, col, value):
|
|
343
|
-
sh.cell(row, col).value = value
|
|
344
|
-
|
|
345
|
-
start_col = 1
|
|
346
|
-
start_row = 1
|
|
347
|
-
else:
|
|
348
|
-
def write(sh, row, col, value):
|
|
349
|
-
sh.write(row, col, value)
|
|
350
|
-
|
|
351
|
-
start_col = 0
|
|
352
|
-
start_row = 0
|
|
353
|
-
try:
|
|
354
|
-
sh = wb.active
|
|
355
|
-
sh.title = title if title else f'{model_class._meta.table_description}'
|
|
356
|
-
|
|
357
|
-
for col, header in enumerate(headers, start_col):
|
|
358
|
-
write(sh, start_row, col, header)
|
|
359
|
-
|
|
360
|
-
finally:
|
|
361
|
-
bytes_io = io.BytesIO()
|
|
362
|
-
wb.save(bytes_io)
|
|
363
|
-
bytes_io.seek(0)
|
|
364
|
-
|
|
365
|
-
return StreamingResponse(
|
|
366
|
-
bytes_io,
|
|
367
|
-
media_type="application/vnd.ms-excel;charset=UTF-8",
|
|
368
|
-
)
|
|
369
13
|
|
|
370
14
|
|
|
371
15
|
|
|
File without changes
|