brilliance-admin 0.44.6__tar.gz → 0.44.8__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.
Files changed (86) hide show
  1. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/PKG-INFO +9 -2
  2. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/README.md +8 -1
  3. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/brilliance_admin/api/views/autocomplete.py +1 -1
  4. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/brilliance_admin/api/views/table.py +5 -5
  5. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/brilliance_admin/integrations/sqlalchemy/autocomplete.py +6 -1
  6. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/brilliance_admin/integrations/sqlalchemy/table/create.py +2 -0
  7. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/brilliance_admin/integrations/sqlalchemy/table/delete.py +1 -1
  8. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/brilliance_admin/integrations/sqlalchemy/table/list.py +18 -6
  9. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/brilliance_admin/integrations/sqlalchemy/table/retrieve.py +15 -6
  10. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/brilliance_admin/integrations/sqlalchemy/table/update.py +5 -1
  11. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/brilliance_admin/locales/en.yml +1 -1
  12. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/brilliance_admin/locales/ru.yml +1 -1
  13. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/brilliance_admin/schema/admin_schema.py +4 -2
  14. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/brilliance_admin/schema/table/category_table.py +10 -7
  15. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/brilliance_admin/schema/table/fields/base.py +44 -5
  16. brilliance_admin-0.44.6/brilliance_admin/static/index-BV43pxcV.js → brilliance_admin-0.44.8/brilliance_admin/static/index-BSBmA7K6.js +119 -119
  17. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/brilliance_admin/templates/index.html +1 -1
  18. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/brilliance_admin.egg-info/PKG-INFO +9 -2
  19. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/brilliance_admin.egg-info/SOURCES.txt +1 -1
  20. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/pyproject.toml +1 -1
  21. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/tests/test_sqlalcmeny_crud.py +77 -2
  22. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/tests/test_sqlalcmeny_filters.py +10 -0
  23. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/LICENSE +0 -0
  24. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/brilliance_admin/__init__.py +0 -0
  25. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/brilliance_admin/api/__init__.py +0 -0
  26. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/brilliance_admin/api/routers.py +0 -0
  27. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/brilliance_admin/api/utils.py +0 -0
  28. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/brilliance_admin/api/views/__init__.py +0 -0
  29. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/brilliance_admin/api/views/auth.py +0 -0
  30. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/brilliance_admin/api/views/graphs.py +0 -0
  31. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/brilliance_admin/api/views/index.py +0 -0
  32. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/brilliance_admin/api/views/schema.py +0 -0
  33. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/brilliance_admin/api/views/settings.py +0 -0
  34. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/brilliance_admin/auth.py +0 -0
  35. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/brilliance_admin/docs.py +0 -0
  36. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/brilliance_admin/exceptions.py +0 -0
  37. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/brilliance_admin/integrations/__init__.py +0 -0
  38. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/brilliance_admin/integrations/sqlalchemy/__init__.py +0 -0
  39. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/brilliance_admin/integrations/sqlalchemy/auth.py +0 -0
  40. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/brilliance_admin/integrations/sqlalchemy/fields.py +0 -0
  41. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/brilliance_admin/integrations/sqlalchemy/fields_schema.py +0 -0
  42. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/brilliance_admin/integrations/sqlalchemy/table/__init__.py +0 -0
  43. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/brilliance_admin/integrations/sqlalchemy/table/base.py +0 -0
  44. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/brilliance_admin/schema/__init__.py +0 -0
  45. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/brilliance_admin/schema/category.py +0 -0
  46. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/brilliance_admin/schema/graphs/__init__.py +0 -0
  47. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/brilliance_admin/schema/graphs/category_graphs.py +0 -0
  48. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/brilliance_admin/schema/table/__init__.py +0 -0
  49. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/brilliance_admin/schema/table/admin_action.py +0 -0
  50. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/brilliance_admin/schema/table/fields/__init__.py +0 -0
  51. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/brilliance_admin/schema/table/fields/function_field.py +0 -0
  52. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/brilliance_admin/schema/table/fields_schema.py +0 -0
  53. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/brilliance_admin/schema/table/table_models.py +0 -0
  54. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/brilliance_admin/static/index-P_wdMBbz.css +0 -0
  55. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/brilliance_admin/static/materialdesignicons-webfont-CYDMK1kx.woff2 +0 -0
  56. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/brilliance_admin/static/materialdesignicons-webfont-CgCzGbLl.woff +0 -0
  57. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/brilliance_admin/static/materialdesignicons-webfont-D3kAzl71.ttf +0 -0
  58. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/brilliance_admin/static/materialdesignicons-webfont-DttUABo4.eot +0 -0
  59. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/brilliance_admin/static/tinymce/dark-first/content.min.css +0 -0
  60. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/brilliance_admin/static/tinymce/dark-first/skin.min.css +0 -0
  61. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/brilliance_admin/static/tinymce/dark-slim/content.min.css +0 -0
  62. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/brilliance_admin/static/tinymce/dark-slim/skin.min.css +0 -0
  63. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/brilliance_admin/static/tinymce/img/example.png +0 -0
  64. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/brilliance_admin/static/tinymce/img/tinymce.woff2 +0 -0
  65. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/brilliance_admin/static/tinymce/lightgray/content.min.css +0 -0
  66. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/brilliance_admin/static/tinymce/lightgray/fonts/tinymce.woff +0 -0
  67. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/brilliance_admin/static/tinymce/lightgray/skin.min.css +0 -0
  68. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/brilliance_admin/static/tinymce/plugins/accordion/css/accordion.css +0 -0
  69. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/brilliance_admin/static/tinymce/plugins/accordion/plugin.js +0 -0
  70. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/brilliance_admin/static/tinymce/plugins/codesample/css/prism.css +0 -0
  71. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/brilliance_admin/static/tinymce/plugins/customLink/css/link.css +0 -0
  72. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/brilliance_admin/static/tinymce/plugins/customLink/plugin.js +0 -0
  73. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/brilliance_admin/static/tinymce/tinymce.min.js +0 -0
  74. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/brilliance_admin/static/vanilla-picker-B6E6ObS_.js +0 -0
  75. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/brilliance_admin/translations.py +0 -0
  76. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/brilliance_admin/utils.py +0 -0
  77. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/brilliance_admin.egg-info/dependency_links.txt +0 -0
  78. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/brilliance_admin.egg-info/requires.txt +0 -0
  79. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/brilliance_admin.egg-info/top_level.txt +0 -0
  80. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/setup.cfg +0 -0
  81. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/tests/test_action.py +0 -0
  82. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/tests/test_payments_fields_schema.py +0 -0
  83. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/tests/test_settings.py +0 -0
  84. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/tests/test_sqlalcmeny_auth.py +0 -0
  85. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/tests/test_sqlalcmeny_schema.py +0 -0
  86. {brilliance_admin-0.44.6 → brilliance_admin-0.44.8}/tests/test_translations.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: brilliance-admin
