fastgenerateapi 0.0.28__py2.py3-none-any.whl → 1.1.7__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.7.dist-info}/METADATA +61 -24
  73. fastgenerateapi-1.1.7.dist-info/RECORD +109 -0
  74. {fastgenerateapi-0.0.28.dist-info → fastgenerateapi-1.1.7.dist-info}/WHEEL +1 -1
  75. {fastgenerateapi-0.0.28.dist-info → fastgenerateapi-1.1.7.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.7.dist-info}/LICENSE +0 -0
@@ -0,0 +1,336 @@
1
+ import importlib
2
+ import io
3
+ import operator
4
+ from tempfile import NamedTemporaryFile
5
+ from typing import List, Union, Optional, Dict, Type
6
+
7
+ import openpyxl
8
+ from fastapi import UploadFile
9
+ from openpyxl.styles import Alignment, PatternFill
10
+ from openpyxl.styles.colors import COLOR_INDEX, Color
11
+ from openpyxl.utils import get_column_letter
12
+ from openpyxl.worksheet.worksheet import Worksheet
13
+ from starlette._utils import is_async_callable
14
+ from starlette.responses import StreamingResponse, JSONResponse, FileResponse
15
+ from tortoise import Model
16
+ from pydantic import ValidationError
17
+
18
+ from fastgenerateapi import BaseModel, BaseView
19
+ from fastgenerateapi.api_view.mixin.response_mixin import ResponseMixin
20
+ from fastgenerateapi.schemas_factory.common_schema_factory import common_schema_factory
21
+
22
+
23
+ class XlsxUtil:
24
+ default_align = Alignment(
25
+ horizontal='center',
26
+ vertical='center',
27
+ text_rotation=0,
28
+ wrap_text=True,
29
+ shrink_to_fit=True,
30
+ indent=0,
31
+ )
32
+ default_fill = PatternFill(
33
+ start_color=Color(COLOR_INDEX[44]),
34
+ end_color=Color(COLOR_INDEX[44]),
35
+ fill_type='solid'
36
+ )
37
+
38
+ @staticmethod
39
+ def write_headers(sh: Worksheet, headers: List[str]) -> List[int]:
40
+ """
41
+ 写入第一行信息
42
+ :return:
43
+ """
44
+ col_max_len_list = []
45
+ sh.row_dimensions[1].height = 26
46
+ for col, header in enumerate(headers, 1):
47
+ sh.cell(1, col).value = header
48
+ sh.cell(1, col).alignment = XlsxUtil.default_align
49
+ sh.cell(1, col).fill = XlsxUtil.default_fill
50
+ sh.cell(1, col).alignment = XlsxUtil.default_align
51
+ col_max_len_list.append(len(header.encode('gb18030')))
52
+
53
+ return col_max_len_list
54
+
55
+ @staticmethod
56
+ def write_content(model_list: List[Model]):
57
+ """
58
+ 填写内容部分
59
+ :return:
60
+ """
61
+ # 跳过标题,从第二行开始写入
62
+ for row, model in enumerate(model_list, 2):
63
+ ...
64
+
65
+ return
66
+
67
+ @staticmethod
68
+ def adaptive_format(sh: Worksheet, col_max_len_list: List[int], height_num: int):
69
+ """
70
+ 自适应宽度
71
+ :return:
72
+ """
73
+ # 设置自适应列宽
74
+ for i, col_max_len in enumerate(col_max_len_list, 1):
75
+ # 256*字符数得到excel列宽,为了不显得特别紧凑添加两个字符宽度
76
+ max_width = col_max_len + 4
77
+ if max_width > 256:
78
+ max_width = 256
79
+ sh.column_dimensions[get_column_letter(i)].width = max_width
80
+ for y in range(2, height_num + 2):
81
+ sh.row_dimensions[y].height = 18
82
+
83
+ return
84
+
85
+ async def export_xlsx(
86
+ self,
87
+ model_list: List[Model],
88
+ headers: List[str],
89
+ fields: List[str],
90
+ fields_handler: dict,
91
+ file_save_path: Optional[str] = None,
92
+ # rpc_param: Union[Dict[str, Dict[str, List[str]]], Type[RPCParam], None] = None,
93
+ title: str = None,
94
+ ) -> StreamingResponse:
95
+ wb = openpyxl.Workbook()
96
+ col_max_len_list = []
97
+
98
+ def write(sh, row, col, value):
99
+ sh.cell(row, col).value = value
100
+ sh.cell(row, col).alignment = XlsxUtil.default_align
101
+ if col_max_len_list[col - 1] < len(str(value).encode('gb18030')):
102
+ col_max_len_list[col - 1] = len(str(value).encode('gb18030'))
103
+
104
+ start_row = 1
105
+ try:
106
+ sh = wb.active
107
+ sh.title = title if title else f'{self.model_class._meta.table_description}'
108
+
109
+ col_max_len_list = XlsxUtil.write_headers(sh, headers)
110
+
111
+ for row, model in enumerate(model_list, start_row + 1):
112
+ model = await BaseView.getattr_model(model=model, fields=fields)
113
+ # model = await self.setattr_model_rpc(self.model_class, model, rpc_param)
114
+
115
+ for col, field in enumerate(fields, 1):
116
+ info = getattr(model, field, "")
117
+ handler = fields_handler.get(field)
118
+ if handler and hasattr(handler, "__call__"):
119
+ if is_async_callable(handler):
120
+ info = await handler(info)
121
+ else:
122
+ info = handler(info)
123
+ write(sh, row, col, info)
124
+
125
+ XlsxUtil.adaptive_format(sh, col_max_len_list, len(model_list))
126
+ finally:
127
+ if file_save_path:
128
+ wb.save(file_save_path)
129
+ return ResponseMixin.success(msg="请求成功")
130
+ bytes_io = io.BytesIO()
131
+ wb.save(bytes_io)
132
+ bytes_io.seek(0)
133
+
134
+ return ResponseMixin.stream(bytes_io, is_xlsx=True)
135
+
136
+ async def import_xlsx(
137
+ self,
138
+ file: UploadFile,
139
+ headers: List[str],
140
+ # [
141
+ # "name",
142
+ # ("is_male", {"男": True, "女": False} 或者 方法, {"额外字段": 方法}, ...),
143
+ # ]
144
+ # 方法(默认传excel的值)
145
+ fields: List[Union[str, dict, tuple, list]],
146
+ combine_fields: Optional[List[Dict[str, any]]] = None,
147
+ model_class: Optional[Type[Model]] = None,
148
+ create_schema: Optional[Type[BaseModel]] = None,
149
+ # storage_path: Union[str, Path],
150
+ # rpc_param: Union[Dict[str, Dict[str, List[Union[str, tuple]]]], Type[RPCParam]] = None,
151
+ modules: str = "openpyxl",
152
+ ) -> JSONResponse:
153
+ """
154
+ fields: 方法(默认传excel的值)
155
+ 例如:
156
+ [
157
+ "name", # 传入值是 name 字段的值
158
+ ("is_male", {"男": True, "女": False} 或者 方法, {"额外字段": 方法}, ...),
159
+ # 值 "男" 获取为bool值,不在字典里为None, 页可以自定义 同步或异步方法 获取值
160
+ ]
161
+ """
162
+ limit_modules = ["openpyxl"]
163
+ if modules not in limit_modules:
164
+ return ResponseMixin.error(msg=f"export xlsx modules only import {'、'.join(limit_modules)}")
165
+
166
+ if not file:
167
+ return ResponseMixin.fail(msg=f"请先选择合适的文件")
168
+
169
+ if not model_class:
170
+ model_class = self.model_class
171
+ if not create_schema:
172
+ create_schema = common_schema_factory(model_class, name=f"{model_class.__name__}ExcelImportSchema")
173
+
174
+ with NamedTemporaryFile() as tmp2:
175
+ tmp2.write(await file.read())
176
+ try:
177
+ wb = importlib.import_module(modules).load_workbook(tmp2, read_only=True, data_only=True)
178
+ except Exception:
179
+ return ResponseMixin.error(msg=f"please pip install {modules}")
180
+
181
+ try:
182
+ ws = wb.active
183
+
184
+ header_row = ws[1]
185
+ header_list = []
186
+ for msg in header_row:
187
+ header_list.append(str(msg.value).replace(" ", ''))
188
+
189
+ if len(header_list) != len(headers):
190
+ return ResponseMixin.fail(message="文件首行长度校验错误")
191
+
192
+ if not operator.eq(header_list, headers):
193
+ return ResponseMixin.fail(message="文件首行内容校验错误")
194
+
195
+ # if ws.max_row < 2:
196
+ # return ResponseMixin.fail(msg="导入数据不能为空")
197
+
198
+ create_list = []
199
+ effective_row = 0
200
+ for row in range(2, ws.max_row + 1):
201
+ data = {}
202
+ # data_schema = {}
203
+ row_data = ws[row]
204
+ if await self.excel_row_is_empty(row_data):
205
+ continue
206
+ effective_row += 1
207
+ for col, field_input in enumerate(fields):
208
+ if type(field_input) in [str, int]:
209
+ data[field_input] = row_data[col].value
210
+ # data_schema[field_input] = (type(row_data[col].value), ...)
211
+
212
+ if type(field_input) == tuple or type(field_input) == list:
213
+ key = field_input[0]
214
+ val = field_input[1]
215
+ required_doc = {}
216
+ if len(field_input) > 2:
217
+ required_doc = field_input[2]
218
+ if required_doc == "required":
219
+ required_doc = {"required": True}
220
+ if type(val) == dict:
221
+ model_val = val.get(row_data[col].value)
222
+ if not model_val and required_doc.get("required"):
223
+ return ResponseMixin.fail(
224
+ msg=required_doc.get("error",
225
+ "") or f"第{row}行{self.get_field_description(key)}不能为空")
226
+ data[key] = model_val
227
+ # data_schema[key] = (type(model_val), ...)
228
+ elif hasattr(val, "__call__"):
229
+ if is_async_callable(val):
230
+ model_val = await val(row_data[col].value)
231
+ else:
232
+ model_val = val(row_data[col].value)
233
+ data[key] = model_val
234
+ # data_schema[key] = (type(model_val), ...)
235
+ else:
236
+ raise NotImplemented
237
+ else:
238
+ raise NotImplemented
239
+ for combine_field in combine_fields:
240
+ field = combine_field.get("field", None)
241
+ value = combine_field.get("value", None)
242
+ function = combine_field.get("function", None)
243
+ args = combine_field.get("args", None)
244
+ if not field or (not function and not value):
245
+ continue
246
+ if value:
247
+ data[field] = value
248
+ else:
249
+ if not args:
250
+ if is_async_callable(function):
251
+ model_val = await function()
252
+ else:
253
+ model_val = function()
254
+ else:
255
+ args_list = []
256
+ for arg in args:
257
+ args_list.append(data.get(arg, ""))
258
+ if is_async_callable(function):
259
+ model_val = await function(*args_list)
260
+ else:
261
+ model_val = function(*args_list)
262
+ data[field] = model_val
263
+ try:
264
+ create_obj = model_class(**create_schema(**data).dict(exclude_unset=True))
265
+ except ValidationError as e:
266
+ error_field = e.errors()[0].get('loc')[0]
267
+ description = self.get_field_description(error_field)
268
+ if not data.get(error_field):
269
+ return ResponseMixin.fail(message=f"第{row}行{description}不能为空")
270
+ return ResponseMixin.fail(message=f"第{row}行{description}填写错误")
271
+ await self.check_unique_field(create_obj, model_class=model_class)
272
+ create_list.append(create_obj)
273
+
274
+ await model_class.bulk_create(create_list)
275
+ finally:
276
+ wb.close()
277
+ if effective_row == 0:
278
+ return ResponseMixin.fail(message="导入数据不能为空")
279
+ return ResponseMixin.success(msg='创建成功')
280
+
281
+ @staticmethod
282
+ async def excel_row_is_empty(row_list) -> bool:
283
+ is_empty = True
284
+ for row in row_list:
285
+ if row.value is not None:
286
+ return False
287
+
288
+ return is_empty
289
+
290
+ async def excel_model(
291
+ self,
292
+ headers: List[str] = None,
293
+ model_class: Optional[Model] = None,
294
+ excel_model_path: Optional[str] = None,
295
+ modules: str = "openpyxl",
296
+ title: Optional[str] = None,
297
+ ) -> Union[FileResponse, StreamingResponse]:
298
+ if excel_model_path:
299
+ return FileResponse(
300
+ path=excel_model_path,
301
+ filename="导入模板.xlsx",
302
+ media_type="xlsx",
303
+ )
304
+
305
+ limit_modules = ["openpyxl", "xlsxwriter"]
306
+ if modules not in limit_modules:
307
+ return ResponseMixin.error(msg=f"export xlsx modules only import {'、'.join(limit_modules)}")
308
+ try:
309
+ wb = importlib.import_module(modules).Workbook()
310
+ except Exception:
311
+ return ResponseMixin.error(msg=f"please pip install {modules}")
312
+ if modules == "openpyxl":
313
+ def write(sh, row, col, value):
314
+ sh.cell(row, col).value = value
315
+
316
+ start_col = 1
317
+ start_row = 1
318
+ else:
319
+ def write(sh, row, col, value):
320
+ sh.write(row, col, value)
321
+
322
+ start_col = 0
323
+ start_row = 0
324
+ try:
325
+ sh = wb.active
326
+ sh.title = title if title else f'{model_class._meta.table_description}'
327
+
328
+ for col, header in enumerate(headers, start_col):
329
+ write(sh, start_row, col, header)
330
+
331
+ finally:
332
+ bytes_io = io.BytesIO()
333
+ wb.save(bytes_io)
334
+ bytes_io.seek(0)
335
+
336
+ return ResponseMixin.stream(bytes_io, is_xlsx=True)
@@ -0,0 +1,50 @@
1
+ import io
2
+ import os
3
+ import zipfile
4
+ from typing import Union
5
+
6
+
7
+ class ZipUtil:
8
+
9
+ @staticmethod
10
+ def folder_to_zip(folder_path: str, zip_path: Union[str, io.BytesIO]):
11
+ """
12
+ 把文件夹打包成zip
13
+ :param folder_path: 文件夹路径
14
+ :param zip_path: 打包后zip路径
15
+ :return:
16
+ """
17
+ # 创建一个 ZIP 文件对象,使用 'w' 模式表示写入
18
+ with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
19
+ # 遍历文件夹及其子文件夹
20
+ for root, dirs, files in os.walk(folder_path):
21
+ # 处理当前目录,将其添加到压缩包中,实现打包空文件夹
22
+ relative_path = os.path.relpath(root, folder_path)
23
+ if relative_path and relative_path != ".":
24
+ # 确保相对路径不是空字符串,避免将根目录重复添加
25
+ zip_info = zipfile.ZipInfo(relative_path + '/')
26
+ zipf.writestr(zip_info, '')
27
+ # 处理文件
28
+ for file in files:
29
+ # 获取文件的完整路径
30
+ file_path = os.path.join(root, file)
31
+ # 获取文件相对于文件夹的相对路径
32
+ relative_path = os.path.relpath(file_path, folder_path)
33
+ # 将文件添加到 ZIP 文件中
34
+ zipf.write(file_path, relative_path)
35
+
36
+ @staticmethod
37
+ def folder_to_zip_bytes_io(folder_path: str):
38
+ """
39
+ 把文件夹打包zip后转换成io格式
40
+ :param folder_path: 文件夹路径
41
+ :return:
42
+ """
43
+ bytes_io = io.BytesIO()
44
+ ZipUtil.folder_to_zip(folder_path, bytes_io)
45
+ bytes_io.seek(0)
46
+ return bytes_io
47
+
48
+
49
+
50
+
@@ -6,9 +6,9 @@ from starlette.responses import JSONResponse
6
6
 
