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.
Files changed (81) hide show
  1. {admin_panel → brilliance_admin}/__init__.py +2 -2
  2. brilliance_admin/api/routers.py +18 -0
  3. {admin_panel → brilliance_admin}/api/utils.py +1 -1
  4. {admin_panel → brilliance_admin}/api/views/auth.py +6 -6
  5. {admin_panel → brilliance_admin}/api/views/autocomplete.py +9 -9
  6. {admin_panel → brilliance_admin}/api/views/graphs.py +8 -8
  7. {admin_panel → brilliance_admin}/api/views/index.py +11 -4
  8. {admin_panel → brilliance_admin}/api/views/schema.py +3 -3
  9. {admin_panel → brilliance_admin}/api/views/settings.py +5 -5
  10. {admin_panel → brilliance_admin}/api/views/table.py +23 -23
  11. {admin_panel → brilliance_admin}/auth.py +1 -1
  12. {admin_panel → brilliance_admin}/exceptions.py +3 -4
  13. {admin_panel → brilliance_admin}/integrations/sqlalchemy/__init__.py +1 -0
  14. {admin_panel → brilliance_admin}/integrations/sqlalchemy/auth.py +6 -6
  15. {admin_panel → brilliance_admin}/integrations/sqlalchemy/autocomplete.py +10 -6
  16. {admin_panel → brilliance_admin}/integrations/sqlalchemy/fields.py +24 -20
  17. {admin_panel → brilliance_admin}/integrations/sqlalchemy/fields_schema.py +27 -18
  18. {admin_panel → brilliance_admin}/integrations/sqlalchemy/table/base.py +8 -8
  19. {admin_panel → brilliance_admin}/integrations/sqlalchemy/table/create.py +10 -10
  20. {admin_panel → brilliance_admin}/integrations/sqlalchemy/table/delete.py +4 -4
  21. {admin_panel → brilliance_admin}/integrations/sqlalchemy/table/list.py +11 -11
  22. {admin_panel → brilliance_admin}/integrations/sqlalchemy/table/retrieve.py +22 -10
  23. {admin_panel → brilliance_admin}/integrations/sqlalchemy/table/update.py +11 -11
  24. brilliance_admin/locales/en.yml +25 -0
  25. brilliance_admin/locales/ru.yml +26 -0
  26. {admin_panel → brilliance_admin}/schema/admin_schema.py +39 -31
  27. {admin_panel → brilliance_admin}/schema/category.py +8 -9
  28. {admin_panel → brilliance_admin}/schema/graphs/category_graphs.py +10 -9
  29. {admin_panel → brilliance_admin}/schema/group.py +10 -10
  30. {admin_panel → brilliance_admin}/schema/table/admin_action.py +8 -7
  31. {admin_panel → brilliance_admin}/schema/table/category_table.py +21 -21
  32. {admin_panel → brilliance_admin}/schema/table/fields/base.py +58 -29
  33. {admin_panel → brilliance_admin}/schema/table/fields/function_field.py +3 -3
  34. {admin_panel → brilliance_admin}/schema/table/fields_schema.py +11 -10
  35. {admin_panel → brilliance_admin}/schema/table/table_models.py +1 -1
  36. admin_panel/static/index-BeniOHDv.js → brilliance_admin/static/index-BnnESruI.js +131 -131
  37. {admin_panel → brilliance_admin}/templates/index.html +1 -1
  38. brilliance_admin/translations.py +115 -0
  39. brilliance_admin/utils.py +153 -0
  40. brilliance_admin-0.43.7.dist-info/METADATA +217 -0
  41. brilliance_admin-0.43.7.dist-info/RECORD +74 -0
  42. brilliance_admin-0.43.7.dist-info/top_level.txt +1 -0
  43. admin_panel/api/routers.py +0 -18
  44. admin_panel/static/favicon.jpg +0 -0
  45. admin_panel/translations.py +0 -145
  46. admin_panel/utils.py +0 -50
  47. brilliance_admin-0.42.0.dist-info/METADATA +0 -155
  48. brilliance_admin-0.42.0.dist-info/RECORD +0 -73
  49. brilliance_admin-0.42.0.dist-info/top_level.txt +0 -1
  50. {admin_panel → brilliance_admin}/api/__init__.py +0 -0
  51. {admin_panel → brilliance_admin}/api/views/__init__.py +0 -0
  52. {admin_panel → brilliance_admin}/docs.py +0 -0
  53. {admin_panel → brilliance_admin}/integrations/__init__.py +0 -0
  54. {admin_panel → brilliance_admin}/integrations/sqlalchemy/table/__init__.py +0 -0
  55. {admin_panel → brilliance_admin}/schema/__init__.py +0 -0
  56. {admin_panel → brilliance_admin}/schema/graphs/__init__.py +0 -0
  57. {admin_panel → brilliance_admin}/schema/table/__init__.py +0 -0
  58. {admin_panel → brilliance_admin}/schema/table/fields/__init__.py +0 -0
  59. {admin_panel → brilliance_admin}/static/index-vlBToOhT.css +0 -0
  60. {admin_panel → brilliance_admin}/static/materialdesignicons-webfont-CYDMK1kx.woff2 +0 -0
  61. {admin_panel → brilliance_admin}/static/materialdesignicons-webfont-CgCzGbLl.woff +0 -0
  62. {admin_panel → brilliance_admin}/static/materialdesignicons-webfont-D3kAzl71.ttf +0 -0
  63. {admin_panel → brilliance_admin}/static/materialdesignicons-webfont-DttUABo4.eot +0 -0
  64. {admin_panel → brilliance_admin}/static/tinymce/dark-first/content.min.css +0 -0
  65. {admin_panel → brilliance_admin}/static/tinymce/dark-first/skin.min.css +0 -0
  66. {admin_panel → brilliance_admin}/static/tinymce/dark-slim/content.min.css +0 -0
  67. {admin_panel → brilliance_admin}/static/tinymce/dark-slim/skin.min.css +0 -0
  68. {admin_panel → brilliance_admin}/static/tinymce/img/example.png +0 -0
  69. {admin_panel → brilliance_admin}/static/tinymce/img/tinymce.woff2 +0 -0
  70. {admin_panel → brilliance_admin}/static/tinymce/lightgray/content.min.css +0 -0
  71. {admin_panel → brilliance_admin}/static/tinymce/lightgray/fonts/tinymce.woff +0 -0
  72. {admin_panel → brilliance_admin}/static/tinymce/lightgray/skin.min.css +0 -0
  73. {admin_panel → brilliance_admin}/static/tinymce/plugins/accordion/css/accordion.css +0 -0
  74. {admin_panel → brilliance_admin}/static/tinymce/plugins/accordion/plugin.js +0 -0
  75. {admin_panel → brilliance_admin}/static/tinymce/plugins/codesample/css/prism.css +0 -0
  76. {admin_panel → brilliance_admin}/static/tinymce/plugins/customLink/css/link.css +0 -0
  77. {admin_panel → brilliance_admin}/static/tinymce/plugins/customLink/plugin.js +0 -0
  78. {admin_panel → brilliance_admin}/static/tinymce/tinymce.min.js +0 -0
  79. {admin_panel → brilliance_admin}/static/vanilla-picker-B6E6ObS_.js +0 -0
  80. {brilliance_admin-0.42.0.dist-info → brilliance_admin-0.43.7.dist-info}/WHEEL +0 -0
  81. {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 admin_panel.integrations import sqlalchemy
4
- from admin_panel import schema
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,6 +1,6 @@
1
1
  from fastapi import HTTPException
2
2
 
3
- from admin_panel.auth import AdminAuthentication
3
+ from brilliance_admin.auth import AdminAuthentication
4
4
 
5
5
 
6
6
  async def get_user(request):
@@ -1,10 +1,10 @@
1
1
  from fastapi import APIRouter, Request
2
2
  from fastapi.responses import JSONResponse
3
3
 
4
- from admin_panel.auth import AdminAuthentication, AuthData, AuthResult
5
- from admin_panel.exceptions import AdminAPIException, APIError
6
- from admin_panel.schema.admin_schema import AdminSchema
7
- from admin_panel.translations import LanguageManager
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
- language_manager: LanguageManager = schema.get_language_manager(language_slug)
21
- context = {'language_manager': language_manager}
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 admin_panel.api.utils import get_category
5
- from admin_panel.exceptions import AdminAPIException
6
- from admin_panel.schema.admin_schema import AdminSchema
7
- from admin_panel.schema.table.table_models import AutocompleteData, AutocompleteResult
8
- from admin_panel.translations import LanguageManager
9
- from admin_panel.utils import get_logger
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
- language_manager: LanguageManager = schema.get_language_manager(language_slug)
23
- context = {'language_manager': language_manager}
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, language_manager)
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 admin_panel.api.utils import get_category
5
- from admin_panel.exceptions import AdminAPIException
6
- from admin_panel.schema.admin_schema import AdminSchema
7
- from admin_panel.schema.graphs.category_graphs import CategoryGraphs, GraphData, GraphsDataResult
8
- from admin_panel.translations import LanguageManager
9
- from admin_panel.utils import get_logger
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
- language_manager: LanguageManager = schema.get_language_manager(language_slug)
25
- context = {'language_manager': language_manager}
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 admin_panel.schema import AdminSchema
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("admin_panel", "templates"),
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 = "/" + rest_of_path
29
- if path in EXACT_BLOCK or path.startswith(PREFIX_BLOCK):
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 admin_panel.auth import AdminAuthentication
5
- from admin_panel.exceptions import AdminAPIException, APIError
6
- from admin_panel.schema import AdminSchema, AdminSchemaData
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 admin_panel.exceptions import AdminAPIException, APIError
5
- from admin_panel.schema.admin_schema import AdminSchema, AdminSettingsData
6
- from admin_panel.translations import LanguageManager
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
- language_manager: LanguageManager = schema.get_language_manager(language_slug)
23
- context = {'language_manager': language_manager}
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 admin_panel.api.utils import get_category
7
- from admin_panel.exceptions import AdminAPIException, APIError
8
- from admin_panel.schema import AdminSchema
9
- from admin_panel.schema.table.admin_action import ActionData, ActionResult
10
- from admin_panel.schema.table.category_table import CategoryTable
11
- from admin_panel.schema.table.table_models import CreateResult, ListData, RetrieveResult, TableListResult, UpdateResult
12
- from admin_panel.translations import LanguageManager
13
- from admin_panel.utils import get_logger
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
- language_manager: LanguageManager = schema.get_language_manager(language_slug)
29
- context = {'language_manager': language_manager}
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, language_manager)
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
- language_manager: LanguageManager = schema.get_language_manager(language_slug)
53
- context = {'language_manager': language_manager}
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, language_manager)
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
- language_manager: LanguageManager = schema.get_language_manager(language_slug)
76
- context = {'language_manager': language_manager}
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, language_manager)
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
- language_manager: LanguageManager = schema.get_language_manager(language_slug)
99
- context = {'language_manager': language_manager}
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, language_manager)
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
- language_manager: LanguageManager = schema.get_language_manager(language_slug)
126
- context = {'language_manager': language_manager}
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, language_manager, user,
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)
@@ -2,7 +2,7 @@ import abc
2
2
 
