fastgenerateapi 0.0.28__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.

Files changed (80) hide show
  1. fastgenerateapi/__init__.py +2 -2
  2. fastgenerateapi/__version__.py +1 -1
  3. fastgenerateapi/api_view/base_view.py +17 -7
  4. fastgenerateapi/api_view/create_view.py +1 -1
  5. fastgenerateapi/api_view/delete_filter_view.py +1 -1
  6. fastgenerateapi/api_view/delete_tree_view.py +3 -3
  7. fastgenerateapi/api_view/delete_view.py +3 -3
  8. fastgenerateapi/api_view/get_all_view.py +10 -8
  9. fastgenerateapi/api_view/get_one_view.py +1 -1
  10. fastgenerateapi/api_view/get_relation_view.py +1 -1
  11. fastgenerateapi/api_view/get_tree_view.py +1 -1
  12. fastgenerateapi/api_view/mixin/base_mixin.py +11 -7
  13. fastgenerateapi/api_view/mixin/dbmodel_mixin.py +30 -20
  14. fastgenerateapi/api_view/mixin/response_mixin.py +68 -38
  15. fastgenerateapi/api_view/mixin/tool_mixin.py +1 -357
  16. fastgenerateapi/api_view/mixin/utils/__init__.py +0 -0
  17. fastgenerateapi/api_view/mixin/utils/docx_util.py +399 -0
  18. fastgenerateapi/api_view/mixin/utils/file_util.py +30 -0
  19. fastgenerateapi/api_view/mixin/utils/pdf_util.py +76 -0
  20. fastgenerateapi/api_view/mixin/utils/xlsx_util.py +336 -0
  21. fastgenerateapi/api_view/mixin/utils/zip_util.py +50 -0
  22. fastgenerateapi/api_view/switch_view.py +2 -2
  23. fastgenerateapi/api_view/update_relation_view.py +3 -3
  24. fastgenerateapi/api_view/update_view.py +1 -1
  25. fastgenerateapi/cache/cache_decorator.py +1 -1
  26. fastgenerateapi/controller/filter_controller.py +68 -26
  27. fastgenerateapi/controller/router_controller.py +9 -9
  28. fastgenerateapi/controller/rpc_controller.py +1 -1
  29. fastgenerateapi/controller/ws_controller.py +1 -1
  30. fastgenerateapi/deps/filter_params_deps.py +34 -4
  31. fastgenerateapi/deps/paginator_deps.py +4 -4
  32. fastgenerateapi/deps/tree_params_deps.py +4 -4
  33. fastgenerateapi/fastapi_utils/__init__.py +0 -0
  34. fastgenerateapi/fastapi_utils/all.py +5 -0
  35. fastgenerateapi/fastapi_utils/param_utils.py +37 -0
  36. fastgenerateapi/fastapi_utils/response_utils.py +344 -0
  37. fastgenerateapi/model/__init__.py +0 -0
  38. fastgenerateapi/model/base_model.py +56 -0
  39. fastgenerateapi/my_fields/enum_field.py +5 -5
  40. fastgenerateapi/my_fields/validator.py +60 -0
  41. fastgenerateapi/pydantic_utils/base_model.py +46 -20
  42. fastgenerateapi/pydantic_utils/base_settings.py +16 -0
  43. fastgenerateapi/pydantic_utils/json_encoders.py +2 -1
  44. fastgenerateapi/schemas_factory/common_function.py +1 -1
  45. fastgenerateapi/schemas_factory/common_schema_factory.py +4 -4
  46. fastgenerateapi/schemas_factory/create_schema_factory.py +4 -4
  47. fastgenerateapi/schemas_factory/filter_schema_factory.py +6 -6
  48. fastgenerateapi/schemas_factory/get_all_schema_factory.py +5 -5
  49. fastgenerateapi/schemas_factory/get_one_schema_factory.py +4 -3
  50. fastgenerateapi/schemas_factory/get_relation_schema_factory.py +3 -3
  51. fastgenerateapi/schemas_factory/get_tree_schema_factory.py +3 -3
  52. fastgenerateapi/schemas_factory/response_factory.py +3 -3
  53. fastgenerateapi/schemas_factory/sql_get_all_schema_factory.py +3 -3
  54. fastgenerateapi/schemas_factory/update_schema_factory.py +4 -4
  55. fastgenerateapi/settings/__init__.py +6 -0
  56. fastgenerateapi/settings/all_settings.py +91 -0
  57. fastgenerateapi/settings/{settings.py → app_settings.py} +9 -9
  58. fastgenerateapi/settings/db_settings.py +69 -0
  59. fastgenerateapi/settings/file_settings.py +24 -0
  60. fastgenerateapi/settings/jwt_settings.py +23 -0
  61. fastgenerateapi/settings/otlp_settings.py +69 -0
  62. fastgenerateapi/settings/redis_settings.py +16 -0
  63. fastgenerateapi/settings/sms_settings.py +25 -0
  64. fastgenerateapi/settings/system_settings.py +30 -0
  65. fastgenerateapi/utils/auto_discover.py +61 -0
  66. fastgenerateapi/utils/file_utils.py +76 -0
  67. fastgenerateapi/utils/pwd_utils.py +49 -0
  68. fastgenerateapi/utils/ramdom_utils.py +48 -0
  69. fastgenerateapi/utils/snowflake.py +23 -20
  70. fastgenerateapi/utils/str_util.py +120 -0
  71. fastgenerateapi/utils/swagger_to_js.py +26 -0
  72. {fastgenerateapi-0.0.28.dist-info → fastgenerateapi-1.1.6.dist-info}/METADATA +61 -24
  73. fastgenerateapi-1.1.6.dist-info/RECORD +109 -0
  74. {fastgenerateapi-0.0.28.dist-info → fastgenerateapi-1.1.6.dist-info}/WHEEL +1 -1
  75. {fastgenerateapi-0.0.28.dist-info → fastgenerateapi-1.1.6.dist-info}/top_level.txt +1 -0
  76. script/__init__.py +2 -0
  77. fastgenerateapi/settings/register_settings.py +0 -6
  78. fastgenerateapi/utils/parse_str.py +0 -36
  79. fastgenerateapi-0.0.28.dist-info/RECORD +0 -82
  80. {fastgenerateapi-0.0.28.dist-info → fastgenerateapi-1.1.6.dist-info}/LICENSE +0 -0