7
7
  from fastgenerateapi.api_view.base_view import BaseView
8
8
  from fastgenerateapi.schemas_factory import response_factory, get_one_schema_factory
9
- from fastgenerateapi.settings.register_settings import settings
9
+ from fastgenerateapi.settings.all_settings import settings
10
10
  from fastgenerateapi.utils.exception import NOT_FOUND
11
- from fastgenerateapi.utils.parse_str import parse_str_to_bool
11
+ from fastgenerateapi.utils.str_util import parse_str_to_bool
12
12
 
13
13
 
14
14
  class SwitchView(BaseView):
@@ -10,9 +10,9 @@ from tortoise.transactions import atomic
10
10
  from fastgenerateapi.api_view.base_view import BaseView
11
11
  from fastgenerateapi.api_view.mixin.save_mixin import SaveMixin
12
12
  from fastgenerateapi.data_type.data_type import DEPENDENCIES
13
- from fastgenerateapi.pydantic_utils.base_model import IDList
13
+ from fastgenerateapi.pydantic_utils.base_model import IdList
14
14
  from fastgenerateapi.schemas_factory import response_factory
15
- from fastgenerateapi.settings.register_settings import settings
15
+ from fastgenerateapi.settings.all_settings import settings
16
16
  from fastgenerateapi.utils.exception import NOT_FOUND