3
- Version: 0.44.6
3
+ Version: 0.44.8
4
4
  Summary: Simple and lightweight data managment framework powered by FastAPI and Vue3 Vuetify all-in-one. Some call it heavenly in its brilliance.
5
5
  License-Expression: MIT
6
6
  Requires-Python: >=3.10
@@ -40,7 +40,7 @@ Dynamic: license-file
40
40
  [![PyPI](https://img.shields.io/pypi/v/brilliance-admin)](https://pypi.org/project/brilliance-admin/)
41
41
  [![CI](https://github.com/brilliance-admin/backend-python/actions/workflows/deploy.yml/badge.svg)](https://github.com/brilliance-admin/backend-python/actions)
42
42
 
43
- Simple and lightweight data managment framework powered by `FastAPI` and `Vue3` `Vuetify` all-in-one. \
43
+ Simple and lightweight data management framework powered by `FastAPI` and `Vue3` `Vuetify` all-in-one. \
44
44
  Integrated with `SQLAlchemy`. Inspaired by Django Admin and DRF.\
45
45
  _Some call it heavenly in its brilliance._
46
46
 
@@ -60,6 +60,13 @@ This endpoint can be added to any ASGI compatable backend. For existing project
60
60
  - API to fetch the UI JSON schema
61
61
  - API methods for that UI to work with (to read and modify data)
62
62
 
63
+ <details open>
64
+ <summary><h2>Screenshots</h2></summary>
65
+ <div align="center"><img src="https://github.com/brilliance-admin/.github/blob/main/screenshots/login.png?raw=true"/></div>
66
+ <div align="center"><img src="https://github.com/brilliance-admin/.github/blob/main/screenshots/table.png?raw=true"/></div>
67
+ <div align="center"><img src="https://github.com/brilliance-admin/.github/blob/main/screenshots/charts.png?raw=true"/></div>
68
+ </details>
69
+
63
70
  ## Key ideas
64
71
 
65
72
  - **API Oriented** <br>
@@ -6,7 +6,7 @@
6
6
  [![PyPI](https://img.shields.io/pypi/v/brilliance-admin)](https://pypi.org/project/brilliance-admin/)
7
7
  [![CI](https://github.com/brilliance-admin/backend-python/actions/workflows/deploy.yml/badge.svg)](https://github.com/brilliance-admin/backend-python/actions)
8
8
 
9
- Simple and lightweight data managment framework powered by `FastAPI` and `Vue3` `Vuetify` all-in-one. \
9
+ Simple and lightweight data management framework powered by `FastAPI` and `Vue3` `Vuetify` all-in-one. \
10
10
  Integrated with `SQLAlchemy`. Inspaired by Django Admin and DRF.\
11
11
  _Some call it heavenly in its brilliance._
12
12
 
@@ -26,6 +26,13 @@ This endpoint can be added to any ASGI compatable backend. For existing project
26
26
  - API to fetch the UI JSON schema
27
27
  - API methods for that UI to work with (to read and modify data)
28
28
 
29
+ <details open>
30
+ <summary><h2>Screenshots</h2></summary>
31
+ <div align="center"><img src="https://github.com/brilliance-admin/.github/blob/main/screenshots/login.png?raw=true"/></div>
32
+ <div align="center"><img src="https://github.com/brilliance-admin/.github/blob/main/screenshots/table.png?raw=true"/></div>
33
+ <div align="center"><img src="https://github.com/brilliance-admin/.github/blob/main/screenshots/charts.png?raw=true"/></div>
34
+ </details>
35
+
29
36
  ## Key ideas
30
37
 
31
38
  - **API Oriented** <br>
@@ -23,7 +23,7 @@ async def autocomplete(request: Request, group: str, category: str, data: Autoco
23
23
  context = {'language_context': language_context}
24
24
 
25
25
  try:
26
- result: AutocompleteResult = await schema_category.autocomplete(data, user, language_context)
26
+ result: AutocompleteResult = await schema_category.autocomplete(data, user, language_context, schema)
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:
@@ -30,7 +30,7 @@ async def table_list(request: Request, group: str, category: str, list_data: Lis
30
30
  context = {'language_context': language_context}
31
31
 
32
32
  try:
33
- result: TableListResult = await schema_category.get_list(list_data, user, language_context)
33
+ result: TableListResult = await schema_category.get_list(list_data, user, language_context, schema)
34
34
  except AdminAPIException as e:
35
35
  return JSONResponse(e.get_error().model_dump(mode='json', context=context), status_code=e.status_code)
36
36
 
@@ -54,7 +54,7 @@ async def table_retrieve(request: Request, group: str, category: str, pk: Any) -
54
54
  context = {'language_context': language_context}
55
55
 
56
56
  try:
57
- result: RetrieveResult = await schema_category.retrieve(pk, user, language_context)
57
+ result: RetrieveResult = await schema_category.retrieve(pk, user, language_context, schema)
58
58
  except AdminAPIException as e:
59
59
  return JSONResponse(e.get_error().model_dump(mode='json', context=context), status_code=e.status_code)
60
60
 
@@ -77,7 +77,7 @@ async def table_create(request: Request, group: str, category: str) -> CreateRes
77
77
  context = {'language_context': language_context}
78
78
 
79
79
  try:
80
- result: CreateResult = await schema_category.create(await request.json(), user, language_context)
80
+ result: CreateResult = await schema_category.create(await request.json(), user, language_context, schema)
81
81
  except AdminAPIException as e:
82
82
  return JSONResponse(e.get_error().model_dump(mode='json', context=context), status_code=e.status_code)
83
83
 
@@ -100,7 +100,7 @@ async def table_update(request: Request, group: str, category: str, pk: Any) ->
100
100
  context = {'language_context': language_context}
101
101
 
102
102
  try:
103
- result: UpdateResult = await schema_category.update(pk, await request.json(), user, language_context)
103
+ result: UpdateResult = await schema_category.update(pk, await request.json(), user, language_context, schema)
104
104
  except AdminAPIException as e:
105
105
  return JSONResponse(e.get_error().model_dump(mode='json', context=context), status_code=e.status_code)
106
106
 
@@ -129,7 +129,7 @@ async def table_action(
129
129
  try:
130
130
  # pylint: disable=protected-access
131
131
  result: ActionResult = await schema_category._perform_action(
132
- request, action, action_data, language_context, user,
132
+ request, action, action_data, language_context, user, schema,
133
133
  )
134
134
  except AdminAPIException as e:
135
135
  return JSONResponse(e.get_error().model_dump(mode='json', context=context), status_code=e.status_code)
@@ -1,11 +1,16 @@
1
1
  from brilliance_admin.auth import UserABC
2
+ from brilliance_admin.schema.admin_schema import AdminSchema
2
3
  from brilliance_admin.schema.table.table_models import AutocompleteData, AutocompleteResult
3
4
  from brilliance_admin.translations import LanguageContext
4
5
 
5
6
 
6
7
  class SQLAlchemyAdminAutocompleteMixin:
7
8
  async def autocomplete(
8
- self, data: AutocompleteData, user: UserABC, language_context: LanguageContext,
9
+ self,
10
+ data: AutocompleteData,
11
+ user: UserABC,
12
+ language_context: LanguageContext,
13
+ admin_schema: AdminSchema,
9
14
  ) -> AutocompleteResult:
10
15
  form_schema = None
11
16
 
@@ -1,6 +1,7 @@
1
1
  from brilliance_admin import schema
2
2
  from brilliance_admin.auth import UserABC
3
3
  from brilliance_admin.exceptions import AdminAPIException, APIError
4
+ from brilliance_admin.schema.admin_schema import AdminSchema
4
5
  from brilliance_admin.translations import LanguageContext
5
6
  from brilliance_admin.translations import TranslateText as _
6
7
  from brilliance_admin.utils import get_logger
@@ -16,6 +17,7 @@ class SQLAlchemyAdminCreate:
16
17
  data: dict,
17
18
  user: UserABC,
18
19
  language_context: LanguageContext,
20
+ admin_schema: AdminSchema,
19
21
  ) -> schema.CreateResult:
20
22
  if not self.has_create:
21
23
  raise AdminAPIException(APIError(message=_('errors.method_not_allowed')), status_code=500)
@@ -12,7 +12,7 @@ class SQLAlchemyDeleteAction:
12
12
  base_color='red-lighten-2',
13
13
  variant='outlined',
14
14
  )
15
- async def delete(self, action_data: ActionData):
15
+ async def delete(self, *args, action_data: ActionData, **kwargs):
16
16
  if not self.has_delete:
17
17
  raise AdminAPIException(APIError(message=_('errors.method_not_allowed')), status_code=500)
18
18
  return ActionResult(message=ActionMessage(_('deleted_successfully')))
@@ -1,6 +1,7 @@
1
1
  from brilliance_admin import auth, schema
2
2
  from brilliance_admin.exceptions import AdminAPIException, APIError, FieldError
3
3
  from brilliance_admin.integrations.sqlalchemy.fields_schema import SQLAlchemyFieldsSchema
4
+ from brilliance_admin.schema.admin_schema import AdminSchema
4
5
  from brilliance_admin.translations import LanguageContext
5
6
  from brilliance_admin.translations import TranslateText as _
6
7
  from brilliance_admin.utils import get_logger
@@ -88,6 +89,7 @@ class SQLAlchemyAdminListMixin:
88
89
  list_data: schema.ListData,
89
90
  user: auth.UserABC,
90
91
  language_context: LanguageContext,
92
+ admin_schema: AdminSchema,
91
93
  ) -> schema.TableListResult:
92
94
  # pylint: disable=import-outside-toplevel
93
95
  from sqlalchemy import exc, func, select
@@ -120,7 +122,10 @@ class SQLAlchemyAdminListMixin:
120
122
  'list_data': list_data,
121
123
  }
122
124
  )
123
- raise AdminAPIException(APIError(message=_('errors.filters_exception'), code='filters_exception'), status_code=500) from e
125
+ msg = _('errors.filters_exception') % {
126
+ 'error': str(e) if admin_schema.debug else type(e).__name__,
127
+ }
128
+ raise AdminAPIException(APIError(message=msg, code='filters_exception'), status_code=500) from e
124
129
 
125
130
  try:
126
131
  async with self.db_async_session() as session:
@@ -163,7 +168,9 @@ class SQLAlchemyAdminListMixin:
163
168
  'list_data': list_data,
164
169
  }
165
170
  )
166
- msg = _('errors.db_error_list') % {'error_type': type(e).__name__}
171
+ msg = _('errors.db_error_list') % {
172
+ 'error_type': str(e) if admin_schema.debug else type(e).__name__,
173
+ }
167
174
  raise AdminAPIException(
168
175
  APIError(message=msg, code='db_error_list'), status_code=500,
169
176
  ) from e
@@ -182,15 +189,20 @@ class SQLAlchemyAdminListMixin:
182
189
  'SQLAlchemy %s list %s serialize field error: %s',
183
190
  type(self).__name__, self.model.__name__, e,
184
191
  )
185
- msg = _('serialize_error.field_error') % {'error': e.message, 'field_slug': e.field_slug}
186
- raise AdminAPIException(APIError(message=msg, code='filters_exception'), status_code=500) from e
192
+ msg = _('serialize_error.field_error') % {
193
+ 'error': e.message,
194
+ 'field_slug': e.field_slug,
195
+ }
196
+ raise AdminAPIException(APIError(message=msg, code='field_error'), status_code=500) from e
187
197
 
188
198
  except Exception as e:
189
199
  logger.exception(
190
200
  'SQLAlchemy %s list %s serialize error: %s',
191
201
  type(self).__name__, self.model.__name__, e,
192
202
  )
193
- msg = _('serialize_error.unexpected_error') % {'error': str(e)}
194
- raise AdminAPIException(APIError(message=msg, code='filters_exception'), status_code=500) from e
203
+ msg = _('serialize_error.unexpected_error') % {
204
+ 'error': str(e) if admin_schema.debug else type(e).__name__,
205
+ }
206
+ raise AdminAPIException(APIError(message=msg, code='unexpected_error'), status_code=500) from e
195
207
 
196
208
  return schema.TableListResult(data=data, total_count=int(total_count or 0))
@@ -3,6 +3,7 @@ from typing import Any
3
3
  from brilliance_admin import auth, schema
4
4
  from brilliance_admin.exceptions import AdminAPIException, APIError, FieldError
5
5
  from brilliance_admin.integrations.sqlalchemy.fields_schema import SQLAlchemyFieldsSchema
6
+ from brilliance_admin.schema.admin_schema import AdminSchema
6
7
  from brilliance_admin.translations import LanguageContext
7
8
  from brilliance_admin.translations import TranslateText as _
8
9
  from brilliance_admin.utils import get_logger
@@ -20,8 +21,9 @@ class SQLAlchemyAdminRetrieveMixin:
20
21
  pk: Any,
21
22
  user: auth.UserABC,
22
23
  language_context: LanguageContext,
24
+ admin_schema: AdminSchema,
23
25
  ) -> schema.RetrieveResult:
24
- if not self.has_delete:
26
+ if not self.has_retrieve:
25
27
  raise AdminAPIException(APIError(message=_('errors.method_not_allowed')), status_code=500)
26
28
 
27
29
  # pylint: disable=import-outside-toplevel
@@ -42,7 +44,9 @@ class SQLAlchemyAdminRetrieveMixin:
42
44
  'SQLAlchemy %s retrieve %s #%s db error: %s',
43
45
  type(self).__name__, self.model.__name__, pk, e,
44
46
  )
45
- msg = _('errors.db_error_retrieve') % {'error_type': type(e).__name__}
47
+ msg = _('errors.db_error_retrieve') % {
48
+ 'error_type': str(e) if admin_schema.debug else type(e).__name__,
49
+ }
46
50
  raise AdminAPIException(
47
51
  APIError(message=msg, code='db_error_retrieve'), status_code=500,
48
52
  ) from e
@@ -64,16 +68,21 @@ class SQLAlchemyAdminRetrieveMixin:
64
68
  'SQLAlchemy %s retrieve %s #%s serialize field error: %s',
65
69
  type(self).__name__, self.model.__name__, pk, e,
66
70
  )