3
3
  from pydantic import BaseModel
4
4
  from pydantic.dataclasses import dataclass
5
- from admin_panel.utils import DataclassBase
5
+ from brilliance_admin.utils import DataclassBase
6
6
 
7
7
 
8
8
  @dataclass
@@ -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 admin_panel.translations import TranslateText
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: str | TranslateText = None
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: str | TranslateText | None = None
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 admin_panel.auth import AdminAuthentication, AuthData, AuthResult, UserABC, UserResult
2
- from admin_panel.exceptions import AdminAPIException, APIError
3
- from admin_panel.translations import TranslateText as _
4
- from admin_panel.utils import get_logger
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 admin_panel.auth import UserABC
2
- from admin_panel.schema.table.table_models import AutocompleteData, AutocompleteResult
3
- from admin_panel.translations import LanguageManager
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, language_manager: LanguageManager,
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
- async with self.db_async_session() as session:
36
- results = await field.autocomplete(self.model, data, user, extra={'db_session': session})
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 admin_panel.auth import UserABC
6
- from admin_panel.exceptions import AdminAPIException, APIError, FieldError
7
- from admin_panel.schema.category import FieldSchemaData
8
- from admin_panel.schema.table.fields.base import TableField
9
- from admin_panel.schema.table.table_models import Record
10
- from admin_panel.translations import LanguageManager
11
- from admin_panel.translations import TranslateText as _
12
- from admin_panel.utils import DeserializeAction
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 = 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, language_manager: LanguageManager) -> FieldSchemaData:
62
- schema = super().generate_schema(user, field_slug, language_manager)
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('db_session') is None:
105
- msg = f'SQLAlchemyRelatedField.autocomplete {type(self).__name__} requires extra["db_session"] (AsyncSession)'
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
- session = extra['db_session']
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
- records = (await session.execute(stmt)).scalars().all()
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} (many=True)')
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
- raise FieldError(f'Related field "{self.rel_name}" is missing on record (many=False)')
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 admin_panel import schema
5
- from admin_panel.exceptions import AdminAPIException, APIError
6
- from admin_panel.integrations.sqlalchemy.fields import SQLAlchemyRelatedField
7
- from admin_panel.schema.table.fields.base import DateTimeField
8
- from admin_panel.translations import TranslateText as _
9
- from admin_panel.utils import DeserializeAction, humanize_field_name
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
- is_mutable = isinstance(impl, Mutable)
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"] = not is_mutable
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
- attr.key: getattr(record, attr.key, None)
242
- for attr in inspect(record).mapper.column_attrs
243
- if self.get_field(attr.key)
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 admin_panel.integrations.sqlalchemy.autocomplete import SQLAlchemyAdminAutocompleteMixin
4
- from admin_panel.integrations.sqlalchemy.fields_schema import SQLAlchemyFieldsSchema
5
- from admin_panel.schema.table.category_table import CategoryTable
6
- from admin_panel.translations import TranslateText as _
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__,