17
17
 
18
18
 
@@ -20,7 +20,7 @@ class UpdateRelationView(BaseView, SaveMixin):
20
20
 
21
21
  path_id_name: str
22
22
  relation_id_name: str
23
- update_relation_schema: Optional[Type[BaseModel]] = IDList
23
+ update_relation_schema: Optional[Type[BaseModel]] = IdList
24
24
  update_relation_route: Union[bool, DEPENDENCIES] = True
25
25
  """
26
26
  path_id_name: 路径id在模型中对应的字段名
@@ -12,7 +12,7 @@ from fastgenerateapi.api_view.mixin.save_mixin import SaveMixin
12
12
  from fastgenerateapi.data_type.data_type import DEPENDENCIES, CALLABLE
13
13
  from fastgenerateapi.pydantic_utils.base_model import BaseModel
14
14
  from fastgenerateapi.schemas_factory import update_schema_factory, get_one_schema_factory, response_factory
15
- from fastgenerateapi.settings.register_settings import settings
15
+ from fastgenerateapi.settings.all_settings import settings
16
16
  from fastgenerateapi.utils.exception import NOT_FOUND
17
17
 
18
18
 
@@ -1,4 +1,4 @@
1
- from fastgenerateapi.settings.register_settings import settings
1
+ from fastgenerateapi.settings.all_settings import settings
2
2
 
3
3
 
4
4
  def get_one_cache_decorator(func):
@@ -1,9 +1,50 @@
1
- from typing import Union, Any
1
+ from datetime import datetime, date, time
2
+ from typing import Union, Any, Optional
2
3
 
3
4
  from tortoise.expressions import Q
4
5
  from tortoise.queryset import QuerySet
5
6
 
6
- from fastgenerateapi.settings.register_settings import settings
7
+ from fastgenerateapi.settings.all_settings import settings
8
+
9
+
10
+ class FilterUtils:
11
+
12
+ @staticmethod
13
+ def date_to_datetime_23(date_value: Union[None, str, date, datetime]) -> Optional[datetime]:
14
+ """
15
+ 使用场景:如创建时间筛选2025-01-01 至 2025-12-31;由于储存数据为datetime类型,会导致2025-12-31当天数据不包含
16
+ :param cls:
17
+ :param date_value:
18
+ :return:
19
+ """
20
+ if date_value is None:
21
+ return None
22
+
23
+ # 如果 date_value 是字符串,尝试解析为日期
24
+ if isinstance(date_value, str):
25
+ try:
26
+ # 尝试解析为 date 类型,这里假设日期字符串的格式是 YYYY-MM-DD
27
+ parsed_date = datetime.strptime(date_value, '%Y-%m-%d').date()
28
+ except ValueError:
29
+ # 如果解析失败,则尝试解析为 datetime 类型(可能包含时间信息)
30
+ try:
31
+ parsed_datetime = datetime.strptime(date_value, '%Y-%m-%d %H:%M:%S')
32
+ parsed_date = parsed_datetime.date()
33
+ except ValueError:
34
+ # 如果仍然失败,则返回 None
35
+ return None
36
+
37
+ # 如果 date_value 已经是 date 类型,直接使用
38
+ elif isinstance(date_value, date):
39
+ parsed_date = date_value
40
+
41
+ # 其他类型返回 None
42
+ else:
43
+ return None
44
+
45
+ # 将日期加上时间 "23:59:59" 转换为 datetime 类型
46
+ result_datetime = datetime.combine(parsed_date, datetime.min.time().replace(hour=23, minute=59, second=59))
47
+ return result_datetime
7
48
 
8
49
 
9
50
  class BaseFilter:
@@ -14,37 +55,36 @@ class BaseFilter:
14
55
  def __init__(self, filter_str: Union[str, tuple]):
15
56
  """