67
- msg = _('serialize_error.field_error') % {'error': e.message, 'field_slug': e.field_slug}
68
- raise AdminAPIException(APIError(message=msg, code='filters_exception'), status_code=500) from e
71
+ msg = _('serialize_error.field_error') % {
72
+ 'error': e.message,
73
+ 'field_slug': e.field_slug,
74
+ }
75
+ raise AdminAPIException(APIError(message=msg, code='field_error'), status_code=500) from e
69
76
 
70
77
  except Exception as e:
71
78
  logger.exception(
72
79
  'SQLAlchemy %s list %s #%s serialize error: %s',
73
80
  type(self).__name__, self.model.__name__, pk, e,
74
81
  )
75
- msg = _('serialize_error.unexpected_error') % {'error': str(e)}
76
- raise AdminAPIException(APIError(message=msg, code='filters_exception'), status_code=500) from e
82
+ msg = _('serialize_error.unexpected_error') % {
83
+ 'error': str(e) if admin_schema.debug else type(e).__name__,
84
+ }
85
+ raise AdminAPIException(APIError(message=msg, code='unexpected_error'), status_code=500) from e
77
86
 
78
87
  logger.debug(
79
88
  '%s model %s #%s retrieved by %s',
@@ -2,6 +2,7 @@ from typing import Any
2
2
 
3
3
  from brilliance_admin import auth, schema
4
4
  from brilliance_admin.exceptions import AdminAPIException, APIError
5
+ from brilliance_admin.schema.admin_schema import AdminSchema
5
6
  from brilliance_admin.translations import LanguageContext
6
7
  from brilliance_admin.translations import TranslateText as _
7
8
  from brilliance_admin.utils import get_logger
@@ -19,6 +20,7 @@ class SQLAlchemyAdminUpdate:
19
20
  data: dict,
20
21
  user: auth.UserABC,
21
22
  language_context: LanguageContext,
23
+ admin_schema: AdminSchema,
22
24
  ) -> schema.UpdateResult:
23
25
  if not self.has_update:
24
26
  raise AdminAPIException(APIError(message=_('errors.method_not_allowed')), status_code=500)
@@ -83,7 +85,9 @@ class SQLAlchemyAdminUpdate:
83
85
  type(self).__name__, self.table_schema.model.__name__, pk, e,
84
86
  extra={'data': data}
85
87
  )
86
- msg = _('errors.db_error_update') % {'error_type': type(e).__name__}
88
+ msg = _('errors.db_error_update') % {
89
+ 'error_type': str(e) if admin_schema.debug else type(e).__name__,
90
+ }
87
91
  raise AdminAPIException(
88
92
  APIError(message=msg, code='db_error_update'), status_code=500,
89
93
  ) from e
@@ -12,7 +12,7 @@ errors:
12
12
  db_error_list: 'Failed to retrieve table data from the database: %(error_type)s'
13
13
 
14
14
  connection_refused_error: 'Database connection error: %(error)s'
15
- filters_exception: 'An unknown technical error occurred while filtering data.'
15
+ filters_exception: 'An unknown technical error occurred while filtering data: %(error_type)s'
16
16
  method_not_allowed: 'Error, method not allowed. This action is not permitted.'
17
17
  filter_error: 'An error occurred during filtering: {error}'
18
18
  bad_type_error: 'Invalid data type: %(type)s but %(expected)s expected'