@@ -0,0 +1,399 @@
1
+ import base64
2
+ import os
3
+
4
+ from docx.oxml import OxmlElement
5
+ from docx.oxml.ns import qn
6
+
7
+
8
+ class DocxUtil:
9
+
10
+ ######################## 表格内容填充 ############################
11
+ @staticmethod
12
+ def insert_table_content(document_object, text_list=None, picture_list=None, width=None, height=None, style=None):
13
+ """
14
+ 在表格插入数据
15
+ text_list示例: {"table": 2, "row": 12, "cell": 2, "result": "本地url地址", style=None}
16
+ picture_list示例:
17
+ {"table": 2, "row": 12, "cell": 2, "result": "本地url地址", width=None, height=Cm(1.5)}
18
+ {"table": 2, "row": 12, "cell": 2, "result": ["本地url地址", "url2"], width=None, height=Cm(1.5)}
19
+ """
20
+ DocxUtil.insert_table_text(document_object, text_list, style)
21
+ DocxUtil.insert_table_picture(document_object, picture_list, width, height)
22
+
23
+ @staticmethod
24
+ def insert_table_text(document_object, text_list=None, style=None):
25
+ """
26
+ 在表格插入文本类型数据
27
+ text_list示例: {"table": 2, "row": 12, "cell": 2, "paragraph": 0, "result": "本地url地址", style=None}
28
+ table 必填
29
+ row 必填
30
+ cell 必填
31
+ paragraph 默认 0
32
+ """
33
+ if not text_list:
34
+ return
35
+ for text in text_list:
36
+ result = text.get("result")
37
+ if not result:
38
+ continue
39
+ cell = document_object.tables[text.get("table")].rows[text.get("row")].cells[text.get("cell")]
40
+ paragraph = cell.paragraphs[text.get("paragraph", 0)]
41
+ paragraph.add_run(result, style=text.get("style", style))
42
+
43
+ @staticmethod
44
+ def insert_table_picture(document_object, picture_list=None, width=None, height=None):
45
+ """
46
+ 在表格插入图片类型数据
47
+ picture_list示例:
48
+ {"table": 2, "row": 12, "cell": 2, "paragraph": 0, "result": "本地url地址", width=None, height=Cm(1.5)}
49
+ {"table": 2, "row": 12, "cell": 2, "paragraph": 0, "result": ["本地url地址", "url2"], width=None, height=Cm(1.5)}
50
+ """
51
+ if not picture_list:
52
+ return
53
+ for picture in picture_list:
54
+ result = picture.get("result")
55
+ if not result:
56
+ continue
57
+ if type(result) == list:
58
+ img_list = result
59
+ elif type(result) == str:
60
+ img_list = [result]
61
+ else:
62
+ return
63
+ cell = document_object.tables[picture.get("table")].rows[picture.get("row")].cells[picture.get("cell")]
64
+ run = cell.paragraphs[picture.get("paragraph", 0)].add_run()
65
+ for img in img_list:
66
+ if not os.path.exists(img):
67
+ continue
68
+ run.add_picture(img, width=picture.get("width") or width, height=picture.get("height") or height)
69
+
70
+ @staticmethod
71
+ def insert_base64_image_to_table(document_object, base64_img_list, width=None, height=None):
72
+ """
73
+ 把 base64 格式的字符串插入docx中
74
+ base64_img_list示例:
75
+ {"table": 2, "row": 12, "cell": 2, "paragraph": 0, "result": "base64_str", width=None, height=Cm(1.5)}
76
+ {"table": 2, "row": 12, "cell": 2, "paragraph": 0, "result": ["base64_str", "base64_str"], width=None, height=Cm(1.5)}
77
+ """
78
+ for base64_img in base64_img_list:
79
+ # 处理参数result
80
+ result = base64_img.get("result")
81
+ if not result:
82
+ continue
83
+ if type(result) == list:
84
+ result_list = result
85
+ elif type(result) == str:
86
+ result_list = [result]
87
+ else:
88
+ continue
89
+
90
+ # 解码 Base64 字符串
91
+ base64_list = []
92
+ for base64_str in result_list:
93
+ if base64_str.startswith("data:image/png;base64,"):
94
+ base64_str = base64_str[len("data:image/png;base64,"):]
95
+ base64_list.append(base64_str)
96
+ if not base64_list:
97
+ continue
98
+ for base64_str in base64_list:
99
+ image_data = base64.b64decode(base64_str)
100
+ # 临时图片文件名
101
+ temp_image_path = "statics/temp_image.jpg"
102
+ try:
103
+ # 保存为临时图片文件
104
+ with open(temp_image_path, 'wb') as f:
105
+ f.write(image_data)
106
+
107
+ DocxUtil.insert_table_picture(
108
+ document_object,
109
+ [
110
+ {
111
+ "table": base64_img.get("table"),
112
+ "row": base64_img.get("row"),
113
+ "cell": base64_img.get("cell"),
114
+ "paragraph": base64_img.get("paragraph", 0),
115
+ "result": temp_image_path,
116
+ "width": base64_img.get("width"),
117
+ "height": base64_img.get("height"),
118
+ }
119
+ ],
120
+ width=width,
121
+ height=height
122
+ )
123
+ except Exception as e:
124
+ print(f"插入图片时出错: {e}")
125
+ finally:
126
+ # 删除临时图片文件
127
+ if os.path.exists(temp_image_path):
128
+ os.remove(temp_image_path)
129
+
130
+ ######################## 文档文字替换 ############################
131
+ @staticmethod
132
+ def replace_text(document_object, table_replace_list=None, para_replace_list=None):
133
+ """
134
+ 在指定位置替换文本
135
+ para_replace_list 示例:{"paragraph": 2, "run": 12, "cell": 2, "mark": "$code", "result": "替换结果值"}
136
+ table_replace_list 示例:{"table": 2, "row": 12, "cell": 2, "mark": "$code", "result": "替换结果值"}
137
+ """
138
+ if para_replace_list:
139
+ for para_replace in para_replace_list:
140
+ run = document_object.paragraphs[para_replace.get("paragraph")].runs[para_replace.get("run")]
141
+ run.text = run.text.replace(para_replace.get("mark"), para_replace.get("result") or "")
142
+ if table_replace_list:
143
+ for table_replace in table_replace_list:
144
+ cell = document_object.tables[table_replace.get("table")][table_replace.get("row")][
145
+ table_replace.get("cell")]
146
+ for paragraph in cell.paragraphs:
147
+ for run in paragraph.runs:
148
+ run.text = run.text.replace(table_replace.get("mark"), table_replace.get("result") or "")
149
+
150
+ return
151
+
152
+ @staticmethod
153
+ def replace_range_text(document_object, section=None, section_dict=None, paragraph=None, para_dict=None, table=None,
154
+ table_dict=None):
155
+ """
156
+ 在文档内的字段,
157
+ :param document_object: Document 对象,代表要处理的 Word 文档
158
+ :param section 指定页面,不指定便利所有
159
+ :param section_dict 替换内容
160
+ :param paragraph: 指定段落,默认所有段落
161
+ :param para_dict: 例如 { "$name": "名称" }
162
+ :param table: 指定表,默认所有表
163
+ :param table_dict: 例如 { "$name": "名称" }
164
+ """
165
+ DocxUtil.replace_section(document_object, section_dict, section)
166
+ DocxUtil.replace_paragraph(document_object, para_dict, paragraph)
167
+ DocxUtil.replace_table(document_object, table_dict, table)
168
+
169
+ return document_object
170
+
171
+ @staticmethod
172
+ def replace_section(document_object, section_dict, section=None):
173
+ """
174
+ 页眉替换文本
175
+ :param document_object:
176
+ :param section_dict: 例如 { "$code": "编码" }
177
+ :param section: 指定页眉,默认所有页眉
178
+ :return:
179
+ """
180
+ if not section_dict:
181
+ return
182
+ section_list = [section] if section else document_object.sections
183
+ for section in section_list:
184
+ header = section.header
185
+ for header_para in header.paragraphs:
186
+ for i in range(len(header_para.runs)):
187
+ for key, value in section_dict.items():
188
+ header_para.runs[i].text = header_para.runs[i].text.replace(key, value or "")
189
+
190
+ @staticmethod
191
+ def replace_paragraph(document_object, para_dict, paragraph=None):
192
+ """
193
+ 段落替换文本
194
+ :param document_object:
195
+ :param para_dict: 例如 { "$name": "名称" }
196
+ :param paragraph: 指定段落,默认所有段落
197
+ :return:
198
+ """
199
+ if not para_dict:
200
+ return
201
+ paragraph_list = [paragraph] if paragraph else document_object.paragraphs
202
+ for para in paragraph_list:
203
+ for i in range(len(para.runs)):
204
+ for key, value in para_dict.items():
205
+ if key in para.runs[i].text:
206
+ para.runs[i].text = para.runs[i].text.replace(key, value or "")
207
+
208
+ @staticmethod
209
+ def replace_table(document_object, table_dict, table=None):
210
+ """
211
+ 表格替换文本
212
+ :param document_object:
213
+ :param table_dict: 例如 { "$name": "名称" }
214
+ :param table: 指定表,默认所有表
215
+ :return:
216
+ """
217
+ if not table_dict:
218
+ return
219
+ table_list = [table] if table else document_object.tables
220
+ for table in table_list:
221
+ for row in table.rows:
222
+ for cell in row.cells:
223
+ for key, value in table_dict.items():
224
+ if key in cell.text:
225
+ for paragraph in cell.paragraphs:
226
+ for run in paragraph.runs:
227
+ run.text = run.text.replace(key, value or "")
228
+
229
+ ######################## 表格行复制 ############################
230
+ @staticmethod
231
+ def copy_row(target_table, source_row, insert_index=None, row_num=1):
232
+ """
233
+ target_table: 指定操作表
234
+ source_row: 指定复制的行
235
+ insert_index: 复制后的行插入位置
236
+ row_num: 复制行的数量
237
+ 完整复制行包括合并属性
238
+ """
239
+ while row_num > 0:
240
+ row_num -= 1
241
+ # 在表格末尾添加新行
242
+ new_row = target_table.add_row()
243
+
244
+ # 复制单元格内容和格式
245
+ delete_cell = 0
246
+ for src_idx, src_cell in enumerate(source_row.cells):
247
+ new_cell = new_row.cells[src_idx]
248
+ new_cell = DocxUtil.copy_cell_properties(new_cell, src_cell)
249
+
250
+ # 统计合并单元格的占用数量
251
+ grid_span = src_cell._tc.tcPr.find(qn("w:gridSpan"))
252
+ span = int(grid_span.get(qn("w:val"))) if grid_span is not None else 1
253
+ if span > 1:
254
+ # 处理合并单元格的列跨度
255
+ DocxUtil.copy_merged_cell_properties(src_cell, new_cell)
256
+ delete_cell += span - 1
257
+ while delete_cell > 1:
258
+ delete_cell -= 1
259
+ if len(new_row.cells) > 0:
260
+ new_row._element.remove(new_row.cells[-1]._element)
261
+
262
+ # 复制行高
263
+ source_tr = source_row._tr
264
+ new_tr = new_row._tr
265
+ trPr = source_tr.find(qn('w:trPr'))
266
+ if trPr is not None:
267
+ new_trPr = new_tr.get_or_add_trPr()
268
+ for child in trPr.iterchildren():
269
+ if child.tag.endswith('}trHeight'):
270
+ new_trHeight = OxmlElement('w:trHeight')
271
+ for attr, value in child.attrib.items():
272
+ new_trHeight.set(attr, value)
273
+ new_trPr.append(new_trHeight)
274
+
275
+ # 移动行到指定位置
276
+ if insert_index and insert_index < len(target_table.rows):
277
+ target_table._tbl.insert(insert_index + 1, new_row._tr)
278
+ else:
279
+ target_table._tbl.append(new_row._tr)
280
+
281
+ @staticmethod
282
+ def copy_cell_properties(target_cell, source_cell):
283
+ """
284
+ target_cell: 指定单元格
285
+ source_cell: 指定复制的单元格
286
+ 复制cell的属性
287
+ """
288
+ # 清空目标单元格内容,复制文本内容
289
+ target_cell.text = ""
290
+ target_cell.text = source_cell.text
291
+
292
+ # 根据目标段落填充数量
293
+ add_para_num = len(source_cell.paragraphs) - len(target_cell.paragraphs)
294
+ while add_para_num > 0:
295
+ add_para_num -= 1
296
+ target_cell.add_paragraph()
297
+
298
+ # 复制段落样式
299
+ for para_idx, para in enumerate(source_cell.paragraphs):
300
+ new_para = target_cell.paragraphs[para_idx]
301
+ new_para.style = para.style
302
+
303
+ # 复制段落对齐方式
304
+ new_para.alignment = para.alignment
305
+
306
+ # 复制行间距
307
+ new_para.paragraph_format.line_spacing = para.paragraph_format.line_spacing
308
+ new_para.paragraph_format.line_spacing_rule = para.paragraph_format.line_spacing_rule
309
+ new_para.paragraph_format.page_break_before = para.paragraph_format.page_break_before
310
+ new_para.paragraph_format.right_indent = para.paragraph_format.right_indent
311
+ new_para.paragraph_format.space_after = para.paragraph_format.space_after
312
+ new_para.paragraph_format.space_before = para.paragraph_format.space_before
313
+ new_para.paragraph_format.widow_control = para.paragraph_format.widow_control
314
+
315
+ # 复制文本格式
316
+ for run in para.runs:
317
+ new_run = new_para.add_run(run.text)
318
+ new_run.style = run.style
319
+ new_run.font.bold = run.font.bold
320
+ new_run.font.italic = run.font.italic
321
+ new_run.font.underline = run.font.underline
322
+ new_run.font.size = run.font.size
323
+ if run.font.color.rgb:
324
+ new_run.font.color.rgb = run.font.color.rgb
325
+
326
+ # 复制单元格垂直对齐方式
327
+ target_cell.vertical_alignment = source_cell.vertical_alignment
328
+ # 复制单元格边框
329
+ source_tc = source_cell._tc
330
+ target_tc = target_cell._tc
331
+ source_tcPr = source_tc.tcPr
332
+ target_tcPr = target_tc.get_or_add_tcPr()
333
+ for border in ['top', 'left', 'bottom', 'right']:
334
+ source_border = source_tcPr.xpath(f'.//w:{border}')
335
+ if source_border:
336
+ new_border = OxmlElement(f'w:{border}')
337
+ for key, value in source_border[0].attrib.items():
338
+ new_border.set(key, value)
339
+ target_tcPr.append(new_border)
340
+
341
+ # 复制单元格背景色
342
+ shading = source_tcPr.xpath('.//w:shd')
343
+ if shading:
344
+ new_shading = OxmlElement('w:shd')
345
+ for key, value in shading[0].attrib.items():
346
+ new_shading.set(key, value)
347
+ target_tcPr.append(new_shading)
348
+
349
+ return target_cell
350
+
351
+ @staticmethod
352
+ def copy_merged_cell_properties(target_cell, source_cell):
353
+ """复制合并单元格属性(gridSpan和vMerge)"""
354
+ # 获取源单元格属性元素
355
+ source_tc = source_cell._tc
356
+ source_tcPr = source_tc.tcPr
357
+
358
+ # 准备目标单元格属性元素
359
+ target_tc = target_cell._tc
360
+ target_tcPr = target_tc.get_or_add_tcPr()
361
+
362
+ # 处理gridSpan(跨列)
363
+ grid_span = source_tcPr.find(qn("w:gridSpan"))
364
+ if grid_span is not None:
365
+ # 删除目标单元格原有gridSpan
366
+ existing = target_tcPr.find(qn("w:gridSpan"))
367
+ if existing is not None:
368
+ target_tcPr.remove(existing)
369
+ # 复制新的gridSpan
370
+ new_gs = OxmlElement("w:gridSpan")
371
+ new_gs.set(qn("w:val"), grid_span.get(qn("w:val")))
372
+ target_tcPr.append(new_gs)
373
+
374
+ # 处理vMerge(跨行)
375
+ v_merge = source_tcPr.find(qn("w:vMerge"))
376
+ if v_merge is not None:
377
+ # 删除目标单元格原有vMerge
378
+ existing = target_tcPr.find(qn("w:vMerge"))
379
+ if existing is not None:
380
+ target_tcPr.remove(existing)
381
+ # 复制新的vMerge
382
+ new_vm = OxmlElement("w:vMerge")
383
+ if v_merge.get(qn("w:val")) is not None:
384
+ new_vm.set(qn("w:val"), v_merge.get(qn("w:val")))
385
+ target_tcPr.append(new_vm)
386
+
387
+ ######################## 其他操作 ############################
388
+ @staticmethod
389
+ def remove_paragraph(target_cell, para_index):
390
+ """
391
+ 删除单元格的段落
392
+ """
393
+ para = target_cell.paragraphs[para_index]
394
+ p = para._element
395
+ p.getparent().remove(p)
396
+ para._element = None
397
+
398
+ return
399
+
@@ -0,0 +1,30 @@
1
+ import os.path
2
+ import time
3
+ from typing import Optional, List
4
+
5
+
6
+ class FileUtil:
7
+
8
+ @staticmethod
9
+ def get_sub_file_names(file_num: int, base_dir: Optional[str] = "./temp", dir_name: Optional[str] = None, file_name: Optional[str] = None) -> List[str]:
10
+ """
11
+ 功能用于多个文件打包在压缩包中下载
12
+ 在指定或时间戳文件夹下,根据文件数量生成对应文件名
13
+ :param base_dir: 基础路径
14
+ :param file_num: 子文件数量
15
+ :param dir_name: 文件夹名称,可以忽略
16
+ :param file_name: 文件夹名称,建议填写,方便区分
17
+ :return: 文件名列表
18
+ """
19
+ if not base_dir:
20
+ base_dir = "./temp"
21
+ if not dir_name:
22
+ dir_name = str(int(time.time()*1000))
23
+ dir_path = os.path.join(base_dir, dir_name)
24
+ if os.path.exists(dir_path):
25
+ dir_path = os.path.join(base_dir, str(int(time.time()*1000)))
26
+ os.makedirs(dir_path, exist_ok=True)
27
+ file_name_list = []
28
+ for i in range(1, file_num+1):
29
+ file_name_list.append(os.path.join(dir_path, (file_name or "") +"_"+str(i)))
30
+ return file_name_list
@@ -0,0 +1,76 @@
1
+ import importlib
2
+ import io
3
+ from typing import List, Union, Tuple
4
+
5
+ from starlette.responses import StreamingResponse
6
+ from tortoise.models import Model
7
+ from tortoise.queryset import QuerySetSingle
8
+
9
+ from fastgenerateapi import BaseView
10
+ from fastgenerateapi.api_view.mixin.response_mixin import ResponseMixin
11
+
12
+
13
+ class PdfUtil:
14
+
15
+ async def export_pdf(
16
+ self,
17
+ model: Model,
18
+ fields_list: List[List[Union[str, Tuple[str]]]],
19
+ data: List[List[str]],
20
+ # rpc_param: Union[Dict[str, Dict[str, List[str]]], Type[RPCParam], None] = None,
21
+ font: str = "msyh",
22
+ font_path: str = None,
23
+ modules: str = "fpdf"
24
+ ) -> StreamingResponse:
25
+ """
26
+ fields_list: [["名字", ("name", "名字"), (数据库字段, 字段中文名)], [第二行]]
27
+
28
+ """
29
+ limit_modules = ["fpdf"]
30
+ if modules not in limit_modules:
31
+ return ResponseMixin.error(msg=f"export xlsx modules only import {'、'.join(limit_modules)}")
32
+ try:
33
+ pdf = importlib.import_module(modules).FPDF()
34
+ except Exception:
35
+ return ResponseMixin.error(msg=f"please pip install {modules}")
36
+ pdf.add_page()
37
+ pdf.add_font(font, '', font_path if font_path else f"../font/{font}.ttf", True)
38
+ pdf.set_font(font, '', 8)
39
+ if data:
40
+ for data_row in data:
41
+ data_row_width = 180 / len(data_row)
42
+ for data_col in data_row:
43
+ pdf.cell(data_row_width, 8, data_col)
44
+ pdf.ln(10)
45
+ else:
46
+ async def write(model_single_obj):
47
+ fields_data = []
48
+ for fields in fields_list:
49
+ for field in fields:
50
+ if type(field) == tuple:
51
+ fields_data.append(field[0])
52
+ model_data = await BaseView.getattr_model(model_single_obj, fields_data)
53
+ # model_data = await self.setattr_model_rpc(self.model_class, model_data, rpc_param)
54
+ for fields in fields_list:
55
+ cell_width = 180 / len(fields)
56
+ for field in fields:
57
+ if type(field) == str:
58
+ msg = f"{field[1]}"
59
+ else:
60
+ msg = f"{field[1]} {getattr(model_data, field[0]) if getattr(model_data, field[0]) else ''}"
61
+ pdf.cell(cell_width, 8, msg)
62
+ pdf.ln(10)
63
+
64
+ if type(model) == QuerySetSingle:
65
+ await write(model)
66
+ else:
67
+ for model_obj in model:
68
+ await write(model_obj)
69
+ pdf.add_page()
70
+ byte_string = pdf.output(dest="S").encode('latin-1')
71
+ bytes_io = io.BytesIO(byte_string)
72
+
73
+ return ResponseMixin.stream(bytes_io, is_pdf=True)
74
+
75
+
76
+