16
57
  :param filter_str: Union[str, tuple]
58
+ 当tuple时,第一个为str,后面参数无顺序和数量要求,可以是 类型、重命名字符串、用于修改传值的方法
17
59
  example: name__contains or (create_at__gt, datetime) or (create_at__gt, datetime, create_time)
18
60
  """
19
61
  field_type = str
20
62
  model_field = filter_str
21
- filter_field = filter_str
22
- if isinstance(filter_str, str):
23
- if settings.app_settings.FILTER_UNDERLINE_WHETHER_DOUBLE_TO_SINGLE and "__" in filter_str:
24
- filter_field = filter_str.replace("__", "_")
63
+ filter_field = None
64
+ filter_func = None
25
65
  # 判断filter表达式的类型
26
66
  if isinstance(filter_str, tuple):
27
67
  model_field = filter_str[0]
28
- if len(filter_str) == 2:
29
- if type(filter_str[1]) == type:
30
- field_type = filter_str[1]
31
- if settings.app_settings.FILTER_UNDERLINE_WHETHER_DOUBLE_TO_SINGLE and "__" in filter_str:
32
- filter_field = model_field.replace("__", "_")
33
- else:
34
- filter_field = model_field
35
- else:
36
- filter_field = filter_str[1]
37
- elif len(filter_str) > 2:
38
- if type(filter_str[1]) == type:
39
- field_type = filter_str[1]
40
- filter_field = filter_str[2]
41
- else:
42
- field_type = filter_str[2]
43
- filter_field = filter_str[1]
68
+ filter_field = filter_str[0]
69
+ for f in filter_str[1:]:
70
+ if type(f) == type:
71
+ field_type = f
72
+ continue
73
+ if type(f) == str:
74
+ filter_field = f
75
+ continue
76
+ if callable(f):
77
+ filter_func = f
78
+ if not filter_field:
79
+ if settings.app_settings.FILTER_UNDERLINE_WHETHER_DOUBLE_TO_SINGLE:
80
+ filter_field = model_field.replace("__", "_")
81
+ else:
82
+ filter_field = model_field
44
83
 
84
+ self.field_type = field_type
45
85
  self.model_field = model_field
46
86
  self.filter_field = filter_field
47
- self.field_type = field_type
87
+ self.filter_func = filter_func
48
88
 
49
89
  def generate_q(self, value: Union[str, list, bool]) -> Q:
50
90
  """