@@ -12,7 +12,7 @@ errors:
12
12
  db_error_list: 'Ошибка получения данных таблицы из базы данных: %(error_type)s'
13
13
 
14
14
  connection_refused_error: 'Ошибка подключения к базе данных: %(error)s'
15
- filters_exception: 'Произошла неизвестная техническая ошибка при фильтрации данных.'
15
+ filters_exception: 'Произошла неизвестная техническая ошибка при фильтрации данных: %(error_type)s'
16
16
  method_not_allowed: 'Ошибка, данный метод недоступен.'
17
17
  filter_error: 'Проишла ошибка при фильтрации: %(error)s'
18
18
  bad_type_error: 'Некорректный тип данных: %(type)s; ожидается %(expected)s'
@@ -68,6 +68,8 @@ class AdminSchema:
68
68
 
69
69
  language_manager: LanguageManager | None = None
70
70
 
71
+ debug: bool = False
72
+
71
73
  def __post_init__(self):
72
74
  for category in self.categories:
73
75
  if not issubclass(category.__class__, BaseCategory):
@@ -122,8 +124,6 @@ class AdminSchema:
122
124
  languages=languages,
123
125
  )
124
126
 
125
- # pylint: disable=too-many-arguments
126
- # pylint: disable=too-many-positional-arguments
127
127
  def generate_app(
128
128
  self,
129
129
  debug=False,
@@ -133,6 +133,8 @@ class AdminSchema:
133
133
  include_docs=False,
134
134
  include_redoc=False,
135
135
  ) -> FastAPI:
136
+ self.debug = debug
137
+
136
138
  # pylint: disable=unused-variable
137
139
  language_context = self.get_language_context(language_slug=None)
138
140
 
@@ -8,8 +8,8 @@ from pydantic import Field
8
8
 
9
9
  from brilliance_admin.auth import UserABC
10
10
  from brilliance_admin.exceptions import AdminAPIException, APIError
11
- from brilliance_admin.schema.category import BaseCategory
12
- from brilliance_admin.schema.category import TableInfoSchemaData
11
+ from brilliance_admin.schema.admin_schema import AdminSchema
12
+ from brilliance_admin.schema.category import BaseCategory, TableInfoSchemaData
13
13
  from brilliance_admin.schema.table.admin_action import ActionData, ActionResult
14
14
  from brilliance_admin.schema.table.fields_schema import FieldsSchema
15
15
  from brilliance_admin.schema.table.table_models import AutocompleteData, AutocompleteResult, ListData, TableListResult
@@ -130,6 +130,7 @@ class CategoryTable(BaseCategory):
130
130
  action_data: ActionData,
131
131
  language_context: LanguageContext,
132
132
  user: UserABC,
133
+ admin_schema: AdminSchema,
133
134
  ) -> ActionResult:
134
135
  action_fn = self._get_action_fn(action)
