brilliance-admin 0.42.0__py3-none-any.whl → 0.43.7__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.
- {admin_panel → brilliance_admin}/__init__.py +2 -2
- brilliance_admin/api/routers.py +18 -0
- {admin_panel → brilliance_admin}/api/utils.py +1 -1
- {admin_panel → brilliance_admin}/api/views/auth.py +6 -6
- {admin_panel → brilliance_admin}/api/views/autocomplete.py +9 -9
- {admin_panel → brilliance_admin}/api/views/graphs.py +8 -8
- {admin_panel → brilliance_admin}/api/views/index.py +11 -4
- {admin_panel → brilliance_admin}/api/views/schema.py +3 -3
- {admin_panel → brilliance_admin}/api/views/settings.py +5 -5
- {admin_panel → brilliance_admin}/api/views/table.py +23 -23
- {admin_panel → brilliance_admin}/auth.py +1 -1
- {admin_panel → brilliance_admin}/exceptions.py +3 -4
- {admin_panel → brilliance_admin}/integrations/sqlalchemy/__init__.py +1 -0
- {admin_panel → brilliance_admin}/integrations/sqlalchemy/auth.py +6 -6
- {admin_panel → brilliance_admin}/integrations/sqlalchemy/autocomplete.py +10 -6
- {admin_panel → brilliance_admin}/integrations/sqlalchemy/fields.py +24 -20
- {admin_panel → brilliance_admin}/integrations/sqlalchemy/fields_schema.py +27 -18
- {admin_panel → brilliance_admin}/integrations/sqlalchemy/table/base.py +8 -8
- {admin_panel → brilliance_admin}/integrations/sqlalchemy/table/create.py +10 -10
- {admin_panel → brilliance_admin}/integrations/sqlalchemy/table/delete.py +4 -4
- {admin_panel → brilliance_admin}/integrations/sqlalchemy/table/list.py +11 -11
- {admin_panel → brilliance_admin}/integrations/sqlalchemy/table/retrieve.py +22 -10
- {admin_panel → brilliance_admin}/integrations/sqlalchemy/table/update.py +11 -11
- brilliance_admin/locales/en.yml +25 -0
- brilliance_admin/locales/ru.yml +26 -0
- {admin_panel → brilliance_admin}/schema/admin_schema.py +39 -31
- {admin_panel → brilliance_admin}/schema/category.py +8 -9
- {admin_panel → brilliance_admin}/schema/graphs/category_graphs.py +10 -9
- {admin_panel → brilliance_admin}/schema/group.py +10 -10
- {admin_panel → brilliance_admin}/schema/table/admin_action.py +8 -7
- {admin_panel → brilliance_admin}/schema/table/category_table.py +21 -21
- {admin_panel → brilliance_admin}/schema/table/fields/base.py +58 -29
- {admin_panel → brilliance_admin}/schema/table/fields/function_field.py +3 -3
- {admin_panel → brilliance_admin}/schema/table/fields_schema.py +11 -10
- {admin_panel → brilliance_admin}/schema/table/table_models.py +1 -1
- admin_panel/static/index-BeniOHDv.js → brilliance_admin/static/index-BnnESruI.js +131 -131
- {admin_panel → brilliance_admin}/templates/index.html +1 -1
- brilliance_admin/translations.py +115 -0
- brilliance_admin/utils.py +153 -0
- brilliance_admin-0.43.7.dist-info/METADATA +217 -0
- brilliance_admin-0.43.7.dist-info/RECORD +74 -0
- brilliance_admin-0.43.7.dist-info/top_level.txt +1 -0
- admin_panel/api/routers.py +0 -18
- admin_panel/static/favicon.jpg +0 -0
- admin_panel/translations.py +0 -145
- admin_panel/utils.py +0 -50
- brilliance_admin-0.42.0.dist-info/METADATA +0 -155
- brilliance_admin-0.42.0.dist-info/RECORD +0 -73
- brilliance_admin-0.42.0.dist-info/top_level.txt +0 -1
- {admin_panel → brilliance_admin}/api/__init__.py +0 -0
- {admin_panel → brilliance_admin}/api/views/__init__.py +0 -0
- {admin_panel → brilliance_admin}/docs.py +0 -0
- {admin_panel → brilliance_admin}/integrations/__init__.py +0 -0
- {admin_panel → brilliance_admin}/integrations/sqlalchemy/table/__init__.py +0 -0
- {admin_panel → brilliance_admin}/schema/__init__.py +0 -0
- {admin_panel → brilliance_admin}/schema/graphs/__init__.py +0 -0
- {admin_panel → brilliance_admin}/schema/table/__init__.py +0 -0
- {admin_panel → brilliance_admin}/schema/table/fields/__init__.py +0 -0
- {admin_panel → brilliance_admin}/static/index-vlBToOhT.css +0 -0
- {admin_panel → brilliance_admin}/static/materialdesignicons-webfont-CYDMK1kx.woff2 +0 -0
- {admin_panel → brilliance_admin}/static/materialdesignicons-webfont-CgCzGbLl.woff +0 -0
- {admin_panel → brilliance_admin}/static/materialdesignicons-webfont-D3kAzl71.ttf +0 -0
- {admin_panel → brilliance_admin}/static/materialdesignicons-webfont-DttUABo4.eot +0 -0
- {admin_panel → brilliance_admin}/static/tinymce/dark-first/content.min.css +0 -0
- {admin_panel → brilliance_admin}/static/tinymce/dark-first/skin.min.css +0 -0
- {admin_panel → brilliance_admin}/static/tinymce/dark-slim/content.min.css +0 -0
- {admin_panel → brilliance_admin}/static/tinymce/dark-slim/skin.min.css +0 -0
- {admin_panel → brilliance_admin}/static/tinymce/img/example.png +0 -0
- {admin_panel → brilliance_admin}/static/tinymce/img/tinymce.woff2 +0 -0
- {admin_panel → brilliance_admin}/static/tinymce/lightgray/content.min.css +0 -0
- {admin_panel → brilliance_admin}/static/tinymce/lightgray/fonts/tinymce.woff +0 -0
- {admin_panel → brilliance_admin}/static/tinymce/lightgray/skin.min.css +0 -0
- {admin_panel → brilliance_admin}/static/tinymce/plugins/accordion/css/accordion.css +0 -0
- {admin_panel → brilliance_admin}/static/tinymce/plugins/accordion/plugin.js +0 -0
- {admin_panel → brilliance_admin}/static/tinymce/plugins/codesample/css/prism.css +0 -0
- {admin_panel → brilliance_admin}/static/tinymce/plugins/customLink/css/link.css +0 -0
- {admin_panel → brilliance_admin}/static/tinymce/plugins/customLink/plugin.js +0 -0
- {admin_panel → brilliance_admin}/static/tinymce/tinymce.min.js +0 -0
- {admin_panel → brilliance_admin}/static/vanilla-picker-B6E6ObS_.js +0 -0
- {brilliance_admin-0.42.0.dist-info → brilliance_admin-0.43.7.dist-info}/WHEEL +0 -0
- {brilliance_admin-0.42.0.dist-info → brilliance_admin-0.43.7.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
# pylint: disable=wildcard-import, unused-wildcard-import, unused-import
|
|
2
2
|
# flake8: noqa: F405
|
|
3
|
-
from
|
|
4
|
-
from
|
|
3
|
+
from brilliance_admin.integrations import sqlalchemy
|
|
4
|
+
from brilliance_admin import schema
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from fastapi import APIRouter
|
|
2
|
+
|
|
3
|
+
from .views.schema import router as schema_router
|
|
4
|
+
from .views.table import router as schema_table
|
|
5
|
+
from .views.auth import router as schema_auth
|
|
6
|
+
from .views.autocomplete import router as schema_autocomplete
|
|
7
|
+
from .views.graphs import router as schema_graphs
|
|
8
|
+
from .views.settings import router as schema_settings
|
|
9
|
+
from .views.index import router as schema_index
|
|
10
|
+
|
|
11
|
+
brilliance_admin_router = APIRouter()
|
|
12
|
+
brilliance_admin_router.include_router(schema_router)
|
|
13
|
+
brilliance_admin_router.include_router(schema_table)
|
|
14
|
+
brilliance_admin_router.include_router(schema_auth)
|
|
15
|
+
brilliance_admin_router.include_router(schema_autocomplete)
|
|
16
|
+
brilliance_admin_router.include_router(schema_graphs)
|
|
17
|
+
brilliance_admin_router.include_router(schema_settings)
|
|
18
|
+
brilliance_admin_router.include_router(schema_index)
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
from fastapi import APIRouter, Request
|
|
2
2
|
from fastapi.responses import JSONResponse
|
|
3
3
|
|
|
4
|
-
from
|
|
5
|
-
from
|
|
6
|
-
from
|
|
7
|
-
from
|
|
4
|
+
from brilliance_admin.auth import AdminAuthentication, AuthData, AuthResult
|
|
5
|
+
from brilliance_admin.exceptions import AdminAPIException, APIError
|
|
6
|
+
from brilliance_admin.schema.admin_schema import AdminSchema
|
|
7
|
+
from brilliance_admin.translations import LanguageContext
|
|
8
8
|
|
|
9
9
|
router = APIRouter(prefix="/auth", tags=["Auth"])
|
|
10
10
|
|
|
@@ -17,8 +17,8 @@ async def login(request: Request, auth_data: AuthData) -> AuthResult:
|
|
|
17
17
|
schema: AdminSchema = request.app.state.schema
|
|
18
18
|
|
|
19
19
|
language_slug = request.headers.get('Accept-Language')
|
|
20
|
-
|
|
21
|
-
context = {'
|
|
20
|
+
language_context: LanguageContext = schema.get_language_context(language_slug)
|
|
21
|
+
context = {'language_context': language_context}
|
|
22
22
|
|
|
23
23
|
auth: AdminAuthentication = schema.auth
|
|
24
24
|
try:
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
from fastapi import APIRouter, Request
|
|
2
2
|
from fastapi.responses import JSONResponse
|
|
3
3
|
|
|
4
|
-
from
|
|
5
|
-
from
|
|
6
|
-
from
|
|
7
|
-
from
|
|
8
|
-
from
|
|
9
|
-
from
|
|
4
|
+
from brilliance_admin.api.utils import get_category
|
|
5
|
+
from brilliance_admin.exceptions import AdminAPIException
|
|
6
|
+
from brilliance_admin.schema.admin_schema import AdminSchema
|
|
7
|
+
from brilliance_admin.schema.table.table_models import AutocompleteData, AutocompleteResult
|
|
8
|
+
from brilliance_admin.translations import LanguageContext
|
|
9
|
+
from brilliance_admin.utils import get_logger
|
|
10
10
|
|
|
11
11
|
router = APIRouter(prefix="/autocomplete", tags=["Autocomplete"])
|
|
12
12
|
|
|
@@ -19,11 +19,11 @@ async def autocomplete(request: Request, group: str, category: str, data: Autoco
|
|
|
19
19
|
schema_category, user = await get_category(request, group, category)
|
|
20
20
|
|
|
21
21
|
language_slug = request.headers.get('Accept-Language')
|
|
22
|
-
|
|
23
|
-
context = {'
|
|
22
|
+
language_context: LanguageContext = schema.get_language_context(language_slug)
|
|
23
|
+
context = {'language_context': language_context}
|
|
24
24
|
|
|
25
25
|
try:
|
|
26
|
-
result: AutocompleteResult = await schema_category.autocomplete(data, user,
|
|
26
|
+
result: AutocompleteResult = await schema_category.autocomplete(data, user, language_context)
|
|
27
27
|
except AdminAPIException as e:
|
|
28
28
|
return JSONResponse(e.get_error().model_dump(mode='json', context=context), status_code=e.status_code)
|
|
29
29
|
except Exception as e:
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
from fastapi import APIRouter, Request
|
|
2
2
|
from fastapi.responses import JSONResponse
|
|
3
3
|
|
|
4
|
-
from
|
|
5
|
-
from
|
|
6
|
-
from
|
|
7
|
-
from
|
|
8
|
-
from
|
|
9
|
-
from
|
|
4
|
+
from brilliance_admin.api.utils import get_category
|
|
5
|
+
from brilliance_admin.exceptions import AdminAPIException
|
|
6
|
+
from brilliance_admin.schema.admin_schema import AdminSchema
|
|
7
|
+
from brilliance_admin.schema.graphs.category_graphs import CategoryGraphs, GraphData, GraphsDataResult
|
|
8
|
+
from brilliance_admin.translations import LanguageContext
|
|
9
|
+
from brilliance_admin.utils import get_logger
|
|
10
10
|
|
|
11
11
|
router = APIRouter(prefix="/graph", tags=["Category - Graph"])
|
|
12
12
|
|
|
@@ -21,8 +21,8 @@ async def graph_data(request: Request, group: str, category: str, data: GraphDat
|
|
|
21
21
|
result: GraphsDataResult = await schema_category.get_data(data, user)
|
|
22
22
|
|
|
23
23
|
language_slug = request.headers.get('Accept-Language')
|
|
24
|
-
|
|
25
|
-
context = {'
|
|
24
|
+
language_context: LanguageContext = schema.get_language_context(language_slug)
|
|
25
|
+
context = {'language_context': language_context}
|
|
26
26
|
|
|
27
27
|
try:
|
|
28
28
|
return JSONResponse(result.model_dump(mode='json', context=context))
|
|
@@ -1,15 +1,17 @@
|
|
|
1
|
+
from pathlib import PurePosixPath
|
|
2
|
+
|
|
1
3
|
from fastapi import APIRouter, HTTPException, Request
|
|
2
4
|
from fastapi.responses import HTMLResponse
|
|
3
5
|
from fastapi.templating import Jinja2Templates
|
|
4
6
|
from jinja2 import Environment, PackageLoader, select_autoescape
|
|
5
7
|
|
|
6
|
-
from
|
|
8
|
+
from brilliance_admin.schema import AdminSchema
|
|
7
9
|
|
|
8
10
|
router = APIRouter()
|
|
9
11
|
|
|
10
12
|
templates = Jinja2Templates(
|
|
11
13
|
env=Environment(
|
|
12
|
-
loader=PackageLoader("
|
|
14
|
+
loader=PackageLoader("brilliance_admin", "templates"),
|
|
13
15
|
autoescape=select_autoescape(["html", "xml"]),
|
|
14
16
|
)
|
|
15
17
|
)
|
|
@@ -25,8 +27,13 @@ async def admin_index(request: Request, rest_of_path: str):
|
|
|
25
27
|
The request responds with a pre-rendered SPA served as an HTML page.
|
|
26
28
|
'''
|
|
27
29
|
|
|
28
|
-
path =
|
|
29
|
-
|
|
30
|
+
path = PurePosixPath('/' + rest_of_path)
|
|
31
|
+
|
|
32
|
+
if '..' in path.parts:
|
|
33
|
+
raise HTTPException(status_code=404)
|
|
34
|
+
|
|
35
|
+
path_str = str(path)
|
|
36
|
+
if path_str in EXACT_BLOCK or path_str.startswith(PREFIX_BLOCK):
|
|
30
37
|
raise HTTPException(status_code=404)
|
|
31
38
|
|
|
32
39
|
schema: AdminSchema = request.app.state.schema
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
from fastapi import APIRouter, Request
|
|
2
2
|
from fastapi.responses import JSONResponse
|
|
3
3
|
|
|
4
|
-
from
|
|
5
|
-
from
|
|
6
|
-
from
|
|
4
|
+
from brilliance_admin.auth import AdminAuthentication
|
|
5
|
+
from brilliance_admin.exceptions import AdminAPIException, APIError
|
|
6
|
+
from brilliance_admin.schema import AdminSchema, AdminSchemaData
|
|
7
7
|
|
|
8
8
|
router = APIRouter(prefix="/schema", tags=["Main admin schema"])
|
|
9
9
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
from fastapi import APIRouter, Request
|
|
2
2
|
from fastapi.responses import JSONResponse
|
|
3
3
|
|
|
4
|
-
from
|
|
5
|
-
from
|
|
6
|
-
from
|
|
4
|
+
from brilliance_admin.exceptions import AdminAPIException, APIError
|
|
5
|
+
from brilliance_admin.schema.admin_schema import AdminSchema, AdminSettingsData
|
|
6
|
+
from brilliance_admin.translations import LanguageContext
|
|
7
7
|
|
|
8
8
|
router = APIRouter(tags=["Settings"])
|
|
9
9
|
|
|
@@ -19,8 +19,8 @@ async def get_settings(request: Request) -> AdminSettingsData:
|
|
|
19
19
|
schema: AdminSchema = request.app.state.schema
|
|
20
20
|
|
|
21
21
|
language_slug = request.headers.get('Accept-Language')
|
|
22
|
-
|
|
23
|
-
context = {'
|
|
22
|
+
language_context: LanguageContext = schema.get_language_context(language_slug)
|
|
23
|
+
context = {'language_context': language_context}
|
|
24
24
|
|
|
25
25
|
try:
|
|
26
26
|
admin_settings = await schema.get_settings(request)
|
|
@@ -3,14 +3,14 @@ from typing import Any
|
|
|
3
3
|
from fastapi import APIRouter, HTTPException, Request
|
|
4
4
|
from fastapi.responses import JSONResponse
|
|
5
5
|
|
|
6
|
-
from
|
|
7
|
-
from
|
|
8
|
-
from
|
|
9
|
-
from
|
|
10
|
-
from
|
|
11
|
-
from
|
|
12
|
-
from
|
|
13
|
-
from
|
|
6
|
+
from brilliance_admin.api.utils import get_category
|
|
7
|
+
from brilliance_admin.exceptions import AdminAPIException, APIError
|
|
8
|
+
from brilliance_admin.schema import AdminSchema
|
|
9
|
+
from brilliance_admin.schema.table.admin_action import ActionData, ActionResult
|
|
10
|
+
from brilliance_admin.schema.table.category_table import CategoryTable
|
|
11
|
+
from brilliance_admin.schema.table.table_models import CreateResult, ListData, RetrieveResult, TableListResult, UpdateResult
|
|
12
|
+
from brilliance_admin.translations import LanguageContext
|
|
13
|
+
from brilliance_admin.utils import get_logger
|
|
14
14
|
|
|
15
15
|
router = APIRouter(prefix="/table", tags=["Category - Table"])
|
|
16
16
|
|
|
@@ -25,11 +25,11 @@ async def table_list(request: Request, group: str, category: str, list_data: Lis
|
|
|
25
25
|
schema_category, user = await get_category(request, group, category, check_type=CategoryTable)
|
|
26
26
|
|
|
27
27
|
language_slug = request.headers.get('Accept-Language')
|
|
28
|
-
|
|
29
|
-
context = {'
|
|
28
|
+
language_context: LanguageContext = schema.get_language_context(language_slug)
|
|
29
|
+
context = {'language_context': language_context}
|
|
30
30
|
|
|
31
31
|
try:
|
|
32
|
-
result: TableListResult = await schema_category.get_list(list_data, user,
|
|
32
|
+
result: TableListResult = await schema_category.get_list(list_data, user, language_context)
|
|
33
33
|
except AdminAPIException as e:
|
|
34
34
|
return JSONResponse(e.get_error().model_dump(mode='json', context=context), status_code=e.status_code)
|
|
35
35
|
|
|
@@ -49,11 +49,11 @@ async def table_retrieve(request: Request, group: str, category: str, pk: Any) -
|
|
|
49
49
|
raise HTTPException(status_code=404, detail=f"Category {group}.{category} is not allowed for retrive")
|
|
50
50
|
|
|
51
51
|
language_slug = request.headers.get('Accept-Language')
|
|
52
|
-
|
|
53
|
-
context = {'
|
|
52
|
+
language_context: LanguageContext = schema.get_language_context(language_slug)
|
|
53
|
+
context = {'language_context': language_context}
|
|
54
54
|
|
|
55
55
|
try:
|
|
56
|
-
result: RetrieveResult = await schema_category.retrieve(pk, user,
|
|
56
|
+
result: RetrieveResult = await schema_category.retrieve(pk, user, language_context)
|
|
57
57
|
except AdminAPIException as e:
|
|
58
58
|
return JSONResponse(e.get_error().model_dump(mode='json', context=context), status_code=e.status_code)
|
|
59
59
|
|
|
@@ -72,11 +72,11 @@ async def table_create(request: Request, group: str, category: str) -> CreateRes
|
|
|
72
72
|
raise HTTPException(status_code=404, detail=f"Category {group}.{category} is not allowed for create")
|
|
73
73
|
|
|
74
74
|
language_slug = request.headers.get('Accept-Language')
|
|
75
|
-
|
|
76
|
-
context = {'
|
|
75
|
+
language_context: LanguageContext = schema.get_language_context(language_slug)
|
|
76
|
+
context = {'language_context': language_context}
|
|
77
77
|
|
|
78
78
|
try:
|
|
79
|
-
result: CreateResult = await schema_category.create(await request.json(), user,
|
|
79
|
+
result: CreateResult = await schema_category.create(await request.json(), user, language_context)
|
|
80
80
|
except AdminAPIException as e:
|
|
81
81
|
return JSONResponse(e.get_error().model_dump(mode='json', context=context), status_code=e.status_code)
|
|
82
82
|
|
|
@@ -95,11 +95,11 @@ async def table_update(request: Request, group: str, category: str, pk: Any) ->
|
|
|
95
95
|
raise HTTPException(status_code=404, detail=f"Category {group}.{category} is not allowed for update")
|
|
96
96
|
|
|
97
97
|
language_slug = request.headers.get('Accept-Language')
|
|
98
|
-
|
|
99
|
-
context = {'
|
|
98
|
+
language_context: LanguageContext = schema.get_language_context(language_slug)
|
|
99
|
+
context = {'language_context': language_context}
|
|
100
100
|
|
|
101
101
|
try:
|
|
102
|
-
result: UpdateResult = await schema_category.update(pk, await request.json(), user,
|
|
102
|
+
result: UpdateResult = await schema_category.update(pk, await request.json(), user, language_context)
|
|
103
103
|
except AdminAPIException as e:
|
|
104
104
|
return JSONResponse(e.get_error().model_dump(mode='json', context=context), status_code=e.status_code)
|
|
105
105
|
|
|
@@ -122,13 +122,13 @@ async def table_action(
|
|
|
122
122
|
schema_category, user = await get_category(request, group, category, check_type=CategoryTable)
|
|
123
123
|
|
|
124
124
|
language_slug = request.headers.get('Accept-Language')
|
|
125
|
-
|
|
126
|
-
context = {'
|
|
125
|
+
language_context: LanguageContext = schema.get_language_context(language_slug)
|
|
126
|
+
context = {'language_context': language_context}
|
|
127
127
|
|
|
128
128
|
try:
|
|
129
129
|
# pylint: disable=protected-access
|
|
130
130
|
result: ActionResult = await schema_category._perform_action(
|
|
131
|
-
request, action, action_data,
|
|
131
|
+
request, action, action_data, language_context, user,
|
|
132
132
|
)
|
|
133
133
|
except AdminAPIException as e:
|
|
134
134
|
return JSONResponse(e.get_error().model_dump(mode='json', context=context), status_code=e.status_code)
|
|
@@ -3,13 +3,12 @@ from typing import Dict
|
|
|
3
3
|
from pydantic import Field
|
|
4
4
|
from pydantic.dataclasses import dataclass
|
|
5
5
|
|
|
6
|
-
from
|
|
7
|
-
from admin_panel.utils import DataclassBase
|
|
6
|
+
from brilliance_admin.utils import DataclassBase, SupportsStr
|
|
8
7
|
|
|
9
8
|
|
|
10
9
|
@dataclass
|
|
11
10
|
class FieldError(DataclassBase, Exception):
|
|
12
|
-
message:
|
|
11
|
+
message: SupportsStr = None
|
|
13
12
|
code: str | None = None
|
|
14
13
|
|
|
15
14
|
def __post_init__(self):
|
|
@@ -20,7 +19,7 @@ class FieldError(DataclassBase, Exception):
|
|
|
20
19
|
|
|
21
20
|
@dataclass
|
|
22
21
|
class APIError(DataclassBase):
|
|
23
|
-
message:
|
|
22
|
+
message: SupportsStr | None = None
|
|
24
23
|
code: str | None = None
|
|
25
24
|
field_errors: Dict[str, FieldError] | None = None
|
|
26
25
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# pylint: disable=wildcard-import, unused-wildcard-import, unused-import
|
|
2
2
|
# flake8: noqa: F405
|
|
3
|
+
from .fields import SQLAlchemyRelatedField
|
|
3
4
|
from .auth import SQLAlchemyJWTAdminAuthentication
|
|
4
5
|
from .autocomplete import SQLAlchemyAdminAutocompleteMixin
|
|
5
6
|
from .fields_schema import SQLAlchemyFieldsSchema
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
from
|
|
2
|
-
from
|
|
3
|
-
from
|
|
4
|
-
from
|
|
1
|
+
from brilliance_admin.auth import AdminAuthentication, AuthData, AuthResult, UserABC, UserResult
|
|
2
|
+
from brilliance_admin.exceptions import AdminAPIException, APIError
|
|
3
|
+
from brilliance_admin.translations import TranslateText as _
|
|
4
|
+
from brilliance_admin.utils import get_logger
|
|
5
5
|
|
|
6
6
|
logger = get_logger()
|
|
7
7
|
|
|
@@ -54,7 +54,7 @@ class SQLAlchemyJWTAdminAuthentication(AdminAuthentication):
|
|
|
54
54
|
logger.exception(
|
|
55
55
|
'SQLAlchemy %s login db error: %s', type(self).__name__, e,
|
|
56
56
|
)
|
|
57
|
-
msg = _('connection_refused_error') % {'error': str(e)}
|
|
57
|
+
msg = _('errors.connection_refused_error') % {'error': str(e)}
|
|
58
58
|
raise AdminAPIException(
|
|
59
59
|
APIError(message=msg, code='connection_refused_error'),
|
|
60
60
|
status_code=500,
|
|
@@ -127,7 +127,7 @@ class SQLAlchemyJWTAdminAuthentication(AdminAuthentication):
|
|
|
127
127
|
logger.exception(
|
|
128
128
|
'SQLAlchemy %s authenticate db error: %s', type(self).__name__, e,
|
|
129
129
|
)
|
|
130
|
-
msg = _('connection_refused_error') % {'error': str(e)}
|
|
130
|
+
msg = _('errors.connection_refused_error') % {'error': str(e)}
|
|
131
131
|
raise AdminAPIException(
|
|
132
132
|
APIError(message=msg, code='connection_refused_error'),
|
|
133
133
|
status_code=500,
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
from
|
|
2
|
-
from
|
|
3
|
-
from
|
|
1
|
+
from brilliance_admin.auth import UserABC
|
|
2
|
+
from brilliance_admin.schema.table.table_models import AutocompleteData, AutocompleteResult
|
|
3
|
+
from brilliance_admin.translations import LanguageContext
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
class SQLAlchemyAdminAutocompleteMixin:
|
|
7
7
|
async def autocomplete(
|
|
8
|
-
self, data: AutocompleteData, user: UserABC,
|
|
8
|
+
self, data: AutocompleteData, user: UserABC, language_context: LanguageContext,
|
|
9
9
|
) -> AutocompleteResult:
|
|
10
10
|
form_schema = None
|
|
11
11
|
|
|
@@ -32,7 +32,11 @@ class SQLAlchemyAdminAutocompleteMixin:
|
|
|
32
32
|
if not field:
|
|
33
33
|
raise Exception(f'Field "{data.field_slug}" is not found')
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
results = await field.autocomplete(
|
|
36
|
+
self.model,
|
|
37
|
+
data,
|
|
38
|
+
user,
|
|
39
|
+
extra={'db_async_session': self.db_async_session},
|
|
40
|
+
)
|
|
37
41
|
|
|
38
42
|
return AutocompleteResult(results=results)
|
|
@@ -2,14 +2,14 @@ from typing import Any, List
|
|
|
2
2
|
|
|
3
3
|
from pydantic.dataclasses import dataclass
|
|
4
4
|
|
|
5
|
-
from
|
|
6
|
-
from
|
|
7
|
-
from
|
|
8
|
-
from
|
|
9
|
-
from
|
|
10
|
-
from
|
|
11
|
-
from
|
|
12
|
-
from
|
|
5
|
+
from brilliance_admin.auth import UserABC
|
|
6
|
+
from brilliance_admin.exceptions import AdminAPIException, APIError, FieldError
|
|
7
|
+
from brilliance_admin.schema.category import FieldSchemaData
|
|
8
|
+
from brilliance_admin.schema.table.fields.base import TableField
|
|
9
|
+
from brilliance_admin.schema.table.table_models import Record
|
|
10
|
+
from brilliance_admin.translations import LanguageContext
|
|
11
|
+
from brilliance_admin.translations import TranslateText as _
|
|
12
|
+
from brilliance_admin.utils import DeserializeAction
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
def get_pk(obj):
|
|
@@ -43,7 +43,7 @@ class SQLAlchemyRelatedField(TableField):
|
|
|
43
43
|
# - для доступа к связи через ORM
|
|
44
44
|
# getattr(record, rel_name)
|
|
45
45
|
# - для записи и чтения связанных объектов
|
|
46
|
-
rel_name: str | None
|
|
46
|
+
rel_name: str | None
|
|
47
47
|
|
|
48
48
|
# Класс связанной SQLAlchemy-модели.
|
|
49
49
|
# Откуда берётся:
|
|
@@ -58,8 +58,8 @@ class SQLAlchemyRelatedField(TableField):
|
|
|
58
58
|
# Работает только если many=True
|
|
59
59
|
dual_list: bool = False
|
|
60
60
|
|
|
61
|
-
def generate_schema(self, user: UserABC, field_slug,
|
|
62
|
-
schema = super().generate_schema(user, field_slug,
|
|
61
|
+
def generate_schema(self, user: UserABC, field_slug, language_context: LanguageContext) -> FieldSchemaData:
|
|
62
|
+
schema = super().generate_schema(user, field_slug, language_context)
|
|
63
63
|
schema.many = self.many
|
|
64
64
|
schema.rel_name = self.rel_name
|
|
65
65
|
schema.dual_list = self.dual_list
|
|
@@ -101,11 +101,11 @@ class SQLAlchemyRelatedField(TableField):
|
|
|
101
101
|
from sqlalchemy import select
|
|
102
102
|
from sqlalchemy.sql import expression
|
|
103
103
|
|
|
104
|
-
if extra is None or extra.get('
|
|
105
|
-
msg = f'SQLAlchemyRelatedField.autocomplete {type(self).__name__} requires extra["
|
|
104
|
+
if extra is None or extra.get('db_async_session') is None:
|
|
105
|
+
msg = f'SQLAlchemyRelatedField.autocomplete {type(self).__name__} requires extra["db_async_session"] (AsyncSession)'
|
|
106
106
|
raise AttributeError(msg)
|
|
107
107
|
|
|
108
|
-
|
|
108
|
+
db_async_session = extra['db_async_session']
|
|
109
109
|
|
|
110
110
|
results = []
|
|
111
111
|
|
|
@@ -125,7 +125,9 @@ class SQLAlchemyRelatedField(TableField):
|
|
|
125
125
|
if existed_choices and hasattr(target_model, 'id'):
|
|
126
126
|
stmt = stmt.where(getattr(target_model, 'id').in_(existed_choices) | expression.true())
|
|
127
127
|
|
|
128
|
-
|
|
128
|
+
async with db_async_session() as session:
|
|
129
|
+
records = (await session.execute(stmt)).scalars().all()
|
|
130
|
+
|
|
129
131
|
for record in records:
|
|
130
132
|
results.append(Record(key=getattr(record, 'id'), title=str(record)))
|
|
131
133
|
|
|
@@ -139,20 +141,22 @@ class SQLAlchemyRelatedField(TableField):
|
|
|
139
141
|
- value всегда scalar (None или int)
|
|
140
142
|
- ORM-объект доступен через extra["record"]
|
|
141
143
|
"""
|
|
144
|
+
if not value:
|
|
145
|
+
return
|
|
146
|
+
|
|
142
147
|
record = extra.get('record')
|
|
143
148
|
if record is None:
|
|
144
149
|
raise FieldError(f'Missing record in serialize context in value: {value}')
|
|
145
150
|
|
|
151
|
+
related = getattr(record, self.rel_name, None)
|
|
152
|
+
|
|
146
153
|
if self.many:
|
|
147
|
-
related = getattr(record, self.rel_name, None)
|
|
148
154
|
if related is None:
|
|
149
|
-
raise FieldError(f'Related field "{self.rel_name}" is missing on record {record}
|
|
150
|
-
|
|
155
|
+
raise FieldError(f'Many Related field "{self.rel_name}" is missing on record "{record}"')
|
|
151
156
|
return [{'key': get_pk(obj), 'title': str(obj)} for obj in related]
|
|
152
157
|
|
|
153
|
-
related = getattr(record, self.rel_name, None)
|
|
154
158
|
if related is None:
|
|
155
|
-
|
|
159
|
+
return None
|
|
156
160
|
|
|
157
161
|
return {'key': get_pk(related), 'title': str(related)}
|
|
158
162
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import datetime
|
|
2
2
|
from typing import Any
|
|
3
3
|
|
|
4
|
-
from
|
|
5
|
-
from
|
|
6
|
-
from
|
|
7
|
-
from
|
|
8
|
-
from
|
|
9
|
-
from
|
|
4
|
+
from brilliance_admin import schema
|
|
5
|
+
from brilliance_admin.exceptions import AdminAPIException, APIError
|
|
6
|
+
from brilliance_admin.integrations.sqlalchemy.fields import SQLAlchemyRelatedField
|
|
7
|
+
from brilliance_admin.schema.table.fields.base import DateTimeField
|
|
8
|
+
from brilliance_admin.translations import TranslateText as _
|
|
9
|
+
from brilliance_admin.utils import DeserializeAction, humanize_field_name
|
|
10
10
|
|
|
11
11
|
FIELD_FILTERS_NOT_FOUND = '{class_name} filter "{field_slug}" not found inside table_filters fields: {available_filters}'
|
|
12
12
|
|
|
@@ -54,9 +54,6 @@ class SQLAlchemyFieldsSchema(schema.FieldsSchema):
|
|
|
54
54
|
and not col.primary_key
|
|
55
55
|
)
|
|
56
56
|
|
|
57
|
-
if "choices" in info:
|
|
58
|
-
field_data["choices"] = [(c[0], c[1]) for c in info["choices"]]
|
|
59
|
-
|
|
60
57
|
col_type = col.type
|
|
61
58
|
try:
|
|
62
59
|
py_t = col_type.python_type
|
|
@@ -64,12 +61,16 @@ class SQLAlchemyFieldsSchema(schema.FieldsSchema):
|
|
|
64
61
|
py_t = None
|
|
65
62
|
|
|
66
63
|
impl = getattr(attr, 'impl', None)
|
|
67
|
-
|
|
64
|
+
is_impl_mutable = isinstance(impl, Mutable)
|
|
68
65
|
|
|
69
66
|
# Foreign key column
|
|
70
67
|
if col.foreign_keys:
|
|
71
68
|
continue
|
|
72
69
|
|
|
70
|
+
elif "choices" in info:
|
|
71
|
+
field_data["choices"] = info['choices']
|
|
72
|
+
field_class = schema.ChoiceField
|
|
73
|
+
|
|
73
74
|
elif isinstance(col_type, (sqltypes.BigInteger, sqltypes.Integer)) or py_t is int:
|
|
74
75
|
field_class = schema.IntegerField
|
|
75
76
|
|
|
@@ -97,7 +98,7 @@ class SQLAlchemyFieldsSchema(schema.FieldsSchema):
|
|
|
97
98
|
elif isinstance(col_type, ARRAY):
|
|
98
99
|
field_class = schema.ArrayField
|
|
99
100
|
field_data["array_type"] = type(col_type.item_type).__name__.lower()
|
|
100
|
-
field_data["read_only"] =
|
|
101
|
+
field_data["read_only"] = is_impl_mutable or isinstance(col_type, Mutable)
|
|
101
102
|
|
|
102
103
|
elif isinstance(col_type, sqltypes.NullType):
|
|
103
104
|
continue
|
|
@@ -126,6 +127,12 @@ class SQLAlchemyFieldsSchema(schema.FieldsSchema):
|
|
|
126
127
|
|
|
127
128
|
# relationship-поля
|
|
128
129
|
for rel in mapper.relationships:
|
|
130
|
+
# relationship, у которых есть локальные FK-колонки, не добавляем в схему,
|
|
131
|
+
# так как связь редактируется через scalar-поле (FK),
|
|
132
|
+
# а relationship используется только для ORM-навигации
|
|
133
|
+
if any(col.foreign_keys for col in rel.local_columns):
|
|
134
|
+
continue
|
|
135
|
+
|
|
129
136
|
field_slug = rel.key
|
|
130
137
|
|
|
131
138
|
field_data = {}
|
|
@@ -233,15 +240,17 @@ class SQLAlchemyFieldsSchema(schema.FieldsSchema):
|
|
|
233
240
|
return stmt
|
|
234
241
|
|
|
235
242
|
async def serialize(self, record, extra: dict, *args, **kwargs) -> dict:
|
|
236
|
-
# pylint: disable=import-outside-toplevel
|
|
237
|
-
from sqlalchemy import inspect
|
|
238
243
|
|
|
239
244
|
# Convert model values to dict
|
|
240
|
-
record_data = {
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
+
record_data = {}
|
|
246
|
+
|
|
247
|
+
for slug, field in self.get_fields().items():
|
|
248
|
+
# pylint: disable=protected-access
|
|
249
|
+
if field._type == 'related':
|
|
250
|
+
record_data[slug] = record
|
|
251
|
+
else:
|
|
252
|
+
record_data[slug] = getattr(record, slug, None)
|
|
253
|
+
|
|
245
254
|
return await super().serialize(record_data, extra, *args, **kwargs)
|
|
246
255
|
|
|
247
256
|
def validate_incoming_data(self, data):
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
from typing import Any
|
|
2
2
|
|
|
3
|
-
from
|
|
4
|
-
from
|
|
5
|
-
from
|
|
6
|
-
from
|
|
3
|
+
from brilliance_admin.integrations.sqlalchemy.autocomplete import SQLAlchemyAdminAutocompleteMixin
|
|
4
|
+
from brilliance_admin.integrations.sqlalchemy.fields_schema import SQLAlchemyFieldsSchema
|
|
5
|
+
from brilliance_admin.schema.table.category_table import CategoryTable
|
|
6
|
+
from brilliance_admin.translations import TranslateText as _
|
|
7
7
|
|
|
8
8
|
EXCEPTION_REL_NAME = '''
|
|
9
9
|
Model "{model_name}" doesn\'t contain rel_name:"{rel_name}" for field "{slug}"
|
|
@@ -123,11 +123,11 @@ class SQLAlchemyAdminBase(SQLAlchemyAdminAutocompleteMixin, CategoryTable):
|
|
|
123
123
|
# pylint: disable=protected-access
|
|
124
124
|
if field._type == "related":
|
|
125
125
|
|
|
126
|
-
# pylint: disable=import-outside-toplevel
|
|
127
|
-
from sqlalchemy import inspect
|
|
128
|
-
|
|
129
|
-
model_attrs = [attr.key for attr in inspect(self.model).mapper.attrs]
|
|
130
126
|
if not hasattr(self.model, field.rel_name):
|
|
127
|
+
# pylint: disable=import-outside-toplevel
|
|
128
|
+
from sqlalchemy import inspect
|
|
129
|
+
model_attrs = [attr.key for attr in inspect(self.model).mapper.attrs]
|
|
130
|
+
|
|
131
131
|
msg = EXCEPTION_REL_NAME.format(
|
|
132
132
|
slug=slug,
|
|
133
133
|
model_name=self.model.__name__,
|