@@ -52,8 +92,8 @@ class BaseFilter:
52
92
  :param value:
53
93
  :return:
54
94
  """
55
- if isinstance(value, str):
56
- if value.upper() in ["NONE", "NULL", "NIL", "0"]:
95
+ if isinstance(value, (str, datetime, date, time)):
96
+ if hasattr(value, "upper") and value.upper() in ["NONE", "NULL", "NIL"]:
57
97
  return eval(f"Q({self.model_field}={None})")
58
98
  return eval(f"Q({self.model_field}='{value}')")
59
99
  return eval(f"Q({self.model_field}={value})")
@@ -65,6 +105,8 @@ class BaseFilter:
65
105
  :param value:
66
106
  :return:
67
107
  """
108
+ if self.filter_func:
109
+ value = self.filter_func(value)
68
110
  queryset = queryset.filter(self.generate_q(value=value))
69
111
  return queryset
70
112
 
@@ -90,7 +132,7 @@ class FilterController:
90
132
  for k in values:
91
133
  f = self.filter_map.get(k, None)
92
134
  v = values[k]
93
- if f is not None and (isinstance(v, bool) or v):
135
+ if f is not None and (v or v == 0 or isinstance(v, bool)):
94
136
  queryset = f.query(queryset=queryset, value=v)
