fastadmin 0.3.4__tar.gz → 0.3.5__tar.gz
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.
- {fastadmin-0.3.4 → fastadmin-0.3.5}/PKG-INFO +1 -1
- {fastadmin-0.3.4 → fastadmin-0.3.5}/fastadmin/api/frameworks/django/app/api.py +15 -1
- {fastadmin-0.3.4 → fastadmin-0.3.5}/fastadmin/api/frameworks/fastapi/api.py +26 -3
- {fastadmin-0.3.4 → fastadmin-0.3.5}/fastadmin/api/frameworks/flask/api.py +15 -1
- {fastadmin-0.3.4 → fastadmin-0.3.5}/fastadmin/api/service.py +67 -34
- {fastadmin-0.3.4 → fastadmin-0.3.5}/fastadmin/models/base.py +47 -0
- {fastadmin-0.3.4 → fastadmin-0.3.5}/fastadmin/models/helpers.py +6 -0
- {fastadmin-0.3.4 → fastadmin-0.3.5}/fastadmin/models/orms/django.py +16 -4
- {fastadmin-0.3.4 → fastadmin-0.3.5}/fastadmin/models/orms/ponyorm.py +13 -2
- {fastadmin-0.3.4 → fastadmin-0.3.5}/fastadmin/models/orms/sqlalchemy.py +32 -2
- {fastadmin-0.3.4 → fastadmin-0.3.5}/fastadmin/models/orms/tortoise.py +13 -2
- {fastadmin-0.3.4 → fastadmin-0.3.5}/pyproject.toml +1 -1
- {fastadmin-0.3.4 → fastadmin-0.3.5}/LICENSE +0 -0
- {fastadmin-0.3.4 → fastadmin-0.3.5}/README.md +0 -0
- {fastadmin-0.3.4 → fastadmin-0.3.5}/fastadmin/__init__.py +0 -0
- {fastadmin-0.3.4 → fastadmin-0.3.5}/fastadmin/api/__init__.py +0 -0
- {fastadmin-0.3.4 → fastadmin-0.3.5}/fastadmin/api/exceptions.py +0 -0
- {fastadmin-0.3.4 → fastadmin-0.3.5}/fastadmin/api/frameworks/__init__.py +0 -0
- {fastadmin-0.3.4 → fastadmin-0.3.5}/fastadmin/api/frameworks/django/__init__.py +0 -0
- {fastadmin-0.3.4 → fastadmin-0.3.5}/fastadmin/api/frameworks/django/app/__init__.py +0 -0
- {fastadmin-0.3.4 → fastadmin-0.3.5}/fastadmin/api/frameworks/django/app/urls.py +0 -0
- {fastadmin-0.3.4 → fastadmin-0.3.5}/fastadmin/api/frameworks/django/app/views.py +0 -0
- {fastadmin-0.3.4 → fastadmin-0.3.5}/fastadmin/api/frameworks/fastapi/__init__.py +0 -0
- {fastadmin-0.3.4 → fastadmin-0.3.5}/fastadmin/api/frameworks/fastapi/app.py +0 -0
- {fastadmin-0.3.4 → fastadmin-0.3.5}/fastadmin/api/frameworks/fastapi/views.py +0 -0
- {fastadmin-0.3.4 → fastadmin-0.3.5}/fastadmin/api/frameworks/flask/__init__.py +0 -0
- {fastadmin-0.3.4 → fastadmin-0.3.5}/fastadmin/api/frameworks/flask/app.py +0 -0
- {fastadmin-0.3.4 → fastadmin-0.3.5}/fastadmin/api/frameworks/flask/views.py +0 -0
- {fastadmin-0.3.4 → fastadmin-0.3.5}/fastadmin/api/helpers.py +0 -0
- {fastadmin-0.3.4 → fastadmin-0.3.5}/fastadmin/api/schemas.py +0 -0
- {fastadmin-0.3.4 → fastadmin-0.3.5}/fastadmin/models/__init__.py +0 -0
- {fastadmin-0.3.4 → fastadmin-0.3.5}/fastadmin/models/decorators.py +0 -0
- {fastadmin-0.3.4 → fastadmin-0.3.5}/fastadmin/models/orms/__init__.py +0 -0
- {fastadmin-0.3.4 → fastadmin-0.3.5}/fastadmin/models/schemas.py +0 -0
- {fastadmin-0.3.4 → fastadmin-0.3.5}/fastadmin/settings.py +0 -0
- {fastadmin-0.3.4 → fastadmin-0.3.5}/fastadmin/static/assets/worker-6Z7niv9l.js +0 -0
- {fastadmin-0.3.4 → fastadmin-0.3.5}/fastadmin/static/assets/worker-C151k0-L.js +0 -0
- {fastadmin-0.3.4 → fastadmin-0.3.5}/fastadmin/static/assets/worker-C9KPOOKD.js +0 -0
- {fastadmin-0.3.4 → fastadmin-0.3.5}/fastadmin/static/assets/worker-D1fCgYB7.js +0 -0
- {fastadmin-0.3.4 → fastadmin-0.3.5}/fastadmin/static/assets/worker-D3TWcJOI.js +0 -0
- {fastadmin-0.3.4 → fastadmin-0.3.5}/fastadmin/static/assets/worker-DRNcaZ-V.js +0 -0
- {fastadmin-0.3.4 → fastadmin-0.3.5}/fastadmin/static/assets/worker-MF2p-l5_.js +0 -0
- {fastadmin-0.3.4 → fastadmin-0.3.5}/fastadmin/static/images/favicon.png +0 -0
- {fastadmin-0.3.4 → fastadmin-0.3.5}/fastadmin/static/images/header-logo.svg +0 -0
- {fastadmin-0.3.4 → fastadmin-0.3.5}/fastadmin/static/images/sign-in-logo.svg +0 -0
- {fastadmin-0.3.4 → fastadmin-0.3.5}/fastadmin/static/index.html +0 -0
- {fastadmin-0.3.4 → fastadmin-0.3.5}/fastadmin/static/index.min.css +0 -0
- {fastadmin-0.3.4 → fastadmin-0.3.5}/fastadmin/static/index.min.js +0 -0
- {fastadmin-0.3.4 → fastadmin-0.3.5}/fastadmin/templates/index.html +0 -0
|
@@ -64,6 +64,7 @@ async def sign_in(request: HttpRequest) -> JsonResponse:
|
|
|
64
64
|
session_id = await api_service.sign_in(
|
|
65
65
|
request.COOKIES.get(settings.ADMIN_SESSION_ID_KEY, None),
|
|
66
66
|
payload,
|
|
67
|
+
request=request,
|
|
67
68
|
)
|
|
68
69
|
|
|
69
70
|
response = JsonResponse({})
|
|
@@ -111,7 +112,10 @@ async def me(request: HttpRequest) -> JsonResponse:
|
|
|
111
112
|
if not user_id:
|
|
112
113
|
raise AdminApiException(401, "User is not authenticated.")
|
|
113
114
|
obj = await api_service.get(
|
|
114
|
-
request.COOKIES.get(settings.ADMIN_SESSION_ID_KEY, None),
|
|
115
|
+
request.COOKIES.get(settings.ADMIN_SESSION_ID_KEY, None),
|
|
116
|
+
settings.ADMIN_USER_MODEL,
|
|
117
|
+
user_id,
|
|
118
|
+
request=request,
|
|
115
119
|
)
|
|
116
120
|
return JsonResponse(obj)
|
|
117
121
|
|
|
@@ -143,6 +147,7 @@ async def dashboard_widget(request: HttpRequest, model: str) -> JsonResponse:
|
|
|
143
147
|
min_x_field=min_x_field,
|
|
144
148
|
max_x_field=max_x_field,
|
|
145
149
|
period_x_field=period_x_field,
|
|
150
|
+
request=request,
|
|
146
151
|
)
|
|
147
152
|
return JsonResponse(data)
|
|
148
153
|
except AdminApiException as e:
|
|
@@ -182,6 +187,7 @@ async def list_objs(request: HttpRequest, model: str) -> JsonResponse:
|
|
|
182
187
|
filters=list_filters,
|
|
183
188
|
offset=offset,
|
|
184
189
|
limit=limit,
|
|
190
|
+
request=request,
|
|
185
191
|
)
|
|
186
192
|
return JsonResponse(
|
|
187
193
|
{
|
|
@@ -212,6 +218,7 @@ async def get(request: HttpRequest, model: str, id: UUID | int | str) -> JsonRes
|
|
|
212
218
|
request.COOKIES.get(settings.ADMIN_SESSION_ID_KEY, None),
|
|
213
219
|
model,
|
|
214
220
|
id,
|
|
221
|
+
request=request,
|
|
215
222
|
)
|
|
216
223
|
return JsonResponse(obj)
|
|
217
224
|
|
|
@@ -234,6 +241,7 @@ async def add(request: HttpRequest, model: str) -> JsonResponse:
|
|
|
234
241
|
request.COOKIES.get(settings.ADMIN_SESSION_ID_KEY, None),
|
|
235
242
|
model,
|
|
236
243
|
json.loads(request.body),
|
|
244
|
+
request=request,
|
|
237
245
|
)
|
|
238
246
|
return JsonResponse(obj)
|
|
239
247
|
except AdminApiException as e:
|
|
@@ -257,6 +265,7 @@ async def change_password(request: HttpRequest, id: UUID | int | str) -> JsonRes
|
|
|
257
265
|
request.COOKIES.get(settings.ADMIN_SESSION_ID_KEY, None),
|
|
258
266
|
id,
|
|
259
267
|
json.loads(request.body),
|
|
268
|
+
request=request,
|
|
260
269
|
)
|
|
261
270
|
return JsonResponse(id, safe=False)
|
|
262
271
|
|
|
@@ -283,6 +292,7 @@ async def change(request: HttpRequest, model: str, id: UUID | int | str) -> Json
|
|
|
283
292
|
model,
|
|
284
293
|
id,
|
|
285
294
|
json.loads(request.body),
|
|
295
|
+
request=request,
|
|
286
296
|
)
|
|
287
297
|
return JsonResponse(obj)
|
|
288
298
|
|
|
@@ -319,6 +329,7 @@ async def export(request: HttpRequest, model: str) -> JsonResponse:
|
|
|
319
329
|
search=search,
|
|
320
330
|
sort_by=sort_by,
|
|
321
331
|
filters=list_filters,
|
|
332
|
+
request=request,
|
|
322
333
|
)
|
|
323
334
|
response = StreamingHttpResponse(stream, content_type=content_type)
|
|
324
335
|
response.headers["Content-Disposition"] = f'attachment; filename="{file_name}"'
|
|
@@ -349,6 +360,7 @@ async def delete(
|
|
|
349
360
|
request.COOKIES.get(settings.ADMIN_SESSION_ID_KEY, None),
|
|
350
361
|
model,
|
|
351
362
|
id,
|
|
363
|
+
request=request,
|
|
352
364
|
)
|
|
353
365
|
return JsonResponse(deleted_id, safe=False)
|
|
354
366
|
|
|
@@ -378,6 +390,7 @@ async def action(
|
|
|
378
390
|
model,
|
|
379
391
|
action,
|
|
380
392
|
payload,
|
|
393
|
+
request=request,
|
|
381
394
|
)
|
|
382
395
|
return JsonResponse({})
|
|
383
396
|
|
|
@@ -397,5 +410,6 @@ async def configuration(request: HttpRequest) -> JsonResponse:
|
|
|
397
410
|
|
|
398
411
|
obj = await api_service.get_configuration(
|
|
399
412
|
request.COOKIES.get(settings.ADMIN_SESSION_ID_KEY, None),
|
|
413
|
+
request=request,
|
|
400
414
|
)
|
|
401
415
|
return JsonResponse(asdict(obj))
|
|
@@ -35,6 +35,7 @@ async def sign_in(
|
|
|
35
35
|
session_id = await api_service.sign_in(
|
|
36
36
|
request.cookies.get(settings.ADMIN_SESSION_ID_KEY, None),
|
|
37
37
|
payload,
|
|
38
|
+
request=request,
|
|
38
39
|
)
|
|
39
40
|
|
|
40
41
|
response.set_cookie(settings.ADMIN_SESSION_ID_KEY, value=session_id, httponly=True)
|
|
@@ -78,7 +79,10 @@ async def me(
|
|
|
78
79
|
if not user_id:
|
|
79
80
|
raise AdminApiException(401, "User is not authenticated.")
|
|
80
81
|
return await api_service.get(
|
|
81
|
-
request.cookies.get(settings.ADMIN_SESSION_ID_KEY, None),
|
|
82
|
+
request.cookies.get(settings.ADMIN_SESSION_ID_KEY, None),
|
|
83
|
+
settings.ADMIN_USER_MODEL,
|
|
84
|
+
user_id,
|
|
85
|
+
request=request,
|
|
82
86
|
)
|
|
83
87
|
except AdminApiException as e:
|
|
84
88
|
raise HTTPException(e.status_code, detail=e.detail) from None
|
|
@@ -107,6 +111,7 @@ async def dashboard_widget(
|
|
|
107
111
|
min_x_field=min_x_field,
|
|
108
112
|
max_x_field=max_x_field,
|
|
109
113
|
period_x_field=period_x_field,
|
|
114
|
+
request=request,
|
|
110
115
|
)
|
|
111
116
|
return data
|
|
112
117
|
except AdminApiException as e:
|
|
@@ -146,6 +151,7 @@ async def list_objs(
|
|
|
146
151
|
filters=list_filters,
|
|
147
152
|
offset=offset,
|
|
148
153
|
limit=limit,
|
|
154
|
+
request=request,
|
|
149
155
|
)
|
|
150
156
|
return {
|
|
151
157
|
"total": total,
|
|
@@ -172,6 +178,7 @@ async def get(
|
|
|
172
178
|
request.cookies.get(settings.ADMIN_SESSION_ID_KEY, None),
|
|
173
179
|
model,
|
|
174
180
|
id,
|
|
181
|
+
request=request,
|
|
175
182
|
)
|
|
176
183
|
except AdminApiException as e:
|
|
177
184
|
raise HTTPException(e.status_code, detail=e.detail) from None
|
|
@@ -194,6 +201,7 @@ async def add(
|
|
|
194
201
|
request.cookies.get(settings.ADMIN_SESSION_ID_KEY, None),
|
|
195
202
|
model,
|
|
196
203
|
payload,
|
|
204
|
+
request=request,
|
|
197
205
|
)
|
|
198
206
|
except AdminApiException as e:
|
|
199
207
|
raise HTTPException(e.status_code, detail=e.detail) from None
|
|
@@ -212,7 +220,12 @@ async def change_password(
|
|
|
212
220
|
:return: An object.
|
|
213
221
|
"""
|
|
214
222
|
try:
|
|
215
|
-
await api_service.change_password(
|
|
223
|
+
await api_service.change_password(
|
|
224
|
+
request.cookies.get(settings.ADMIN_SESSION_ID_KEY, None),
|
|
225
|
+
id,
|
|
226
|
+
payload,
|
|
227
|
+
request=request,
|
|
228
|
+
)
|
|
216
229
|
return id
|
|
217
230
|
except AdminApiException as e:
|
|
218
231
|
raise HTTPException(e.status_code, detail=e.detail) from None
|
|
@@ -233,7 +246,13 @@ async def change(
|
|
|
233
246
|
:return: An object.
|
|
234
247
|
"""
|
|
235
248
|
try:
|
|
236
|
-
return await api_service.change(
|
|
249
|
+
return await api_service.change(
|
|
250
|
+
request.cookies.get(settings.ADMIN_SESSION_ID_KEY, None),
|
|
251
|
+
model,
|
|
252
|
+
id,
|
|
253
|
+
payload,
|
|
254
|
+
request=request,
|
|
255
|
+
)
|
|
237
256
|
except AdminApiException as e:
|
|
238
257
|
raise HTTPException(e.status_code, detail=e.detail) from None
|
|
239
258
|
|
|
@@ -268,6 +287,7 @@ async def export(
|
|
|
268
287
|
search=search,
|
|
269
288
|
sort_by=sort_by,
|
|
270
289
|
filters=list_filters,
|
|
290
|
+
request=request,
|
|
271
291
|
)
|
|
272
292
|
headers = {"Content-Disposition": f'attachment; filename="{file_name}"'}
|
|
273
293
|
return StreamingResponse(
|
|
@@ -296,6 +316,7 @@ async def delete(
|
|
|
296
316
|
request.cookies.get(settings.ADMIN_SESSION_ID_KEY, None),
|
|
297
317
|
model,
|
|
298
318
|
id,
|
|
319
|
+
request=request,
|
|
299
320
|
)
|
|
300
321
|
except AdminApiException as e:
|
|
301
322
|
raise HTTPException(e.status_code, detail=e.detail) from None
|
|
@@ -321,6 +342,7 @@ async def action(
|
|
|
321
342
|
model,
|
|
322
343
|
action,
|
|
323
344
|
payload,
|
|
345
|
+
request=request,
|
|
324
346
|
)
|
|
325
347
|
except AdminApiException as e:
|
|
326
348
|
raise HTTPException(e.status_code, detail=e.detail) from None
|
|
@@ -337,4 +359,5 @@ async def configuration(
|
|
|
337
359
|
"""
|
|
338
360
|
return await api_service.get_configuration(
|
|
339
361
|
request.cookies.get(settings.ADMIN_SESSION_ID_KEY, None),
|
|
362
|
+
request=request,
|
|
340
363
|
)
|
|
@@ -30,6 +30,7 @@ async def sign_in() -> Response:
|
|
|
30
30
|
session_id = await api_service.sign_in(
|
|
31
31
|
request.cookies.get(settings.ADMIN_SESSION_ID_KEY, None),
|
|
32
32
|
payload,
|
|
33
|
+
request=request,
|
|
33
34
|
)
|
|
34
35
|
response = make_response({})
|
|
35
36
|
response.set_cookie(settings.ADMIN_SESSION_ID_KEY, value=session_id, httponly=True)
|
|
@@ -74,7 +75,10 @@ async def me() -> dict:
|
|
|
74
75
|
if not user_id:
|
|
75
76
|
raise AdminApiException(401, "User is not authenticated.")
|
|
76
77
|
return await api_service.get(
|
|
77
|
-
request.cookies.get(settings.ADMIN_SESSION_ID_KEY, None),
|
|
78
|
+
request.cookies.get(settings.ADMIN_SESSION_ID_KEY, None),
|
|
79
|
+
settings.ADMIN_USER_MODEL,
|
|
80
|
+
user_id,
|
|
81
|
+
request=request,
|
|
78
82
|
)
|
|
79
83
|
except AdminApiException as e:
|
|
80
84
|
http_exception = HTTPException(e.detail)
|
|
@@ -104,6 +108,7 @@ async def dashboard_widget(model: str) -> dict:
|
|
|
104
108
|
min_x_field=min_x_field,
|
|
105
109
|
max_x_field=max_x_field,
|
|
106
110
|
period_x_field=period_x_field,
|
|
111
|
+
request=request,
|
|
107
112
|
)
|
|
108
113
|
return data
|
|
109
114
|
except AdminApiException as e:
|
|
@@ -142,6 +147,7 @@ async def list_objs(model: str) -> dict:
|
|
|
142
147
|
filters=list_filters,
|
|
143
148
|
offset=offset,
|
|
144
149
|
limit=limit,
|
|
150
|
+
request=request,
|
|
145
151
|
)
|
|
146
152
|
return {
|
|
147
153
|
"total": total,
|
|
@@ -174,6 +180,7 @@ async def get(model: str, id: UUID | int | str) -> dict:
|
|
|
174
180
|
request.cookies.get(settings.ADMIN_SESSION_ID_KEY, None),
|
|
175
181
|
model,
|
|
176
182
|
id,
|
|
183
|
+
request=request,
|
|
177
184
|
)
|
|
178
185
|
except AdminApiException as e:
|
|
179
186
|
http_exception = HTTPException(e.detail)
|
|
@@ -195,6 +202,7 @@ async def add(model: str) -> dict:
|
|
|
195
202
|
request.cookies.get(settings.ADMIN_SESSION_ID_KEY, None),
|
|
196
203
|
model,
|
|
197
204
|
payload,
|
|
205
|
+
request=request,
|
|
198
206
|
)
|
|
199
207
|
except AdminApiException as e:
|
|
200
208
|
http_exception = HTTPException(e.detail)
|
|
@@ -220,6 +228,7 @@ async def change_password(id: UUID | int | str) -> UUID | int | str:
|
|
|
220
228
|
request.cookies.get(settings.ADMIN_SESSION_ID_KEY, None),
|
|
221
229
|
id,
|
|
222
230
|
payload,
|
|
231
|
+
request=request,
|
|
223
232
|
)
|
|
224
233
|
return id
|
|
225
234
|
except AdminApiException as e:
|
|
@@ -248,6 +257,7 @@ async def change(model: str, id: UUID | int | str) -> dict:
|
|
|
248
257
|
model,
|
|
249
258
|
id,
|
|
250
259
|
payload,
|
|
260
|
+
request=request,
|
|
251
261
|
)
|
|
252
262
|
except AdminApiException as e:
|
|
253
263
|
http_exception = HTTPException(e.detail)
|
|
@@ -283,6 +293,7 @@ async def export(model: str) -> Response:
|
|
|
283
293
|
search=search,
|
|
284
294
|
sort_by=sort_by,
|
|
285
295
|
filters=list_filters,
|
|
296
|
+
request=request,
|
|
286
297
|
)
|
|
287
298
|
response = Response(stream, mimetype=content_type)
|
|
288
299
|
response.headers["Content-Disposition"] = f'attachment; filename="{file_name}"'
|
|
@@ -313,6 +324,7 @@ async def delete(
|
|
|
313
324
|
request.cookies.get(settings.ADMIN_SESSION_ID_KEY, None),
|
|
314
325
|
model,
|
|
315
326
|
id,
|
|
327
|
+
request=request,
|
|
316
328
|
)
|
|
317
329
|
except AdminApiException as e:
|
|
318
330
|
http_exception = HTTPException(e.detail)
|
|
@@ -340,6 +352,7 @@ async def action(
|
|
|
340
352
|
model,
|
|
341
353
|
action,
|
|
342
354
|
payload,
|
|
355
|
+
request=request,
|
|
343
356
|
)
|
|
344
357
|
return {}
|
|
345
358
|
except AdminApiException as e:
|
|
@@ -357,5 +370,6 @@ async def configuration() -> dict:
|
|
|
357
370
|
"""
|
|
358
371
|
obj = await api_service.get_configuration(
|
|
359
372
|
request.cookies.get(settings.ADMIN_SESSION_ID_KEY, None),
|
|
373
|
+
request=request,
|
|
360
374
|
)
|
|
361
375
|
return asdict(obj)
|
|
@@ -102,10 +102,31 @@ async def get_user_id_from_session_id(session_id: str | None) -> UUID | int | No
|
|
|
102
102
|
|
|
103
103
|
|
|
104
104
|
class ApiService:
|
|
105
|
+
@staticmethod
|
|
106
|
+
def _bind_admin_context(
|
|
107
|
+
admin_model: ModelAdmin | InlineModelAdmin | Any, request: Any | None, user: Any | None
|
|
108
|
+
) -> None:
|
|
109
|
+
if hasattr(admin_model, "set_context"):
|
|
110
|
+
admin_model.set_context(request=request, user=user)
|
|
111
|
+
|
|
112
|
+
async def _get_authenticated_user(self, session_id: str | None) -> tuple[UUID | int, Any | None]:
|
|
113
|
+
current_user_id = await get_user_id_from_session_id(session_id)
|
|
114
|
+
if not current_user_id:
|
|
115
|
+
raise AdminApiException(401, detail="User is not authenticated.")
|
|
116
|
+
|
|
117
|
+
admin_user_model = get_admin_model(settings.ADMIN_USER_MODEL)
|
|
118
|
+
current_user = (
|
|
119
|
+
await admin_user_model.get_obj(current_user_id)
|
|
120
|
+
if admin_user_model and hasattr(admin_user_model, "get_obj")
|
|
121
|
+
else None
|
|
122
|
+
)
|
|
123
|
+
return current_user_id, current_user
|
|
124
|
+
|
|
105
125
|
async def sign_in(
|
|
106
126
|
self,
|
|
107
127
|
session_id: str | None,
|
|
108
128
|
payload: SignInInputSchema,
|
|
129
|
+
request: Any | None = None,
|
|
109
130
|
) -> str:
|
|
110
131
|
model = settings.ADMIN_USER_MODEL
|
|
111
132
|
admin_model = get_admin_model(model)
|
|
@@ -118,6 +139,7 @@ class ApiService:
|
|
|
118
139
|
else:
|
|
119
140
|
authenticate_fn = sync_to_async(admin_model.authenticate) # type: ignore [arg-type]
|
|
120
141
|
|
|
142
|
+
self._bind_admin_context(admin_model, request=request, user=None)
|
|
121
143
|
user_id = await authenticate_fn(payload.username, payload.password)
|
|
122
144
|
|
|
123
145
|
if not user_id or not isinstance(user_id, int | UUID):
|
|
@@ -139,10 +161,9 @@ class ApiService:
|
|
|
139
161
|
async def sign_out(
|
|
140
162
|
self,
|
|
141
163
|
session_id: str | None,
|
|
164
|
+
request: Any | None = None,
|
|
142
165
|
) -> bool:
|
|
143
|
-
|
|
144
|
-
if not current_user_id:
|
|
145
|
-
raise AdminApiException(401, detail="User is not authenticated.")
|
|
166
|
+
_current_user_id, _current_user = await self._get_authenticated_user(session_id)
|
|
146
167
|
|
|
147
168
|
return True
|
|
148
169
|
|
|
@@ -153,10 +174,9 @@ class ApiService:
|
|
|
153
174
|
min_x_field: str | None = None,
|
|
154
175
|
max_x_field: str | None = None,
|
|
155
176
|
period_x_field: str | None = None,
|
|
177
|
+
request: Any | None = None,
|
|
156
178
|
) -> dict[str, str | int | float]:
|
|
157
|
-
|
|
158
|
-
if not current_user_id:
|
|
159
|
-
raise AdminApiException(401, detail="User is not authenticated.")
|
|
179
|
+
_current_user_id, current_user = await self._get_authenticated_user(session_id)
|
|
160
180
|
|
|
161
181
|
query_params = DashboardWidgetQuerySchema(
|
|
162
182
|
min_x_field=min_x_field,
|
|
@@ -168,6 +188,8 @@ class ApiService:
|
|
|
168
188
|
if not dashboard_widget_model:
|
|
169
189
|
raise AdminApiException(404, detail=f"{model} model is not registered.")
|
|
170
190
|
|
|
191
|
+
self._bind_admin_context(dashboard_widget_model, request=request, user=current_user)
|
|
192
|
+
|
|
171
193
|
if inspect.iscoroutinefunction(dashboard_widget_model.get_data):
|
|
172
194
|
get_data = dashboard_widget_model.get_data
|
|
173
195
|
else:
|
|
@@ -190,10 +212,9 @@ class ApiService:
|
|
|
190
212
|
filters: dict | None = None,
|
|
191
213
|
offset: int | None = 0,
|
|
192
214
|
limit: int | None = 10,
|
|
215
|
+
request: Any | None = None,
|
|
193
216
|
) -> tuple[list[dict], int]:
|
|
194
|
-
|
|
195
|
-
if not current_user_id:
|
|
196
|
-
raise AdminApiException(401, detail="User is not authenticated.")
|
|
217
|
+
_current_user_id, current_user = await self._get_authenticated_user(session_id)
|
|
197
218
|
|
|
198
219
|
query_params = ListQuerySchema(
|
|
199
220
|
search=search,
|
|
@@ -206,6 +227,7 @@ class ApiService:
|
|
|
206
227
|
admin_model = get_admin_or_admin_inline_model(model)
|
|
207
228
|
if not admin_model:
|
|
208
229
|
raise AdminApiException(404, detail=f"{model} model is not registered.")
|
|
230
|
+
self._bind_admin_context(admin_model, request=request, user=current_user)
|
|
209
231
|
|
|
210
232
|
# validations
|
|
211
233
|
fields = set(admin_model.get_fields_for_serialize())
|
|
@@ -256,14 +278,14 @@ class ApiService:
|
|
|
256
278
|
session_id: str | None,
|
|
257
279
|
model: str,
|
|
258
280
|
id: UUID | int | str,
|
|
281
|
+
request: Any | None = None,
|
|
259
282
|
) -> dict:
|
|
260
|
-
|
|
261
|
-
if not current_user_id:
|
|
262
|
-
raise AdminApiException(401, detail="User is not authenticated.")
|
|
283
|
+
_current_user_id, current_user = await self._get_authenticated_user(session_id)
|
|
263
284
|
|
|
264
285
|
admin_model = get_admin_or_admin_inline_model(model)
|
|
265
286
|
if not admin_model:
|
|
266
287
|
raise AdminApiException(404, detail=f"{model} model is not registered.")
|
|
288
|
+
self._bind_admin_context(admin_model, request=request, user=current_user)
|
|
267
289
|
|
|
268
290
|
try:
|
|
269
291
|
obj = await admin_model.get_obj(id)
|
|
@@ -278,14 +300,14 @@ class ApiService:
|
|
|
278
300
|
session_id: str | None,
|
|
279
301
|
model: str,
|
|
280
302
|
payload: dict,
|
|
303
|
+
request: Any | None = None,
|
|
281
304
|
) -> dict:
|
|
282
|
-
|
|
283
|
-
if not current_user_id:
|
|
284
|
-
raise AdminApiException(401, detail="User is not authenticated.")
|
|
305
|
+
_current_user_id, current_user = await self._get_authenticated_user(session_id)
|
|
285
306
|
|
|
286
307
|
admin_model = get_admin_or_admin_inline_model(model)
|
|
287
308
|
if not admin_model:
|
|
288
309
|
raise AdminApiException(404, detail=f"{model} model is not registered.")
|
|
310
|
+
self._bind_admin_context(admin_model, request=request, user=current_user)
|
|
289
311
|
return await admin_model.save_model(None, payload) # type: ignore [return-value]
|
|
290
312
|
|
|
291
313
|
async def change_password(
|
|
@@ -293,14 +315,14 @@ class ApiService:
|
|
|
293
315
|
session_id: str | None,
|
|
294
316
|
id: UUID | int | str,
|
|
295
317
|
payload: dict,
|
|
318
|
+
request: Any | None = None,
|
|
296
319
|
) -> None:
|
|
297
|
-
|
|
298
|
-
if not current_user_id:
|
|
299
|
-
raise AdminApiException(401, detail="User is not authenticated.")
|
|
320
|
+
_current_user_id, current_user = await self._get_authenticated_user(session_id)
|
|
300
321
|
|
|
301
322
|
admin_model = get_admin_model(settings.ADMIN_USER_MODEL)
|
|
302
323
|
if not admin_model:
|
|
303
324
|
raise AdminApiException(404, detail=f"{settings.ADMIN_USER_MODEL} model is not registered.")
|
|
325
|
+
self._bind_admin_context(admin_model, request=request, user=current_user)
|
|
304
326
|
|
|
305
327
|
payload = ChangePasswordInputSchema(**payload)
|
|
306
328
|
if payload.password != payload.confirm_password:
|
|
@@ -333,14 +355,14 @@ class ApiService:
|
|
|
333
355
|
model: str,
|
|
334
356
|
id: UUID | int | str,
|
|
335
357
|
payload: dict,
|
|
358
|
+
request: Any | None = None,
|
|
336
359
|
) -> dict:
|
|
337
|
-
|
|
338
|
-
if not current_user_id:
|
|
339
|
-
raise AdminApiException(401, detail="User is not authenticated.")
|
|
360
|
+
_current_user_id, current_user = await self._get_authenticated_user(session_id)
|
|
340
361
|
|
|
341
362
|
admin_model = get_admin_or_admin_inline_model(model)
|
|
342
363
|
if not admin_model:
|
|
343
364
|
raise AdminApiException(404, detail=f"{model} model is not registered.")
|
|
365
|
+
self._bind_admin_context(admin_model, request=request, user=current_user)
|
|
344
366
|
|
|
345
367
|
try:
|
|
346
368
|
obj = await admin_model.save_model(id, payload)
|
|
@@ -358,10 +380,9 @@ class ApiService:
|
|
|
358
380
|
search: str | None = None,
|
|
359
381
|
sort_by: str | None = None,
|
|
360
382
|
filters: dict | None = None,
|
|
383
|
+
request: Any | None = None,
|
|
361
384
|
) -> tuple[str, str, StringIO | BytesIO | None]:
|
|
362
|
-
|
|
363
|
-
if not current_user_id:
|
|
364
|
-
raise AdminApiException(401, detail="User is not authenticated.")
|
|
385
|
+
_current_user_id, current_user = await self._get_authenticated_user(session_id)
|
|
365
386
|
|
|
366
387
|
query_params = ListQuerySchema(
|
|
367
388
|
search=search,
|
|
@@ -374,6 +395,7 @@ class ApiService:
|
|
|
374
395
|
admin_model = get_admin_or_admin_inline_model(model)
|
|
375
396
|
if not admin_model:
|
|
376
397
|
raise AdminApiException(404, detail=f"{model} model is not registered.")
|
|
398
|
+
self._bind_admin_context(admin_model, request=request, user=current_user)
|
|
377
399
|
|
|
378
400
|
# validations
|
|
379
401
|
fields = set(admin_model.get_fields_for_serialize())
|
|
@@ -432,14 +454,14 @@ class ApiService:
|
|
|
432
454
|
session_id: str | None,
|
|
433
455
|
model: str,
|
|
434
456
|
id: UUID | int | str,
|
|
457
|
+
request: Any | None = None,
|
|
435
458
|
) -> UUID | int | str:
|
|
436
|
-
current_user_id = await
|
|
437
|
-
if not current_user_id:
|
|
438
|
-
raise AdminApiException(401, detail="User is not authenticated.")
|
|
459
|
+
current_user_id, current_user = await self._get_authenticated_user(session_id)
|
|
439
460
|
|
|
440
461
|
admin_model = get_admin_or_admin_inline_model(model)
|
|
441
462
|
if not admin_model:
|
|
442
463
|
raise AdminApiException(404, detail=f"{model} model is not registered.")
|
|
464
|
+
self._bind_admin_context(admin_model, request=request, user=current_user)
|
|
443
465
|
|
|
444
466
|
if str(current_user_id) == str(id) and model == settings.ADMIN_USER_MODEL:
|
|
445
467
|
raise AdminApiException(403, detail="You cannot delete yourself.")
|
|
@@ -449,14 +471,20 @@ class ApiService:
|
|
|
449
471
|
raise AdminApiException(404, detail=f"{model} not found.") from None
|
|
450
472
|
return id
|
|
451
473
|
|
|
452
|
-
async def action(
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
474
|
+
async def action(
|
|
475
|
+
self,
|
|
476
|
+
session_id: str | None,
|
|
477
|
+
model: str,
|
|
478
|
+
action: str,
|
|
479
|
+
payload: ActionInputSchema,
|
|
480
|
+
request: Any | None = None,
|
|
481
|
+
) -> None:
|
|
482
|
+
_current_user_id, current_user = await self._get_authenticated_user(session_id)
|
|
456
483
|
|
|
457
484
|
admin_model = get_admin_or_admin_inline_model(model)
|
|
458
485
|
if not admin_model:
|
|
459
486
|
raise AdminApiException(404, detail=f"{model} model is not registered.")
|
|
487
|
+
self._bind_admin_context(admin_model, request=request, user=current_user)
|
|
460
488
|
|
|
461
489
|
if action not in admin_model.actions:
|
|
462
490
|
raise AdminApiException(422, detail=f"{action} action is not in actions setting.")
|
|
@@ -475,9 +503,11 @@ class ApiService:
|
|
|
475
503
|
async def get_configuration(
|
|
476
504
|
self,
|
|
477
505
|
session_id: str | None,
|
|
506
|
+
request: Any | None = None,
|
|
478
507
|
) -> ConfigurationSchema:
|
|
479
|
-
|
|
480
|
-
|
|
508
|
+
try:
|
|
509
|
+
current_user_id, current_user = await self._get_authenticated_user(session_id)
|
|
510
|
+
except AdminApiException:
|
|
481
511
|
return ConfigurationSchema(
|
|
482
512
|
site_name=settings.ADMIN_SITE_NAME,
|
|
483
513
|
site_sign_in_logo=settings.ADMIN_SITE_SIGN_IN_LOGO,
|
|
@@ -493,7 +523,10 @@ class ApiService:
|
|
|
493
523
|
)
|
|
494
524
|
|
|
495
525
|
admin_models = cast(dict[Any, ModelAdmin | InlineModelAdmin], get_admin_models())
|
|
496
|
-
models = cast(
|
|
526
|
+
models = cast(
|
|
527
|
+
Sequence[ModelSchema],
|
|
528
|
+
await generate_models_schema(admin_models, user_id=current_user_id, user=current_user, request=request),
|
|
529
|
+
)
|
|
497
530
|
dashboard_widgets = generate_dashboard_widgets_schema()
|
|
498
531
|
return ConfigurationSchema(
|
|
499
532
|
site_name=settings.ADMIN_SITE_NAME,
|
|
@@ -3,6 +3,7 @@ import datetime
|
|
|
3
3
|
import inspect
|
|
4
4
|
import json
|
|
5
5
|
from collections.abc import Sequence
|
|
6
|
+
from contextvars import ContextVar
|
|
6
7
|
from io import BytesIO, StringIO
|
|
7
8
|
from typing import Any
|
|
8
9
|
from uuid import UUID
|
|
@@ -16,6 +17,9 @@ from fastadmin.models.schemas import DashboardWidgetType, ModelFieldWidgetSchema
|
|
|
16
17
|
class BaseModelAdmin:
|
|
17
18
|
"""Base class for model admin"""
|
|
18
19
|
|
|
20
|
+
_request_context: ContextVar[Any | None]
|
|
21
|
+
_user_context: ContextVar[Any | None]
|
|
22
|
+
|
|
19
23
|
# Use it only if you use several orms in your project.
|
|
20
24
|
model_name_prefix: str | None = None
|
|
21
25
|
|
|
@@ -198,6 +202,23 @@ class BaseModelAdmin:
|
|
|
198
202
|
:params model_cls: an orm/db model class.
|
|
199
203
|
"""
|
|
200
204
|
self.model_cls = model_cls
|
|
205
|
+
self._request_context = ContextVar(f"fastadmin_admin_request_context_{id(self)}", default=None)
|
|
206
|
+
self._user_context = ContextVar(f"fastadmin_admin_user_context_{id(self)}", default=None)
|
|
207
|
+
|
|
208
|
+
@property
|
|
209
|
+
def request(self) -> Any | None:
|
|
210
|
+
"""Current request object for this async context."""
|
|
211
|
+
return self._request_context.get()
|
|
212
|
+
|
|
213
|
+
@property
|
|
214
|
+
def user(self) -> Any | None:
|
|
215
|
+
"""Current authenticated user object for this async context."""
|
|
216
|
+
return self._user_context.get()
|
|
217
|
+
|
|
218
|
+
def set_context(self, request: Any | None = None, user: Any | None = None) -> None:
|
|
219
|
+
"""Set request/user context for the current async task."""
|
|
220
|
+
self._request_context.set(request)
|
|
221
|
+
self._user_context.set(user)
|
|
201
222
|
|
|
202
223
|
@staticmethod
|
|
203
224
|
def get_model_pk_name(orm_model_cls: Any) -> str:
|
|
@@ -227,6 +248,8 @@ class BaseModelAdmin:
|
|
|
227
248
|
search: str | None = None,
|
|
228
249
|
sort_by: str | None = None,
|
|
229
250
|
filters: dict | None = None,
|
|
251
|
+
prefetch_related_fields: list[str] | None = None,
|
|
252
|
+
additional_search_fields: list[str] | None = None,
|
|
230
253
|
) -> tuple[list[Any], int]:
|
|
231
254
|
"""This method is used to get list of orm/db model objects.
|
|
232
255
|
|
|
@@ -235,6 +258,8 @@ class BaseModelAdmin:
|
|
|
235
258
|
:params search: a search query.
|
|
236
259
|
:params sort_by: a sort by field name.
|
|
237
260
|
:params filters: a dict of filters.
|
|
261
|
+
:params prefetch_related_fields: a list of related fields to prefetch.
|
|
262
|
+
:params additional_search_fields: a list of additional search fields.
|
|
238
263
|
:return: A tuple of list of objects and total count.
|
|
239
264
|
"""
|
|
240
265
|
raise NotImplementedError
|
|
@@ -697,6 +722,9 @@ class ModelAdmin(BaseModelAdmin):
|
|
|
697
722
|
|
|
698
723
|
|
|
699
724
|
class DashboardWidgetAdmin:
|
|
725
|
+
_request_context: ContextVar[Any | None]
|
|
726
|
+
_user_context: ContextVar[Any | None]
|
|
727
|
+
|
|
700
728
|
title: str
|
|
701
729
|
dashboard_widget_type: DashboardWidgetType
|
|
702
730
|
x_field: str
|
|
@@ -706,6 +734,25 @@ class DashboardWidgetAdmin:
|
|
|
706
734
|
x_field_filter_widget_props: dict[str, Any] | None = None
|
|
707
735
|
x_field_periods: list[str] | None = None
|
|
708
736
|
|
|
737
|
+
def __init__(self) -> None:
|
|
738
|
+
self._request_context = ContextVar(f"fastadmin_dashboard_request_context_{id(self)}", default=None)
|
|
739
|
+
self._user_context = ContextVar(f"fastadmin_dashboard_user_context_{id(self)}", default=None)
|
|
740
|
+
|
|
741
|
+
@property
|
|
742
|
+
def request(self) -> Any | None:
|
|
743
|
+
"""Current request object for this async context."""
|
|
744
|
+
return self._request_context.get()
|
|
745
|
+
|
|
746
|
+
@property
|
|
747
|
+
def user(self) -> Any | None:
|
|
748
|
+
"""Current authenticated user object for this async context."""
|
|
749
|
+
return self._user_context.get()
|
|
750
|
+
|
|
751
|
+
def set_context(self, request: Any | None = None, user: Any | None = None) -> None:
|
|
752
|
+
"""Set request/user context for the current async task."""
|
|
753
|
+
self._request_context.set(request)
|
|
754
|
+
self._user_context.set(user)
|
|
755
|
+
|
|
709
756
|
async def get_data(
|
|
710
757
|
self,
|
|
711
758
|
min_x_field: str | None = None,
|
|
@@ -101,6 +101,8 @@ def get_admin_or_admin_inline_model(orm_model_cls: str) -> ModelAdmin | InlineMo
|
|
|
101
101
|
async def generate_models_schema(
|
|
102
102
|
admin_models: dict[Any, ModelAdmin | InlineModelAdmin],
|
|
103
103
|
user_id: UUID | int | None = None,
|
|
104
|
+
user: Any | None = None,
|
|
105
|
+
request: Any | None = None,
|
|
104
106
|
inline_parent_admin_modal: ModelAdmin | None = None,
|
|
105
107
|
) -> list[ModelSchema | InlineModelSchema]:
|
|
106
108
|
"""Generate models schema
|
|
@@ -111,6 +113,8 @@ async def generate_models_schema(
|
|
|
111
113
|
"""
|
|
112
114
|
models_schemas: list[ModelSchema | InlineModelSchema] = []
|
|
113
115
|
for orm_model_cls, admin_model_obj in admin_models.items():
|
|
116
|
+
if hasattr(admin_model_obj, "set_context"):
|
|
117
|
+
admin_model_obj.set_context(request=request, user=user)
|
|
114
118
|
orm_model_fields = admin_model_obj.get_model_fields_with_widget_types()
|
|
115
119
|
orm_model_fields_for_serialize = admin_model_obj.get_fields_for_serialize()
|
|
116
120
|
|
|
@@ -258,6 +262,8 @@ async def generate_models_schema(
|
|
|
258
262
|
view_on_site=admin_model_obj.view_on_site,
|
|
259
263
|
inlines=await generate_models_schema(
|
|
260
264
|
{inline.model: inline(inline.model) for inline in admin_model_obj.inlines},
|
|
265
|
+
user=user,
|
|
266
|
+
request=request,
|
|
261
267
|
inline_parent_admin_modal=admin_model_obj,
|
|
262
268
|
user_id=user_id,
|
|
263
269
|
), # type: ignore [arg-type]
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import operator
|
|
2
1
|
from base64 import b64decode
|
|
3
2
|
from typing import Any
|
|
4
3
|
from uuid import UUID
|
|
@@ -245,6 +244,8 @@ class DjangoORMMixin:
|
|
|
245
244
|
search: str | None = None,
|
|
246
245
|
sort_by: str | None = None,
|
|
247
246
|
filters: dict | None = None,
|
|
247
|
+
prefetch_related_fields: list[str] | None = None,
|
|
248
|
+
additional_search_fields: list[str] | None = None,
|
|
248
249
|
) -> tuple[list[Any], int]:
|
|
249
250
|
"""This method is used to get list of orm/db model objects.
|
|
250
251
|
|
|
@@ -253,19 +254,30 @@ class DjangoORMMixin:
|
|
|
253
254
|
:params search: a search query.
|
|
254
255
|
:params sort_by: a sort by field name.
|
|
255
256
|
:params filters: a dict of filters.
|
|
257
|
+
:params prefetch_related_fields: a list of related fields to prefetch.
|
|
258
|
+
:params additional_search_fields: a list of additional search fields.
|
|
256
259
|
:return: A tuple of list of objects and total count.
|
|
257
260
|
"""
|
|
258
261
|
qs = self.model_cls.objects.all()
|
|
259
262
|
|
|
263
|
+
if prefetch_related_fields:
|
|
264
|
+
qs = qs.prefetch_related(*prefetch_related_fields)
|
|
265
|
+
|
|
260
266
|
if filters:
|
|
261
267
|
for field_with_condition, value in filters.items():
|
|
262
268
|
field = field_with_condition[0]
|
|
263
269
|
condition = field_with_condition[1]
|
|
264
270
|
qs = qs.filter(**{f"{field}__{condition}" if condition != "exact" else field: value})
|
|
265
271
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
272
|
+
search_fields = list(self.search_fields)
|
|
273
|
+
if additional_search_fields:
|
|
274
|
+
search_fields.extend(additional_search_fields)
|
|
275
|
+
|
|
276
|
+
if search and search_fields:
|
|
277
|
+
search_conditions = [Q(**{f + "__icontains": search}) for f in search_fields]
|
|
278
|
+
search_q = search_conditions[0]
|
|
279
|
+
for condition in search_conditions[1:]:
|
|
280
|
+
search_q |= condition
|
|
269
281
|
qs = qs.filter(search_q)
|
|
270
282
|
|
|
271
283
|
if sort_by:
|
|
@@ -224,6 +224,8 @@ class PonyORMMixin:
|
|
|
224
224
|
search: str | None = None,
|
|
225
225
|
sort_by: str | None = None,
|
|
226
226
|
filters: dict | None = None,
|
|
227
|
+
prefetch_related_fields: list[str] | None = None,
|
|
228
|
+
additional_search_fields: list[str] | None = None,
|
|
227
229
|
) -> tuple[list[Any], int]:
|
|
228
230
|
"""This method is used to get list of orm/db model objects.
|
|
229
231
|
|
|
@@ -232,6 +234,8 @@ class PonyORMMixin:
|
|
|
232
234
|
:params search: a search query.
|
|
233
235
|
:params sort_by: a sort by field name.
|
|
234
236
|
:params filters: a dict of filters.
|
|
237
|
+
:params prefetch_related_fields: a list of related fields to prefetch.
|
|
238
|
+
:params additional_search_fields: a list of additional search fields.
|
|
235
239
|
:return: A tuple of list of objects and total count.
|
|
236
240
|
"""
|
|
237
241
|
|
|
@@ -271,9 +275,13 @@ class PonyORMMixin:
|
|
|
271
275
|
filter_expr = f""""{value}" {pony_condition} m.{field}"""
|
|
272
276
|
qs = qs.filter(filter_expr)
|
|
273
277
|
|
|
274
|
-
|
|
278
|
+
search_fields = list(self.search_fields)
|
|
279
|
+
if additional_search_fields:
|
|
280
|
+
search_fields.extend(additional_search_fields)
|
|
281
|
+
|
|
282
|
+
if search and search_fields:
|
|
275
283
|
ids = []
|
|
276
|
-
for search_field in
|
|
284
|
+
for search_field in search_fields:
|
|
277
285
|
pony_search_field = search_field.replace("__", ".")
|
|
278
286
|
# Pony string filter for case-insensitive search
|
|
279
287
|
filter_expr = f'"{search.lower()}" in m.{pony_search_field}.lower()'
|
|
@@ -296,6 +304,9 @@ class PonyORMMixin:
|
|
|
296
304
|
if self.list_select_related:
|
|
297
305
|
qs = qs.prefetch(*[getattr(self.model_cls, field) for field in self.list_select_related])
|
|
298
306
|
|
|
307
|
+
if prefetch_related_fields:
|
|
308
|
+
qs = qs.prefetch(*[getattr(self.model_cls, field) for field in prefetch_related_fields])
|
|
309
|
+
|
|
299
310
|
if offset is not None and limit is not None:
|
|
300
311
|
qs = qs.limit(limit, offset=offset)
|
|
301
312
|
|
|
@@ -300,6 +300,8 @@ class SqlAlchemyMixin:
|
|
|
300
300
|
search: str | None = None,
|
|
301
301
|
sort_by: str | None = None,
|
|
302
302
|
filters: dict | None = None,
|
|
303
|
+
prefetch_related_fields: list[str] | None = None,
|
|
304
|
+
additional_search_fields: list[str] | None = None,
|
|
303
305
|
) -> tuple[list[Any], int]:
|
|
304
306
|
"""This method is used to get list of orm/db model objects.
|
|
305
307
|
|
|
@@ -308,6 +310,8 @@ class SqlAlchemyMixin:
|
|
|
308
310
|
:params search: a search query.
|
|
309
311
|
:params sort_by: a sort by field name.
|
|
310
312
|
:params filters: a dict of filters.
|
|
313
|
+
:params prefetch_related_fields: a list of related fields to prefetch.
|
|
314
|
+
:params additional_search_fields: a list of additional search fields.
|
|
311
315
|
:return: A tuple of list of objects and total count.
|
|
312
316
|
"""
|
|
313
317
|
|
|
@@ -354,9 +358,13 @@ class SqlAlchemyMixin:
|
|
|
354
358
|
q.append(model_field.ilike(f"%{value}%"))
|
|
355
359
|
qs = qs.where(and_(*q))
|
|
356
360
|
|
|
357
|
-
|
|
361
|
+
search_fields = list(self.search_fields)
|
|
362
|
+
if additional_search_fields:
|
|
363
|
+
search_fields.extend(additional_search_fields)
|
|
364
|
+
|
|
365
|
+
if search and search_fields:
|
|
358
366
|
q = []
|
|
359
|
-
for field in
|
|
367
|
+
for field in search_fields:
|
|
360
368
|
condition = self._build_search_condition(field, search)
|
|
361
369
|
if condition is not None:
|
|
362
370
|
q.append(condition)
|
|
@@ -377,6 +385,28 @@ class SqlAlchemyMixin:
|
|
|
377
385
|
for field in self.list_select_related:
|
|
378
386
|
qs = qs.options(selectinload(getattr(self.model_cls, field)))
|
|
379
387
|
|
|
388
|
+
if prefetch_related_fields:
|
|
389
|
+
for field_path in prefetch_related_fields:
|
|
390
|
+
parts = field_path.split("__")
|
|
391
|
+
current_model = self.model_cls
|
|
392
|
+
attr = getattr(current_model, parts[0], None)
|
|
393
|
+
if attr is None:
|
|
394
|
+
continue
|
|
395
|
+
option = selectinload(attr)
|
|
396
|
+
current_model = getattrs(attr, "property.mapper.class_")
|
|
397
|
+
for part in parts[1:]:
|
|
398
|
+
if current_model is None:
|
|
399
|
+
break
|
|
400
|
+
nested_attr = getattr(current_model, part, None)
|
|
401
|
+
if nested_attr is None:
|
|
402
|
+
break
|
|
403
|
+
next_model = getattrs(nested_attr, "property.mapper.class_")
|
|
404
|
+
if next_model is None:
|
|
405
|
+
break
|
|
406
|
+
option = option.selectinload(nested_attr)
|
|
407
|
+
current_model = next_model
|
|
408
|
+
qs = qs.options(option)
|
|
409
|
+
|
|
380
410
|
if offset is not None and limit is not None:
|
|
381
411
|
qs = qs.offset(offset)
|
|
382
412
|
qs = qs.limit(limit)
|
|
@@ -254,6 +254,8 @@ class TortoiseMixin:
|
|
|
254
254
|
search: str | None = None,
|
|
255
255
|
sort_by: str | None = None,
|
|
256
256
|
filters: dict | None = None,
|
|
257
|
+
prefetch_related_fields: list[str] | None = None,
|
|
258
|
+
additional_search_fields: list[str] | None = None,
|
|
257
259
|
) -> tuple[list[Any], int]:
|
|
258
260
|
"""This method is used to get list of orm/db model objects.
|
|
259
261
|
|
|
@@ -262,21 +264,30 @@ class TortoiseMixin:
|
|
|
262
264
|
:params search: a search query.
|
|
263
265
|
:params sort_by: a sort by field name.
|
|
264
266
|
:params filters: a dict of filters.
|
|
267
|
+
:params prefetch_related_fields: a list of related fields to prefetch.
|
|
268
|
+
:params additional_search_fields: a list of additional search fields.
|
|
265
269
|
:return: A tuple of list of objects and total count.
|
|
266
270
|
"""
|
|
267
271
|
qs = self.model_cls.all()
|
|
268
272
|
|
|
273
|
+
if prefetch_related_fields:
|
|
274
|
+
qs = qs.prefetch_related(*prefetch_related_fields).distinct()
|
|
275
|
+
|
|
269
276
|
if filters:
|
|
270
277
|
for field_with_condition, value in filters.items():
|
|
271
278
|
field = field_with_condition[0]
|
|
272
279
|
condition = field_with_condition[1]
|
|
273
280
|
qs = qs.filter(**{f"{field}__{condition}" if condition != "exact" else field: value})
|
|
274
281
|
|
|
275
|
-
|
|
282
|
+
search_fields = list(self.search_fields)
|
|
283
|
+
if additional_search_fields:
|
|
284
|
+
search_fields.extend(additional_search_fields)
|
|
285
|
+
|
|
286
|
+
if search and search_fields:
|
|
276
287
|
qs = qs.filter(
|
|
277
288
|
functools.reduce(
|
|
278
289
|
operator.or_,
|
|
279
|
-
(Q(**{f + "__icontains": search}) for f in
|
|
290
|
+
(Q(**{f + "__icontains": search}) for f in search_fields),
|
|
280
291
|
Q(),
|
|
281
292
|
)
|
|
282
293
|
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|