135
136
  if action_fn is None:
@@ -156,7 +157,7 @@ class CategoryTable(BaseCategory):
156
157
 
157
158
  return result
158
159
 
159
- async def autocomplete(self, data: AutocompleteData, user: UserABC) -> AutocompleteResult:
160
+ async def autocomplete(self, data: AutocompleteData, user: UserABC, schema: AdminSchema) -> AutocompleteResult:
160
161
  """
161
162
  Retrieves list of found options to select.
162
163
  """
@@ -164,14 +165,16 @@ class CategoryTable(BaseCategory):
164
165
 
165
166
  # pylint: disable=too-many-arguments
166
167
  @abc.abstractmethod
167
- async def get_list(self, list_data: ListData, user: UserABC, language_context: LanguageContext) -> TableListResult:
168
+ async def get_list(
169
+ self, list_data: ListData, user: UserABC, language_context: LanguageContext, admin_schema: AdminSchema
170
+ ) -> TableListResult:
168
171
  raise NotImplementedError()
169
172
 
170
- # async def retrieve(self, pk: Any, user: UserABC) -> RetrieveResult:
173
+ # async def retrieve(self, pk: Any, user: UserABC, language_context: LanguageContext, admin_schema: AdminSchema) -> RetrieveResult:
171
174
  # raise NotImplementedError()
172
175
 
173
- # async def create(self, data: dict, user: UserABC) -> CreateResult:
176
+ # async def create(self, data: dict, user: UserABC, language_context: LanguageContext, admin_schema: AdminSchema) -> CreateResult:
174
177
  # raise NotImplementedError()
175
178
 
176
- # async def update(self, pk: Any, data: dict, user: UserABC) -> UpdateResult:
179
+ # async def update(self, pk: Any, data: dict, user: UserABC, language_context: LanguageContext, admin_schema: AdminSchema) -> UpdateResult:
177
180
  # raise NotImplementedError()
@@ -127,6 +127,18 @@ class BooleanField(TableField):
127
127
  _type = 'boolean'
128
128
 
129
129
 
130
+ def _parse_iso(value: str) -> datetime.datetime:
131
+ if value.endswith('Z'):
132
+ value = value.replace('Z', '+00:00')
133
+
134
+ dt = datetime.datetime.fromisoformat(value)
135
+
136
+ if dt.tzinfo is None:
137
+ dt = dt.replace(tzinfo=datetime.timezone.utc)
138
+
139
+ return dt
140
+
141
+
130
142
  @dataclass
131
143
  class DateTimeField(TableField):
132
144
  _type = 'datetime'
@@ -155,16 +167,17 @@ class DateTimeField(TableField):
155
167
  raise FieldError(_('errors.bad_type_error') % {'type': type(value), 'expected': 'datetime'})
156
168
 
157
169
  if isinstance(value, str):
158
- return datetime.datetime.strptime(value, self.format)
170
+ return _parse_iso(value)
159
171
 
160
172
  if isinstance(value, dict):
161
173
  if not value.get('from') or not value.get('to'):
162
- msg = f'{type(self).__name__} value must be dict with from,to values: {value}'
163
- raise FieldError(msg)
174
+ raise FieldError(
175
+ f'{type(self).__name__} value must be dict with from,to values: {value}'
176
+ )
164
177
 
165
178
  return {
166
- 'from': datetime.datetime.strptime(value.get('from'), self.format),
167
- 'to': datetime.datetime.strptime(value.get('to'), self.format),
179
+ 'from': _parse_iso(value['from']),
180
+ 'to': _parse_iso(value['to']),
168
181
  }
169
182
 
170
183
  raise FieldError(_('errors.bad_type_error') % {'type': type(value), 'expected': 'datetime'})
@@ -291,3 +304,29 @@ class ChoiceField(TableField):
291
304
  'value': value,
292
305
  'title': choice.get('title') or value if choice else value.capitalize(),
293
306
  }
307
+
308
+ async def deserialize(self, value, action: DeserializeAction, extra: dict, *args, **kwargs) -> Any:
309
+ value = await super().deserialize(value, action, extra, *args, **kwargs)
310
+
311
+ if value is None:
312
+ return
313
+
314
+ if isinstance(value, dict):
315
+ if 'value' not in value:
316
+ raise FieldError(
317
+ f'{type(self).__name__} dict value must contain "value": {value}'
318
+ )
319
+ value = value['value']
320
+
321
+ if not isinstance(value, str):
322
+ raise FieldError(
323
+ f'{type(self).__name__} value must be str, got {type(value)}'
324
+ )
325
+
326
+ choice = self.find_choice(value)
327
+ if not choice:
328
+ raise FieldError(
329
+ f'Invalid choice value "{value}", allowed: {[c["value"] for c in self.choices or []]}'
330
+ )
331
+
332
+ return value