95
137
 
96
138
  return queryset
@@ -1,7 +1,7 @@
1
1
  import inspect
2
2
  from typing import Union
3
3
 
4
- from fastgenerateapi.settings.register_settings import settings
4
+ from fastgenerateapi.settings.all_settings import settings
5
5
  from pydantic import BaseModel
6
6
 
7
7
  from fastgenerateapi.api_view.mixin.response_mixin import ResponseMixin
@@ -80,17 +80,17 @@ class RouterController:
80
80
  else:
81
81
  middle_field = "_".join(middle_list)
82
82
  if method == "GET":
83
- prefix_field = settings.app_settings.RESTFUL_GET_ROUTER_ADD_PREFIX
84
- suffix_field = settings.app_settings.RESTFUL_GET_ROUTER_ADD_SUFFIX
83
+ prefix_field = settings.app_settings.RESTFUL_GET_ROUTER_ADD_PREFIX or ""
84
+ suffix_field = settings.app_settings.RESTFUL_GET_ROUTER_ADD_SUFFIX or ""
85
85
  elif method == "POST":
86
- prefix_field = settings.app_settings.RESTFUL_POST_ROUTER_ADD_PREFIX
87
- suffix_field = settings.app_settings.RESTFUL_POST_ROUTER_ADD_SUFFIX
86
+ prefix_field = settings.app_settings.RESTFUL_POST_ROUTER_ADD_PREFIX or ""
87
+ suffix_field = settings.app_settings.RESTFUL_POST_ROUTER_ADD_SUFFIX or ""
88
88
  elif method == "PUT":
