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,9 +1,9 @@
|
|
|
1
|
-
from
|
|
2
|
-
from
|
|
3
|
-
from
|
|
4
|
-
from
|
|
5
|
-
from
|
|
6
|
-
from
|
|
1
|
+
from brilliance_admin import schema
|
|
2
|
+
from brilliance_admin.auth import UserABC
|
|
3
|
+
from brilliance_admin.exceptions import AdminAPIException, APIError
|
|
4
|
+
from brilliance_admin.translations import LanguageContext
|
|
5
|
+
from brilliance_admin.translations import TranslateText as _
|
|
6
|
+
from brilliance_admin.utils import get_logger
|
|
7
7
|
|
|
8
8
|
logger = get_logger()
|
|
9
9
|
|
|
@@ -15,10 +15,10 @@ class SQLAlchemyAdminCreate:
|
|
|
15
15
|
self,
|
|
16
16
|
data: dict,
|
|
17
17
|
user: UserABC,
|
|
18
|
-
|
|
18
|
+
language_context: LanguageContext,
|
|
19
19
|
) -> schema.CreateResult:
|
|
20
20
|
if not self.has_create:
|
|
21
|
-
raise AdminAPIException(APIError(message=_('method_not_allowed')), status_code=500)
|
|
21
|
+
raise AdminAPIException(APIError(message=_('errors.method_not_allowed')), status_code=500)
|
|
22
22
|
|
|
23
23
|
# pylint: disable=import-outside-toplevel
|
|
24
24
|
from sqlalchemy.exc import IntegrityError
|
|
@@ -37,7 +37,7 @@ class SQLAlchemyAdminCreate:
|
|
|
37
37
|
type(self).__name__, self.table_schema.model.__name__, e,
|
|
38
38
|
extra={'data': data},
|
|
39
39
|
)
|
|
40
|
-
msg = _('connection_refused_error') % {'error': str(e)}
|
|
40
|
+
msg = _('errors.connection_refused_error') % {'error': str(e)}
|
|
41
41
|
raise AdminAPIException(
|
|
42
42
|
APIError(message=msg, code='connection_refused_error'),
|
|
43
43
|
status_code=500,
|
|
@@ -62,7 +62,7 @@ class SQLAlchemyAdminCreate:
|
|
|
62
62
|
extra={'data': data},
|
|
63
63
|
)
|
|
64
64
|
raise AdminAPIException(
|
|
65
|
-
APIError(message=_('db_error_create'), code='db_error_create'), status_code=500,
|
|
65
|
+
APIError(message=_('errors.db_error_create'), code='db_error_create'), status_code=500,
|
|
66
66
|
) from e
|
|
67
67
|
|
|
68
68
|
logger.info(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
from
|
|
2
|
-
from
|
|
3
|
-
from
|
|
1
|
+
from brilliance_admin.exceptions import APIError, AdminAPIException
|
|
2
|
+
from brilliance_admin.schema.table.admin_action import ActionData, ActionMessage, ActionResult, admin_action
|
|
3
|
+
from brilliance_admin.translations import TranslateText as _
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
class SQLAlchemyDeleteAction:
|
|
@@ -14,5 +14,5 @@ class SQLAlchemyDeleteAction:
|
|
|
14
14
|
)
|
|
15
15
|
async def delete(self, action_data: ActionData):
|
|
16
16
|
if not self.has_delete:
|
|
17
|
-
raise AdminAPIException(APIError(message=_('method_not_allowed')), status_code=500)
|
|
17
|
+
raise AdminAPIException(APIError(message=_('errors.method_not_allowed')), status_code=500)
|
|
18
18
|
return ActionResult(message=ActionMessage(_('deleted_successfully')))
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
from
|
|
2
|
-
from
|
|
3
|
-
from
|
|
4
|
-
from
|
|
5
|
-
from
|
|
6
|
-
from
|
|
1
|
+
from brilliance_admin import auth, schema
|
|
2
|
+
from brilliance_admin.exceptions import AdminAPIException, APIError, FieldError
|
|
3
|
+
from brilliance_admin.integrations.sqlalchemy.fields_schema import SQLAlchemyFieldsSchema
|
|
4
|
+
from brilliance_admin.translations import LanguageContext
|
|
5
|
+
from brilliance_admin.translations import TranslateText as _
|
|
6
|
+
from brilliance_admin.utils import get_logger
|
|
7
7
|
|
|
8
8
|
logger = get_logger()
|
|
9
9
|
|
|
@@ -87,7 +87,7 @@ class SQLAlchemyAdminListMixin:
|
|
|
87
87
|
self,
|
|
88
88
|
list_data: schema.ListData,
|
|
89
89
|
user: auth.UserABC,
|
|
90
|
-
|
|
90
|
+
language_context: LanguageContext,
|
|
91
91
|
) -> schema.TableListResult:
|
|
92
92
|
# pylint: disable=import-outside-toplevel
|
|
93
93
|
from sqlalchemy import exc, func, select
|
|
@@ -109,7 +109,7 @@ class SQLAlchemyAdminListMixin:
|
|
|
109
109
|
'list_data': list_data,
|
|
110
110
|
}
|
|
111
111
|
)
|
|
112
|
-
msg = _('filter_error') % {'error': e.message}
|
|
112
|
+
msg = _('errors.filter_error') % {'error': e.message}
|
|
113
113
|
raise AdminAPIException(APIError(message=msg, code='filters_exception'), status_code=500) from e
|
|
114
114
|
|
|
115
115
|
except Exception as e:
|
|
@@ -120,7 +120,7 @@ class SQLAlchemyAdminListMixin:
|
|
|
120
120
|
'list_data': list_data,
|
|
121
121
|
}
|
|
122
122
|
)
|
|
123
|
-
raise AdminAPIException(APIError(message=_('filters_exception'), code='filters_exception'), status_code=500) from e
|
|
123
|
+
raise AdminAPIException(APIError(message=_('errors.filters_exception'), code='filters_exception'), status_code=500) from e
|
|
124
124
|
|
|
125
125
|
data = []
|
|
126
126
|
|
|
@@ -143,7 +143,7 @@ class SQLAlchemyAdminListMixin:
|
|
|
143
143
|
'list_data': list_data,
|
|
144
144
|
}
|
|
145
145
|
)
|
|
146
|
-
msg = _('connection_refused_error') % {'error': str(e)}
|
|
146
|
+
msg = _('errors.connection_refused_error') % {'error': str(e)}
|
|
147
147
|
raise AdminAPIException(
|
|
148
148
|
APIError(message=msg, code='connection_refused_error'),
|
|
149
149
|
status_code=500,
|
|
@@ -172,7 +172,7 @@ class SQLAlchemyAdminListMixin:
|
|
|
172
172
|
}
|
|
173
173
|
)
|
|
174
174
|
raise AdminAPIException(
|
|
175
|
-
APIError(message=_('db_error_list'), code='db_error_list'), status_code=500,
|
|
175
|
+
APIError(message=_('errors.db_error_list'), code='db_error_list'), status_code=500,
|
|
176
176
|
) from e
|
|
177
177
|
|
|
178
178
|
return schema.TableListResult(data=data, total_count=int(total_count or 0))
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
from typing import Any
|
|
2
2
|
|
|
3
|
-
from
|
|
4
|
-
from
|
|
5
|
-
from
|
|
6
|
-
from
|
|
7
|
-
from
|
|
3
|
+
from brilliance_admin import auth, schema
|
|
4
|
+
from brilliance_admin.exceptions import AdminAPIException, APIError, FieldError
|
|
5
|
+
from brilliance_admin.integrations.sqlalchemy.fields_schema import SQLAlchemyFieldsSchema
|
|
6
|
+
from brilliance_admin.translations import LanguageContext
|
|
7
|
+
from brilliance_admin.translations import TranslateText as _
|
|
8
|
+
from brilliance_admin.utils import get_logger
|
|
8
9
|
|
|
9
10
|
logger = get_logger()
|
|
10
11
|
|
|
@@ -12,14 +13,16 @@ logger = get_logger()
|
|
|
12
13
|
class SQLAlchemyAdminRetrieveMixin:
|
|
13
14
|
has_retrieve: bool = True
|
|
14
15
|
|
|
16
|
+
table_schema: SQLAlchemyFieldsSchema
|
|
17
|
+
|
|
15
18
|
async def retrieve(
|
|
16
19
|
self,
|
|
17
20
|
pk: Any,
|
|
18
21
|
user: auth.UserABC,
|
|
19
|
-
|
|
22
|
+
language_context: LanguageContext,
|
|
20
23
|
) -> schema.RetrieveResult:
|
|
21
24
|
if not self.has_delete:
|
|
22
|
-
raise AdminAPIException(APIError(message=_('method_not_allowed')), status_code=500)
|
|
25
|
+
raise AdminAPIException(APIError(message=_('errors.method_not_allowed')), status_code=500)
|
|
23
26
|
|
|
24
27
|
# pylint: disable=import-outside-toplevel
|
|
25
28
|
from sqlalchemy import inspect
|
|
@@ -38,16 +41,25 @@ class SQLAlchemyAdminRetrieveMixin:
|
|
|
38
41
|
extra={"record": record, "user": user},
|
|
39
42
|
)
|
|
40
43
|
|
|
44
|
+
except FieldError as e:
|
|
45
|
+
logger.exception(
|
|
46
|
+
'SQLAlchemy %s retrieve %s #%s field error: %s',
|
|
47
|
+
type(self).__name__, self.model.__name__, pk, e,
|
|
48
|
+
)
|
|
49
|
+
msg = _('errors.serialize_field_error') % {'error': e.message}
|
|
50
|
+
raise AdminAPIException(APIError(message=msg, code='filters_exception'), status_code=500) from e
|
|
51
|
+
|
|
41
52
|
except Exception as e:
|
|
42
53
|
logger.exception(
|
|
43
|
-
'SQLAlchemy %s retrieve db error: %s',
|
|
54
|
+
'SQLAlchemy %s retrieve %s #%s db error: %s',
|
|
55
|
+
type(self).__name__, self.model.__name__, pk, e,
|
|
44
56
|
)
|
|
45
57
|
raise AdminAPIException(
|
|
46
|
-
APIError(message=_('db_error_retrieve'), code='db_error_retrieve'), status_code=500,
|
|
58
|
+
APIError(message=_('errors.db_error_retrieve'), code='db_error_retrieve'), status_code=500,
|
|
47
59
|
) from e
|
|
48
60
|
|
|
49
61
|
if record is None:
|
|
50
|
-
msg = _('record_not_found') % {'pk_name': self.pk_name, 'pk': pk}
|
|
62
|
+
msg = _('errors.record_not_found') % {'pk_name': self.pk_name, 'pk': pk}
|
|
51
63
|
raise AdminAPIException(
|
|
52
64
|
APIError(message=msg, code='record_not_found'),
|
|
53
65
|
status_code=400,
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
from typing import Any
|
|
2
2
|
|
|
3
|
-
from
|
|
4
|
-
from
|
|
5
|
-
from
|
|
6
|
-
from
|
|
7
|
-
from
|
|
3
|
+
from brilliance_admin import auth, schema
|
|
4
|
+
from brilliance_admin.exceptions import AdminAPIException, APIError
|
|
5
|
+
from brilliance_admin.translations import LanguageContext
|
|
6
|
+
from brilliance_admin.translations import TranslateText as _
|
|
7
|
+
from brilliance_admin.utils import get_logger
|
|
8
8
|
|
|
9
9
|
logger = get_logger()
|
|
10
10
|
|
|
@@ -18,10 +18,10 @@ class SQLAlchemyAdminUpdate:
|
|
|
18
18
|
pk: Any,
|
|
19
19
|
data: dict,
|
|
20
20
|
user: auth.UserABC,
|
|
21
|
-
|
|
21
|
+
language_context: LanguageContext,
|
|
22
22
|
) -> schema.UpdateResult:
|
|
23
23
|
if not self.has_update:
|
|
24
|
-
raise AdminAPIException(APIError(message=_('method_not_allowed')), status_code=500)
|
|
24
|
+
raise AdminAPIException(APIError(message=_('errors.method_not_allowed')), status_code=500)
|
|
25
25
|
|
|
26
26
|
# pylint: disable=import-outside-toplevel
|
|
27
27
|
from sqlalchemy import inspect
|
|
@@ -29,7 +29,7 @@ class SQLAlchemyAdminUpdate:
|
|
|
29
29
|
|
|
30
30
|
if pk is None:
|
|
31
31
|
raise AdminAPIException(
|
|
32
|
-
APIError(message=_('pk_not_found') % {'pk_name': self.pk_name}, code='pk_not_found'),
|
|
32
|
+
APIError(message=_('errors.pk_not_found') % {'pk_name': self.pk_name}, code='pk_not_found'),
|
|
33
33
|
status_code=400,
|
|
34
34
|
)
|
|
35
35
|
|
|
@@ -42,7 +42,7 @@ class SQLAlchemyAdminUpdate:
|
|
|
42
42
|
async with self.db_async_session() as session:
|
|
43
43
|
record = (await session.execute(stmt)).scalars().first()
|
|
44
44
|
if record is None:
|
|
45
|
-
msg = _('record_not_found') % {'pk_name': self.pk_name, 'pk': pk}
|
|
45
|
+
msg = _('errors.record_not_found') % {'pk_name': self.pk_name, 'pk': pk}
|
|
46
46
|
raise AdminAPIException(
|
|
47
47
|
APIError(message=msg, code='record_not_found'),
|
|
48
48
|
status_code=400,
|
|
@@ -59,7 +59,7 @@ class SQLAlchemyAdminUpdate:
|
|
|
59
59
|
type(self).__name__, self.table_schema.model.__name__, pk, e,
|
|
60
60
|
extra={'data': data},
|
|
61
61
|
)
|
|
62
|
-
msg = _('connection_refused_error') % {'error': str(e)}
|
|
62
|
+
msg = _('errors.connection_refused_error') % {'error': str(e)}
|
|
63
63
|
raise AdminAPIException(
|
|
64
64
|
APIError(message=msg, code='connection_refused_error'),
|
|
65
65
|
status_code=400,
|
|
@@ -84,7 +84,7 @@ class SQLAlchemyAdminUpdate:
|
|
|
84
84
|
extra={'data': data}
|
|
85
85
|
)
|
|
86
86
|
raise AdminAPIException(
|
|
87
|
-
APIError(message=_('db_error_update'), code='db_error_update'), status_code=500,
|
|
87
|
+
APIError(message=_('errors.db_error_update'), code='db_error_update'), status_code=500,
|
|
88
88
|
) from e
|
|
89
89
|
|
|
90
90
|
logger.info(
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
delete: 'Delete'
|
|
2
|
+
delete_confirmation_text: "Are you sure you want to delete those records?\nThis action cannot be undone."
|
|
3
|
+
deleted_successfully: 'The entries were successfully deleted.'
|
|
4
|
+
|
|
5
|
+
errors:
|
|
6
|
+
db_error_create: 'Error creating a record in the database.'
|
|
7
|
+
db_error_update: 'Error updating the record in the database.'
|
|
8
|
+
db_error_retrieve: 'Error retrieving the record from the database.'
|
|
9
|
+
db_error_list: 'Failed to retrieve table data from the database.'
|
|
10
|
+
pk_not_found: 'The "%(pk_name)s" field was not found in the submitted data.'
|
|
11
|
+
record_not_found: 'No record found for %(pk_name)s=%(pk)s.'
|
|
12
|
+
connection_refused_error: 'Database connection error: %(error)s'
|
|
13
|
+
filters_exception: 'An unknown technical error occurred while filtering data.'
|
|
14
|
+
method_not_allowed: 'Error, method not allowed. This action is not permitted.'
|
|
15
|
+
filter_error: 'An error occurred during filtering: {error}'
|
|
16
|
+
|
|
17
|
+
search_help: 'Available search fields: %(fields)s'
|
|
18
|
+
sqlalchemy_search_help: |
|
|
19
|
+
<b>Available search fields:</b>
|
|
20
|
+
%(fields)s
|
|
21
|
+
|
|
22
|
+
<b>Available operators:</b>
|
|
23
|
+
<b>""</b> - quotes for exact match
|
|
24
|
+
<b>%%</b> - any sequence of characters
|
|
25
|
+
<b>_</b> - any single character
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
delete: 'Удалить'
|
|
2
|
+
delete_confirmation_text: "Вы уверены, что хотите удалить данные записи?\nДанное действие нельзя отменить."
|
|
3
|
+
deleted_successfully: 'Записи успешно удалены.'
|
|
4
|
+
|
|
5
|
+
errors:
|
|
6
|
+
pk_not_found: 'Поле "%(pk_name)s" не найдено среди переданных данных.'
|
|
7
|
+
record_not_found: 'Запись по ключу %(pk_name)s=%(pk)s не найдена.'
|
|
8
|
+
db_error_create: 'Ошибка создания записи в базе данных.'
|
|
9
|
+
db_error_update: 'Ошибка обновления записи в базе данных.'
|
|
10
|
+
db_error_retrieve: 'Ошибка получения записи из базы данных.'
|
|
11
|
+
db_error_list: 'Ошибка получения данных таблицы из базы данных.'
|
|
12
|
+
connection_refused_error: 'Ошибка подключения к базе данных: %(error)s'
|
|
13
|
+
filters_exception: 'Произошла неизвестная техническая ошибка при фильтрации данных.'
|
|
14
|
+
method_not_allowed: 'Ошибка, данный метод недоступен.'
|
|
15
|
+
filter_error: 'Проишла ошибка при фильтрации: %(error)s'
|
|
16
|
+
serialize_field_error: 'Ошибка чтения данных: %(error)s'
|
|
17
|
+
|
|
18
|
+
search_help: 'Доступные поля для поиска: %(fields)s'
|
|
19
|
+
sqlalchemy_search_help: |
|
|
20
|
+
<b>Доступные поля для поиска:</b>
|
|
21
|
+
%(fields)s
|
|
22
|
+
|
|
23
|
+
<b>Доступные операторы:</b>
|
|
24
|
+
<b>""</b> - кавычки для точного совпадения
|
|
25
|
+
<b>%%</b> - любая последовательность символов
|
|
26
|
+
<b>_</b> - один любой символ
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import importlib.metadata
|
|
2
2
|
import json
|
|
3
3
|
from importlib import resources
|
|
4
|
-
from typing import Any, Dict, List, Optional
|
|
4
|
+
from typing import Any, Dict, List, Optional
|
|
5
5
|
from urllib.parse import urljoin
|
|
6
6
|
|
|
7
7
|
from fastapi import FastAPI, Request
|
|
@@ -9,11 +9,16 @@ from fastapi.middleware.cors import CORSMiddleware
|
|
|
9
9
|
from fastapi.staticfiles import StaticFiles
|
|
10
10
|
from pydantic.dataclasses import dataclass
|
|
11
11
|
|
|
12
|
-
from
|
|
13
|
-
from
|
|
14
|
-
from
|
|
15
|
-
from
|
|
16
|
-
from
|
|
12
|
+
from brilliance_admin.auth import UserABC
|
|
13
|
+
from brilliance_admin.docs import build_redoc_docs, build_scalar_docs
|
|
14
|
+
from brilliance_admin.schema.group import Group, GroupSchemaData
|
|
15
|
+
from brilliance_admin.translations import LanguageContext, LanguageManager
|
|
16
|
+
from brilliance_admin.utils import DataclassBase, SupportsStr
|
|
17
|
+
|
|
18
|
+
DEFAULT_LANGUAGES = {
|
|
19
|
+
'ru': 'Russian',
|
|
20
|
+
'en': 'English',
|
|
21
|
+
}
|
|
17
22
|
|
|
18
23
|
|
|
19
24
|
@dataclass
|
|
@@ -29,9 +34,9 @@ class AdminSchemaData(DataclassBase):
|
|
|
29
34
|
# pylint: disable=too-many-instance-attributes
|
|
30
35
|
@dataclass
|
|
31
36
|
class AdminSettingsData(DataclassBase):
|
|
32
|
-
title:
|
|
33
|
-
description:
|
|
34
|
-
login_greetings_message:
|
|
37
|
+
title: SupportsStr
|
|
38
|
+
description: SupportsStr | None
|
|
39
|
+
login_greetings_message: SupportsStr | None
|
|
35
40
|
navbar_density: str
|
|
36
41
|
languages: Dict[str, str] | None
|
|
37
42
|
|
|
@@ -39,7 +44,7 @@ class AdminSettingsData(DataclassBase):
|
|
|
39
44
|
@dataclass
|
|
40
45
|
class AdminIndexContextData(DataclassBase):
|
|
41
46
|
title: str
|
|
42
|
-
favicon_image: str
|
|
47
|
+
favicon_image: str | None
|
|
43
48
|
settings_json: str
|
|
44
49
|
|
|
45
50
|
|
|
@@ -48,30 +53,33 @@ class AdminSchema:
|
|
|
48
53
|
groups: List[Group]
|
|
49
54
|
auth: Any
|
|
50
55
|
|
|
51
|
-
title:
|
|
52
|
-
description:
|
|
53
|
-
login_greetings_message:
|
|
56
|
+
title: SupportsStr | None = 'Admin'
|
|
57
|
+
description: SupportsStr | None = None
|
|
58
|
+
login_greetings_message: SupportsStr | None = None
|
|
54
59
|
|
|
55
60
|
logo_image: str | None = None
|
|
56
|
-
favicon_image: str =
|
|
61
|
+
favicon_image: str | None = None
|
|
57
62
|
|
|
58
63
|
navbar_density: str = 'default'
|
|
59
64
|
|
|
60
65
|
backend_prefix = None
|
|
61
66
|
static_prefix = None
|
|
62
67
|
|
|
63
|
-
|
|
68
|
+
language_manager: LanguageManager | None = None
|
|
64
69
|
|
|
65
70
|
def __post_init__(self):
|
|
66
71
|
for group in self.groups:
|
|
67
72
|
if not issubclass(group.__class__, Group):
|
|
68
73
|
raise TypeError(f'Group "{group}" is not instance of Group subclass')
|
|
69
74
|
|
|
70
|
-
|
|
71
|
-
|
|
75
|
+
if not self.language_manager:
|
|
76
|
+
self.language_manager = LanguageManager(DEFAULT_LANGUAGES)
|
|
77
|
+
|
|
78
|
+
def get_language_context(self, language_slug: str | None) -> LanguageContext:
|
|
79
|
+
return LanguageContext(language_slug, language_manager=self.language_manager)
|
|
72
80
|
|
|
73
81
|
def generate_schema(self, user: UserABC, language_slug: str | None) -> AdminSchemaData:
|
|
74
|
-
|
|
82
|
+
language_context: LanguageContext = self.get_language_context(language_slug)
|
|
75
83
|
|
|
76
84
|
groups = {}
|
|
77
85
|
|
|
@@ -80,7 +88,7 @@ class AdminSchema:
|
|
|
80
88
|
msg = f'Category group {type(group).__name__}.slug is empty'
|
|
81
89
|
raise AttributeError(msg)
|
|
82
90
|
|
|
83
|
-
groups[group.slug] = group.generate_schema(user,
|
|
91
|
+
groups[group.slug] = group.generate_schema(user, language_context)
|
|
84
92
|
|
|
85
93
|
return AdminSchemaData(
|
|
86
94
|
groups=groups,
|
|
@@ -96,13 +104,13 @@ class AdminSchema:
|
|
|
96
104
|
|
|
97
105
|
async def get_settings(self, request: Request) -> AdminSettingsData:
|
|
98
106
|
language_slug = request.headers.get('Accept-Language')
|
|
99
|
-
|
|
107
|
+
language_context: LanguageContext = self.get_language_context(language_slug)
|
|
100
108
|
|
|
101
109
|
languages = None
|
|
102
|
-
if language_manager.languages:
|
|
110
|
+
if self.language_manager.languages:
|
|
103
111
|
languages = {}
|
|
104
|
-
for k, v in language_manager.languages.items():
|
|
105
|
-
languages[k] =
|
|
112
|
+
for k, v in self.language_manager.languages.items():
|
|
113
|
+
languages[k] = v
|
|
106
114
|
|
|
107
115
|
return AdminSettingsData(
|
|
108
116
|
title=self.title,
|
|
@@ -124,11 +132,11 @@ class AdminSchema:
|
|
|
124
132
|
include_redoc=False,
|
|
125
133
|
) -> FastAPI:
|
|
126
134
|
# pylint: disable=unused-variable
|
|
127
|
-
|
|
135
|
+
language_context = self.get_language_context(language_slug=None)
|
|
128
136
|
|
|
129
137
|
app = FastAPI(
|
|
130
|
-
title=
|
|
131
|
-
description=
|
|
138
|
+
title=language_context.get_text(self.title),
|
|
139
|
+
description=language_context.get_text(self.description),
|
|
132
140
|
debug=debug,
|
|
133
141
|
docs_url='/docs' if include_docs else None,
|
|
134
142
|
redoc_url=None,
|
|
@@ -143,7 +151,7 @@ class AdminSchema:
|
|
|
143
151
|
allow_headers=["*"]
|
|
144
152
|
)
|
|
145
153
|
|
|
146
|
-
static_dir = resources.files("
|
|
154
|
+
static_dir = resources.files("brilliance_admin").joinpath("static")
|
|
147
155
|
app.mount("/static", StaticFiles(directory=str(static_dir)), name="static")
|
|
148
156
|
|
|
149
157
|
app.state.schema = self
|
|
@@ -155,14 +163,14 @@ class AdminSchema:
|
|
|
155
163
|
app.include_router(build_redoc_docs(app, redoc_url='/redoc'))
|
|
156
164
|
|
|
157
165
|
# pylint: disable=import-outside-toplevel
|
|
158
|
-
from
|
|
159
|
-
app.include_router(
|
|
166
|
+
from brilliance_admin.api.routers import brilliance_admin_router
|
|
167
|
+
app.include_router(brilliance_admin_router)
|
|
160
168
|
|
|
161
169
|
return app
|
|
162
170
|
|
|
163
171
|
async def get_index_context_data(self, request: Request) -> dict:
|
|
164
|
-
|
|
165
|
-
context = {'
|
|
172
|
+
language_context = self.get_language_context(language_slug=None)
|
|
173
|
+
context = {'language_context': language_context}
|
|
166
174
|
|
|
167
175
|
backend_prefix = self.backend_prefix
|
|
168
176
|
if not backend_prefix:
|
|
@@ -5,9 +5,9 @@ from pydantic import Field
|
|
|
5
5
|
from pydantic.dataclasses import dataclass
|
|
6
6
|
from pydantic_core import core_schema
|
|
7
7
|
|
|
8
|
-
from
|
|
9
|
-
from
|
|
10
|
-
from
|
|
8
|
+
from brilliance_admin.auth import UserABC
|
|
9
|
+
from brilliance_admin.translations import LanguageContext
|
|
10
|
+
from brilliance_admin.utils import DataclassBase, SupportsStr, humanize_field_name
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
# pylint: disable=too-many-instance-attributes
|
|
@@ -30,7 +30,6 @@ class FieldSchemaData(DataclassBase):
|
|
|
30
30
|
|
|
31
31
|
choices: List[dict] | None = None
|
|
32
32
|
|
|
33
|
-
tag_colors: dict | None = None
|
|
34
33
|
variant: str | None = None
|
|
35
34
|
size: str | None = None
|
|
36
35
|
|
|
@@ -116,18 +115,18 @@ class CategorySchemaData(DataclassBase):
|
|
|
116
115
|
|
|
117
116
|
class Category(abc.ABC):
|
|
118
117
|
slug: ClassVar[str]
|
|
119
|
-
title: ClassVar[
|
|
120
|
-
description: ClassVar[
|
|
118
|
+
title: ClassVar[SupportsStr | None] = None
|
|
119
|
+
description: ClassVar[SupportsStr | None] = None
|
|
121
120
|
|
|
122
121
|
# https://pictogrammers.com/library/mdi/
|
|
123
122
|
icon: ClassVar[str | None] = None
|
|
124
123
|
|
|
125
124
|
_type_slug: ClassVar[str]
|
|
126
125
|
|
|
127
|
-
def generate_schema(self, user: UserABC,
|
|
126
|
+
def generate_schema(self, user: UserABC, language_context: LanguageContext) -> CategorySchemaData:
|
|
128
127
|
return CategorySchemaData(
|
|
129
|
-
title=
|
|
130
|
-
description=
|
|
128
|
+
title=language_context.get_text(self.title) or humanize_field_name(self.slug),
|
|
129
|
+
description=language_context.get_text(self.description),
|
|
131
130
|
icon=self.icon,
|
|
132
131
|
type=self._type_slug,
|
|
133
132
|
)
|
|
@@ -2,10 +2,11 @@ from typing import Any, Dict, List
|
|
|
2
2
|
|
|
3
3
|
from pydantic import BaseModel, Field
|
|
4
4
|
|
|
5
|
-
from
|
|
6
|
-
from
|
|
7
|
-
from
|
|
8
|
-
from
|
|
5
|
+
from brilliance_admin.schema import Category
|
|
6
|
+
from brilliance_admin.schema.category import GraphInfoSchemaData
|
|
7
|
+
from brilliance_admin.schema.table.fields_schema import FieldsSchema
|
|
8
|
+
from brilliance_admin.translations import LanguageContext
|
|
9
|
+
from brilliance_admin.utils import SupportsStr
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
class GraphData(BaseModel):
|
|
@@ -29,19 +30,19 @@ class CategoryGraphs(Category):
|
|
|
29
30
|
_type_slug: str = 'graphs'
|
|
30
31
|
|
|
31
32
|
search_enabled: bool = False
|
|
32
|
-
search_help:
|
|
33
|
+
search_help: SupportsStr | None = None
|
|
33
34
|
|
|
34
35
|
table_filters: FieldsSchema | None = None
|
|
35
36
|
|
|
36
|
-
def generate_schema(self, user,
|
|
37
|
-
schema = super().generate_schema(user,
|
|
37
|
+
def generate_schema(self, user, language_context: LanguageContext) -> GraphInfoSchemaData:
|
|
38
|
+
schema = super().generate_schema(user, language_context)
|
|
38
39
|
graph = GraphInfoSchemaData(
|
|
39
40
|
search_enabled=self.search_enabled,
|
|
40
|
-
search_help=
|
|
41
|
+
search_help=language_context.get_text(self.search_help),
|
|
41
42
|
)
|
|
42
43
|
|
|
43
44
|
if self.table_filters:
|
|
44
|
-
graph.table_filters = self.table_filters.generate_schema(user,
|
|
45
|
+
graph.table_filters = self.table_filters.generate_schema(user, language_context)
|
|
45
46
|
|
|
46
47
|
schema.graph_info = graph
|
|
47
48
|
return schema
|
|
@@ -3,10 +3,10 @@ from typing import Dict, List
|
|
|
3
3
|
|
|
4
4
|
from pydantic.dataclasses import dataclass
|
|
5
5
|
|
|
6
|
-
from
|
|
7
|
-
from
|
|
8
|
-
from
|
|
9
|
-
from
|
|
6
|
+
from brilliance_admin.auth import UserABC
|
|
7
|
+
from brilliance_admin.schema.category import Category, CategorySchemaData
|
|
8
|
+
from brilliance_admin.translations import LanguageContext
|
|
9
|
+
from brilliance_admin.utils import DataclassBase, SupportsStr, get_logger, humanize_field_name
|
|
10
10
|
|
|
11
11
|
logger = get_logger()
|
|
12
12
|
|
|
@@ -23,8 +23,8 @@ class GroupSchemaData(DataclassBase):
|
|
|
23
23
|
class Group(abc.ABC):
|
|
24
24
|
categories: List[Category]
|
|
25
25
|
slug: str
|
|
26
|
-
title:
|
|
27
|
-
description:
|
|
26
|
+
title: SupportsStr | None = None
|
|
27
|
+
description: SupportsStr | None = None
|
|
28
28
|
|
|
29
29
|
# https://pictogrammers.com/library/mdi/
|
|
30
30
|
icon: str | None = None
|
|
@@ -34,10 +34,10 @@ class Group(abc.ABC):
|
|
|
34
34
|
if not issubclass(category.__class__, Category):
|
|
35
35
|
raise TypeError(f'Category "{category}" is not instance of Category subclass')
|
|
36
36
|
|
|
37
|
-
def generate_schema(self, user: UserABC,
|
|
37
|
+
def generate_schema(self, user: UserABC, language_context: LanguageContext) -> GroupSchemaData:
|
|
38
38
|
result = GroupSchemaData(
|
|
39
|
-
title=
|
|
40
|
-
description=
|
|
39
|
+
title=language_context.get_text(self.title) or humanize_field_name(self.slug),
|
|
40
|
+
description=language_context.get_text(self.description),
|
|
41
41
|
icon=self.icon,
|
|
42
42
|
categories={},
|
|
43
43
|
)
|
|
@@ -55,7 +55,7 @@ class Group(abc.ABC):
|
|
|
55
55
|
msg = f'Category {type(category).__name__}.slug "{self.slug}" already registered by "{exists.title}"'
|
|
56
56
|
raise KeyError(msg)
|
|
57
57
|
|
|
58
|
-
result.categories[category.slug] = category.generate_schema(user,
|
|
58
|
+
result.categories[category.slug] = category.generate_schema(user, language_context)
|
|
59
59
|
|
|
60
60
|
return result
|
|
61
61
|
|
|
@@ -4,8 +4,9 @@ from typing import Any, Dict, List, Optional
|
|
|
4
4
|
from pydantic import BaseModel, Field, validate_call
|
|
5
5
|
from pydantic.dataclasses import dataclass
|
|
6
6
|
|
|
7
|
-
from
|
|
8
|
-
from
|
|
7
|
+
from brilliance_admin.schema.table.fields_schema import FieldsSchema
|
|
8
|
+
from brilliance_admin.translations import DataclassBase
|
|
9
|
+
from brilliance_admin.utils import SupportsStr
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
class ActionData(BaseModel):
|
|
@@ -20,7 +21,7 @@ class ActionData(BaseModel):
|
|
|
20
21
|
|
|
21
22
|
@dataclass
|
|
22
23
|
class ActionMessage(DataclassBase):
|
|
23
|
-
text:
|
|
24
|
+
text: SupportsStr
|
|
24
25
|
type: str = 'success'
|
|
25
26
|
position: str = 'top-center'
|
|
26
27
|
|
|
@@ -28,16 +29,16 @@ class ActionMessage(DataclassBase):
|
|
|
28
29
|
@dataclass
|
|
29
30
|
class ActionResult(DataclassBase):
|
|
30
31
|
message: ActionMessage | None = None
|
|
31
|
-
persistent_message:
|
|
32
|
+
persistent_message: SupportsStr | None = None
|
|
32
33
|
|
|
33
34
|
|
|
34
35
|
# pylint: disable=too-many-arguments
|
|
35
36
|
# pylint: disable=too-many-positional-arguments
|
|
36
37
|
@validate_call
|
|
37
38
|
def admin_action(
|
|
38
|
-
title:
|
|
39
|
-
description: Optional[
|
|
40
|
-
confirmation_text: Optional[
|
|
39
|
+
title: SupportsStr,
|
|
40
|
+
description: Optional[SupportsStr] = None,
|
|
41
|
+
confirmation_text: Optional[SupportsStr] = None,
|
|
41
42
|
|
|
42
43
|
# https://vuetifyjs.com/en/styles/colors/#material-colors
|
|
43
44
|
base_color: Optional[str] = None,
|