89
- prefix_field = settings.app_settings.RESTFUL_PUT_ROUTER_ADD_PREFIX
90
- suffix_field = settings.app_settings.RESTFUL_PUT_ROUTER_ADD_SUFFIX
89
+ prefix_field = settings.app_settings.RESTFUL_PUT_ROUTER_ADD_PREFIX or ""
90
+ suffix_field = settings.app_settings.RESTFUL_PUT_ROUTER_ADD_SUFFIX or ""
91
91
  elif method == "DELETE":
92
- prefix_field = settings.app_settings.RESTFUL_DELETE_ROUTER_ADD_PREFIX
93
- suffix_field = settings.app_settings.RESTFUL_DELETE_ROUTER_ADD_SUFFIX
92
+ prefix_field = settings.app_settings.RESTFUL_DELETE_ROUTER_ADD_PREFIX or ""
93
+ suffix_field = settings.app_settings.RESTFUL_DELETE_ROUTER_ADD_SUFFIX or ""
94
94
  else:
95
95
  prefix_field = ""
96
96
  suffix_field = ""
@@ -51,4 +51,4 @@ class RPCController(ResponseMixin):
51
51
  data = {}
52
52
  for base_param in self.data:
53
53
  data.setdefault(base_param.request_param_key, []).extend(base_param.model_field_value)
54
- return data
54
+ return data
@@ -1,4 +1,4 @@
1
- from fastgenerateapi.settings.register_settings import settings
1
+ from fastgenerateapi.settings.all_settings import settings
2
2
 
3
3
 
4
4
  class WsRouter: