brilliance-admin 0.42.0__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 (73) hide show
  1. admin_panel/__init__.py +4 -0
  2. admin_panel/api/__init__.py +0 -0
  3. admin_panel/api/routers.py +18 -0
  4. admin_panel/api/utils.py +28 -0
  5. admin_panel/api/views/__init__.py +0 -0
  6. admin_panel/api/views/auth.py +29 -0
  7. admin_panel/api/views/autocomplete.py +33 -0
  8. admin_panel/api/views/graphs.py +30 -0
  9. admin_panel/api/views/index.py +38 -0
  10. admin_panel/api/views/schema.py +29 -0
  11. admin_panel/api/views/settings.py +29 -0
  12. admin_panel/api/views/table.py +136 -0
  13. admin_panel/auth.py +32 -0
  14. admin_panel/docs.py +37 -0
  15. admin_panel/exceptions.py +38 -0
  16. admin_panel/integrations/__init__.py +0 -0
  17. admin_panel/integrations/sqlalchemy/__init__.py +6 -0
  18. admin_panel/integrations/sqlalchemy/auth.py +144 -0
  19. admin_panel/integrations/sqlalchemy/autocomplete.py +38 -0
  20. admin_panel/integrations/sqlalchemy/fields.py +254 -0
  21. admin_panel/integrations/sqlalchemy/fields_schema.py +316 -0
  22. admin_panel/integrations/sqlalchemy/table/__init__.py +19 -0
  23. admin_panel/integrations/sqlalchemy/table/base.py +141 -0
  24. admin_panel/integrations/sqlalchemy/table/create.py +73 -0
  25. admin_panel/integrations/sqlalchemy/table/delete.py +18 -0
  26. admin_panel/integrations/sqlalchemy/table/list.py +178 -0
  27. admin_panel/integrations/sqlalchemy/table/retrieve.py +61 -0
  28. admin_panel/integrations/sqlalchemy/table/update.py +95 -0
  29. admin_panel/schema/__init__.py +7 -0
  30. admin_panel/schema/admin_schema.py +191 -0
  31. admin_panel/schema/category.py +149 -0
  32. admin_panel/schema/graphs/__init__.py +1 -0
  33. admin_panel/schema/graphs/category_graphs.py +50 -0
  34. admin_panel/schema/group.py +67 -0
  35. admin_panel/schema/table/__init__.py +8 -0
  36. admin_panel/schema/table/admin_action.py +76 -0
  37. admin_panel/schema/table/category_table.py +175 -0
  38. admin_panel/schema/table/fields/__init__.py +5 -0
  39. admin_panel/schema/table/fields/base.py +249 -0
  40. admin_panel/schema/table/fields/function_field.py +65 -0
  41. admin_panel/schema/table/fields_schema.py +216 -0
  42. admin_panel/schema/table/table_models.py +53 -0
  43. admin_panel/static/favicon.jpg +0 -0
  44. admin_panel/static/index-BeniOHDv.js +525 -0
  45. admin_panel/static/index-vlBToOhT.css +8 -0
  46. admin_panel/static/materialdesignicons-webfont-CYDMK1kx.woff2 +0 -0
  47. admin_panel/static/materialdesignicons-webfont-CgCzGbLl.woff +0 -0
  48. admin_panel/static/materialdesignicons-webfont-D3kAzl71.ttf +0 -0
  49. admin_panel/static/materialdesignicons-webfont-DttUABo4.eot +0 -0
  50. admin_panel/static/tinymce/dark-first/content.min.css +250 -0
  51. admin_panel/static/tinymce/dark-first/skin.min.css +2820 -0
  52. admin_panel/static/tinymce/dark-slim/content.min.css +249 -0
  53. admin_panel/static/tinymce/dark-slim/skin.min.css +2821 -0
  54. admin_panel/static/tinymce/img/example.png +0 -0
  55. admin_panel/static/tinymce/img/tinymce.woff2 +0 -0
  56. admin_panel/static/tinymce/lightgray/content.min.css +1 -0
  57. admin_panel/static/tinymce/lightgray/fonts/tinymce.woff +0 -0
  58. admin_panel/static/tinymce/lightgray/skin.min.css +1 -0
  59. admin_panel/static/tinymce/plugins/accordion/css/accordion.css +17 -0
  60. admin_panel/static/tinymce/plugins/accordion/plugin.js +48 -0
  61. admin_panel/static/tinymce/plugins/codesample/css/prism.css +1 -0
  62. admin_panel/static/tinymce/plugins/customLink/css/link.css +3 -0
  63. admin_panel/static/tinymce/plugins/customLink/plugin.js +147 -0
  64. admin_panel/static/tinymce/tinymce.min.js +2 -0
  65. admin_panel/static/vanilla-picker-B6E6ObS_.js +8 -0
  66. admin_panel/templates/index.html +25 -0
  67. admin_panel/translations.py +145 -0
  68. admin_panel/utils.py +50 -0
  69. brilliance_admin-0.42.0.dist-info/METADATA +155 -0
  70. brilliance_admin-0.42.0.dist-info/RECORD +73 -0
  71. brilliance_admin-0.42.0.dist-info/WHEEL +5 -0
  72. brilliance_admin-0.42.0.dist-info/licenses/LICENSE +17 -0
  73. brilliance_admin-0.42.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,141 @@
1
+ from typing import Any
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 _
7
+
8
+ EXCEPTION_REL_NAME = '''
9
+ Model "{model_name}" doesn\'t contain rel_name:"{rel_name}" for field "{slug}"
10
+ Model fields = {model_attrs}
11
+ '''
12
+
13
+
14
+ class SQLAlchemyAdminBase(SQLAlchemyAdminAutocompleteMixin, CategoryTable):
15
+ model: Any
16
+ slug = None
17
+ ordering_fields = []
18
+
19
+ search_fields = []
20
+
21
+ table_schema: SQLAlchemyFieldsSchema
22
+
23
+ db_async_session: Any = None
24
+
25
+ def __init__(
26
+ self,
27
+ *args,
28
+ model=None,
29
+ table_schema=None,
30
+ db_async_session=None,
31
+ ordering_fields=None,
32
+ default_ordering=None,
33
+ search_fields=None,
34
+ **kwargs,
35
+ ):
36
+ if model:
37
+ self.model = model
38
+
39
+ if search_fields:
40
+ self.search_fields = search_fields
41
+
42
+ if self.search_fields:
43
+ self.search_enabled = True
44
+ self.search_help = _('sqlalchemy_search_help') % {'fields': ', '.join(self.search_fields)}
45
+
46
+ if default_ordering:
47
+ self.default_ordering = default_ordering
48
+
49
+ if ordering_fields:
50
+ self.ordering_fields = ordering_fields
51
+
52
+ self.validate_fields()
53
+
54
+ if table_schema:
55
+ self.table_schema = table_schema
56
+
57
+ if not self.table_schema:
58
+ self.table_schema = SQLAlchemyFieldsSchema(model=self.model)
59
+
60
+ if not issubclass(type(self.table_schema), SQLAlchemyFieldsSchema):
61
+ msg = f'{type(self).__name__}.table_schema {self.table_schema} must be subclass of SQLAlchemyFieldsSchema'
62
+ raise AttributeError(msg)
63
+
64
+ if not self.model:
65
+ msg = f'{type(self).__name__}.model is required for SQLAlchemy'
66
+ raise AttributeError(msg)
67
+
68
+ if not self.slug:
69
+ self.slug = self.model.__name__.lower()
70
+
71
+ if db_async_session:
72
+ self.db_async_session = db_async_session
73
+
74
+ if not self.db_async_session:
75
+ msg = f'{type(self).__name__}.db_async_session is required for SQLAlchemy'
76
+ raise AttributeError(msg)
77
+
78
+ # pylint: disable=import-outside-toplevel
79
+ from sqlalchemy import inspect
80
+ from sqlalchemy.sql.schema import Column
81
+
82
+ for attr in inspect(self.model).mapper.column_attrs:
83
+ col: Column = attr.columns[0]
84
+ if col.primary_key and not self.pk_name:
85
+ self.pk_name = attr.key
86
+ break
87
+
88
+ if not self.default_ordering and self.pk_name:
89
+ self.default_ordering = f'-{self.pk_name}'
90
+
91
+ super().__init__(*args, **kwargs)
92
+
93
+ def validate_fields(self):
94
+ # pylint: disable=import-outside-toplevel
95
+ from sqlalchemy.orm import InstrumentedAttribute
96
+
97
+ if self.search_fields:
98
+ for field in self.search_fields:
99
+ column = getattr(self.model, field, None)
100
+ if not isinstance(column, InstrumentedAttribute):
101
+ raise AttributeError(
102
+ f'{type(self).__name__}: search field "{field}" not found in model {self.model.__name__}'
103
+ )
104
+
105
+ if self.ordering_fields:
106
+ for field in self.ordering_fields:
107
+ column = getattr(self.model, field, None)
108
+ if not isinstance(column, InstrumentedAttribute):
109
+ raise AttributeError(
110
+ f'{type(self).__name__}: ordering field "{field}" not found in model {self.model.__name__}'
111
+ )
112
+
113
+ def get_queryset(self):
114
+ # pylint: disable=import-outside-toplevel
115
+ from sqlalchemy import select
116
+ from sqlalchemy.orm import selectinload
117
+
118
+ stmt = select(self.model).options(selectinload('*'))
119
+
120
+ # Eager-load related fields
121
+ for slug, field in self.table_schema.get_fields().items():
122
+
123
+ # pylint: disable=protected-access
124
+ if field._type == "related":
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
+ if not hasattr(self.model, field.rel_name):
131
+ msg = EXCEPTION_REL_NAME.format(
132
+ slug=slug,
133
+ model_name=self.model.__name__,
134
+ rel_name=field.rel_name,
135
+ model_attrs=model_attrs,
136
+ )
137
+ raise AttributeError(msg)
138
+
139
+ stmt = stmt.options(selectinload(getattr(self.model, field.rel_name)))
140
+
141
+ return stmt
@@ -0,0 +1,73 @@
1
+ from admin_panel import schema
2
+ from admin_panel.auth import UserABC
3
+ from admin_panel.exceptions import AdminAPIException, APIError
4
+ from admin_panel.translations import LanguageManager
5
+ from admin_panel.translations import TranslateText as _
6
+ from admin_panel.utils import get_logger
7
+
8
+ logger = get_logger()
9
+
10
+
11
+ class SQLAlchemyAdminCreate:
12
+ has_create: bool = True
13
+
14
+ async def create(
15
+ self,
16
+ data: dict,
17
+ user: UserABC,
18
+ language_manager: LanguageManager,
19
+ ) -> schema.CreateResult:
20
+ if not self.has_create:
21
+ raise AdminAPIException(APIError(message=_('method_not_allowed')), status_code=500)
22
+
23
+ # pylint: disable=import-outside-toplevel
24
+ from sqlalchemy.exc import IntegrityError
25
+
26
+ try:
27
+ async with self.db_async_session() as session:
28
+ record = await self.table_schema.create(user, data, session)
29
+ pk_value = getattr(record, self.pk_name, None)
30
+
31
+ except AdminAPIException as e:
32
+ raise e
33
+
34
+ except ConnectionRefusedError as e:
35
+ logger.exception(
36
+ 'SQLAlchemy %s create %s db error: %s',
37
+ type(self).__name__, self.table_schema.model.__name__, e,
38
+ extra={'data': data},
39
+ )
40
+ msg = _('connection_refused_error') % {'error': str(e)}
41
+ raise AdminAPIException(
42
+ APIError(message=msg, code='connection_refused_error'),
43
+ status_code=500,
44
+ ) from e
45
+
46
+ except IntegrityError as e:
47
+ logger.warning(
48
+ 'SQLAlchemy %s create %s db error: %s',
49
+ type(self).__name__, self.table_schema.model.__name__, e,
50
+ extra={'data': data},
51
+ )
52
+ orig = e.orig
53
+ message = orig.args[0] if orig.args else type(orig).__name__
54
+ raise AdminAPIException(
55
+ APIError(message=message, code='db_integrity_error'), status_code=500,
56
+ ) from e
57
+
58
+ except Exception as e:
59
+ logger.exception(
60
+ 'SQLAlchemy %s create %s db error: %s',
61
+ type(self).__name__, self.table_schema.model.__name__, e,
62
+ extra={'data': data},
63
+ )
64
+ raise AdminAPIException(
65
+ APIError(message=_('db_error_create'), code='db_error_create'), status_code=500,
66
+ ) from e
67
+
68
+ logger.info(
69
+ '%s model %s #%s created by %s',
70
+ type(self).__name__, self.table_schema.model.__name__, pk_value, user.username,
71
+ extra={'data': data},
72
+ )
73
+ return schema.CreateResult(pk=pk_value)
@@ -0,0 +1,18 @@
1
+ from admin_panel.exceptions import APIError, AdminAPIException
2
+ from admin_panel.schema.table.admin_action import ActionData, ActionMessage, ActionResult, admin_action
3
+ from admin_panel.translations import TranslateText as _
4
+
5
+
6
+ class SQLAlchemyDeleteAction:
7
+ has_delete: bool = True
8
+
9
+ @admin_action(
10
+ title=_('delete'),
11
+ confirmation_text=_('delete_confirmation_text'),
12
+ base_color='red-lighten-2',
13
+ variant='outlined',
14
+ )
15
+ async def delete(self, action_data: ActionData):
16
+ if not self.has_delete:
17
+ raise AdminAPIException(APIError(message=_('method_not_allowed')), status_code=500)
18
+ return ActionResult(message=ActionMessage(_('deleted_successfully')))
@@ -0,0 +1,178 @@
1
+ from admin_panel import auth, schema
2
+ from admin_panel.exceptions import AdminAPIException, APIError, FieldError
3
+ from admin_panel.integrations.sqlalchemy.fields_schema import SQLAlchemyFieldsSchema
4
+ from admin_panel.translations import LanguageManager
5
+ from admin_panel.translations import TranslateText as _
6
+ from admin_panel.utils import get_logger
7
+
8
+ logger = get_logger()
9
+
10
+
11
+ class SQLAlchemyAdminListMixin:
12
+ table_schema: SQLAlchemyFieldsSchema
13
+ table_filters: SQLAlchemyFieldsSchema | None
14
+
15
+ def apply_ordering(self, stmt, list_data):
16
+ # pylint: disable=import-outside-toplevel
17
+ from sqlalchemy import asc, desc
18
+ from sqlalchemy.orm import InstrumentedAttribute
19
+
20
+ ordering = list_data.ordering or self.default_ordering
21
+
22
+ if not ordering:
23
+ return stmt
24
+
25
+ direction = asc
26
+
27
+ if ordering.startswith("-"):
28
+ ordering = ordering[1:]
29
+ direction = desc
30
+
31
+ if list_data.ordering and ordering not in self.ordering_fields:
32
+ msg = f'Ordering "{ordering}" is not allowed; available options: {self.ordering_fields} default_ordering: {self.default_ordering}'
33
+ raise FieldError(message=msg)
34
+
35
+ column = getattr(self.model, ordering, None)
36
+ if not isinstance(column, InstrumentedAttribute):
37
+ msg = f'{type(self).__name__} ordering field "{ordering}" not found in model {self.model}'
38
+ raise FieldError(message=msg)
39
+
40
+ return stmt.order_by(direction(column))
41
+
42
+ def apply_search(self, stmt, list_data: schema.ListData):
43
+ # pylint: disable=import-outside-toplevel
44
+ from sqlalchemy import String, cast, or_
45
+ from sqlalchemy.orm import InstrumentedAttribute
46
+
47
+ if not self.search_fields or not list_data.search:
48
+ return stmt
49
+
50
+ search = list_data.search
51
+ conditions = []
52
+
53
+ for field_slug in self.search_fields:
54
+ column = getattr(self.model, field_slug, None)
55
+ if not isinstance(column, InstrumentedAttribute):
56
+ msg = f'{type(self).__name__} filter "{field_slug}" not found as field inside model {self.model}'
57
+ raise AttributeError(msg)
58
+
59
+ conditions.append(cast(column, String).ilike(search))
60
+
61
+ if conditions:
62
+ stmt = stmt.where(or_(*conditions))
63
+
64
+ return stmt
65
+
66
+ async def apply_filters(self, stmt, list_data: schema.ListData):
67
+ if not self.table_filters or not list_data.filters:
68
+ return stmt
69
+
70
+ if not issubclass(type(self.table_filters), SQLAlchemyFieldsSchema):
71
+ msg = f'{type(self).__name__}.table_filters {type(self.table_filters)} must be SQLAlchemyFieldsSchema subclass'
72
+ raise AttributeError(msg)
73
+
74
+ return await self.table_filters.apply_filters(stmt, list_data.filters)
75
+
76
+ def apply_pagination(self, stmt, list_data: schema.ListData):
77
+ page = max(1, list_data.page or 1)
78
+ limit = min(150, max(1, list_data.limit or 25))
79
+
80
+ offset = (page - 1) * limit
81
+
82
+ return stmt.limit(limit).offset(offset)
83
+
84
+ # pylint: disable=too-many-arguments
85
+ # pylint: disable=too-many-locals
86
+ async def get_list(
87
+ self,
88
+ list_data: schema.ListData,
89
+ user: auth.UserABC,
90
+ language_manager: LanguageManager,
91
+ ) -> schema.TableListResult:
92
+ # pylint: disable=import-outside-toplevel
93
+ from sqlalchemy import exc, func, select
94
+
95
+ try:
96
+ stmt = self.get_queryset()
97
+ stmt = await self.apply_filters(stmt, list_data)
98
+ stmt = self.apply_search(stmt, list_data)
99
+
100
+ count_stmt = select(func.count()).select_from(stmt.subquery())
101
+ stmt = self.apply_pagination(stmt, list_data)
102
+ stmt = self.apply_ordering(stmt, list_data)
103
+
104
+ except FieldError as e:
105
+ logger.exception(
106
+ 'SQLAlchemy %s list filters for %s field error: %s',
107
+ type(self).__name__, self.model.__name__, e,
108
+ extra={
109
+ 'list_data': list_data,
110
+ }
111
+ )
112
+ msg = _('filter_error') % {'error': e.message}
113
+ raise AdminAPIException(APIError(message=msg, code='filters_exception'), status_code=500) from e
114
+
115
+ except Exception as e:
116
+ logger.exception(
117
+ 'SQLAlchemy %s list filters for %s error: %s',
118
+ type(self).__name__, self.model.__name__, e,
119
+ extra={
120
+ 'list_data': list_data,
121
+ }
122
+ )
123
+ raise AdminAPIException(APIError(message=_('filters_exception'), code='filters_exception'), status_code=500) from e
124
+
125
+ data = []
126
+
127
+ try:
128
+ async with self.db_async_session() as session:
129
+ total_count = await session.scalar(count_stmt)
130
+ records = (await session.execute(stmt)).scalars().all()
131
+ for record in records:
132
+ line = await self.table_schema.serialize(
133
+ record,
134
+ extra={"record": record, "user": user},
135
+ )
136
+ data.append(line)
137
+
138
+ except ConnectionRefusedError as e:
139
+ logger.exception(
140
+ 'SQLAlchemy %s get_list db error: %s',
141
+ type(self).__name__, e,
142
+ extra={
143
+ 'list_data': list_data,
144
+ }
145
+ )
146
+ msg = _('connection_refused_error') % {'error': str(e)}
147
+ raise AdminAPIException(
148
+ APIError(message=msg, code='connection_refused_error'),
149
+ status_code=500,
150
+ ) from e
151
+
152
+ except (exc.IntegrityError, exc.StatementError) as e:
153
+ logger.exception(
154
+ 'SQLAlchemy %s get_list db error: %s',
155
+ type(self).__name__, e,
156
+ extra={
157
+ 'list_data': list_data,
158
+ }
159
+ )
160
+ orig = e.orig
161
+ message = orig.args[0] if orig.args else type(orig).__name__
162
+ raise AdminAPIException(
163
+ APIError(message=message, code='db_exception'), status_code=500,
164
+ ) from e
165
+
166
+ except Exception as e:
167
+ logger.exception(
168
+ 'SQLAlchemy %s get_list db error: %s',
169
+ type(self).__name__, e,
170
+ extra={
171
+ 'list_data': list_data,
172
+ }
173
+ )
174
+ raise AdminAPIException(
175
+ APIError(message=_('db_error_list'), code='db_error_list'), status_code=500,
176
+ ) from e
177
+
178
+ return schema.TableListResult(data=data, total_count=int(total_count or 0))
@@ -0,0 +1,61 @@
1
+ from typing import Any
2
+
3
+ from admin_panel import auth, schema
4
+ from admin_panel.exceptions import AdminAPIException, APIError
5
+ from admin_panel.translations import LanguageManager
6
+ from admin_panel.translations import TranslateText as _
7
+ from admin_panel.utils import get_logger
8
+
9
+ logger = get_logger()
10
+
11
+
12
+ class SQLAlchemyAdminRetrieveMixin:
13
+ has_retrieve: bool = True
14
+
15
+ async def retrieve(
16
+ self,
17
+ pk: Any,
18
+ user: auth.UserABC,
19
+ language_manager: LanguageManager,
20
+ ) -> schema.RetrieveResult:
21
+ if not self.has_delete:
22
+ raise AdminAPIException(APIError(message=_('method_not_allowed')), status_code=500)
23
+
24
+ # pylint: disable=import-outside-toplevel
25
+ from sqlalchemy import inspect
26
+
27
+ col = inspect(self.model).mapper.columns[self.pk_name]
28
+ python_type = col.type.python_type
29
+
30
+ assert self.pk_name
31
+ stmt = self.get_queryset().where(getattr(self.model, self.pk_name) == python_type(pk))
32
+
33
+ try:
34
+ async with self.db_async_session() as session:
35
+ record = (await session.execute(stmt)).scalars().first()
36
+ data = await self.table_schema.serialize(
37
+ record,
38
+ extra={"record": record, "user": user},
39
+ )
40
+
41
+ except Exception as e:
42
+ logger.exception(
43
+ 'SQLAlchemy %s retrieve db error: %s', type(self).__name__, e,
44
+ )
45
+ raise AdminAPIException(
46
+ APIError(message=_('db_error_retrieve'), code='db_error_retrieve'), status_code=500,
47
+ ) from e
48
+
49
+ if record is None:
50
+ msg = _('record_not_found') % {'pk_name': self.pk_name, 'pk': pk}
51
+ raise AdminAPIException(
52
+ APIError(message=msg, code='record_not_found'),
53
+ status_code=400,
54
+ )
55
+
56
+ logger.debug(
57
+ '%s model %s #%s retrieved by %s',
58
+ type(self).__name__, self.table_schema.model.__name__, pk, user.username,
59
+ extra={'data': data},
60
+ )
61
+ return schema.RetrieveResult(data=data)
@@ -0,0 +1,95 @@
1
+ from typing import Any
2
+
3
+ from admin_panel import auth, schema
4
+ from admin_panel.exceptions import AdminAPIException, APIError
5
+ from admin_panel.translations import LanguageManager
6
+ from admin_panel.translations import TranslateText as _
7
+ from admin_panel.utils import get_logger
8
+
9
+ logger = get_logger()
10
+
11
+
12
+ class SQLAlchemyAdminUpdate:
13
+ has_update: bool = True
14
+
15
+ # pylint: disable=too-many-locals
16
+ async def update(
17
+ self,
18
+ pk: Any,
19
+ data: dict,
20
+ user: auth.UserABC,
21
+ language_manager: LanguageManager,
22
+ ) -> schema.UpdateResult:
23
+ if not self.has_update:
24
+ raise AdminAPIException(APIError(message=_('method_not_allowed')), status_code=500)
25
+
26
+ # pylint: disable=import-outside-toplevel
27
+ from sqlalchemy import inspect
28
+ from sqlalchemy.exc import IntegrityError
29
+
30
+ if pk is None:
31
+ raise AdminAPIException(
32
+ APIError(message=_('pk_not_found') % {'pk_name': self.pk_name}, code='pk_not_found'),
33
+ status_code=400,
34
+ )
35
+
36
+ col = inspect(self.table_schema.model).mapper.columns[self.pk_name]
37
+ python_type = col.type.python_type
38
+
39
+ stmt = self.get_queryset().where(getattr(self.model, self.pk_name) == python_type(pk))
40
+
41
+ try:
42
+ async with self.db_async_session() as session:
43
+ record = (await session.execute(stmt)).scalars().first()
44
+ if record is None:
45
+ msg = _('record_not_found') % {'pk_name': self.pk_name, 'pk': pk}
46
+ raise AdminAPIException(
47
+ APIError(message=msg, code='record_not_found'),
48
+ status_code=400,
49
+ )
50
+
51
+ await self.table_schema.update(record, user, data, session)
52
+
53
+ except AdminAPIException as e:
54
+ raise e
55
+
56
+ except ConnectionRefusedError as e:
57
+ logger.exception(
58
+ 'SQLAlchemy %s update %s #%s db connection error: %s',
59
+ type(self).__name__, self.table_schema.model.__name__, pk, e,
60
+ extra={'data': data},
61
+ )
62
+ msg = _('connection_refused_error') % {'error': str(e)}
63
+ raise AdminAPIException(
64
+ APIError(message=msg, code='connection_refused_error'),
65
+ status_code=400,
66
+ ) from e
67
+
68
+ except IntegrityError as e:
69
+ logger.warning(
70
+ 'SQLAlchemy %s update %s #%s db error: %s',
71
+ type(self).__name__, self.table_schema.model.__name__, pk, e,
72
+ extra={'data': data},
73
+ )
74
+ orig = e.orig
75
+ message = orig.args[0] if orig.args else type(orig).__name__
76
+ raise AdminAPIException(
77
+ APIError(message=message, code='db_integrity_error'), status_code=500,
78
+ ) from e
79
+
80
+ except Exception as e:
81
+ logger.exception(
82
+ 'SQLAlchemy %s update %s #%s db error: %s',
83
+ type(self).__name__, self.table_schema.model.__name__, pk, e,
84
+ extra={'data': data}
85
+ )
86
+ raise AdminAPIException(
87
+ APIError(message=_('db_error_update'), code='db_error_update'), status_code=500,
88
+ ) from e
89
+
90
+ logger.info(
91
+ '%s model %s #%s updated by %s',
92
+ type(self).__name__, self.table_schema.model.__name__, pk, user.username,
93
+ extra={'data': data},
94
+ )
95
+ return schema.UpdateResult(pk=pk)
@@ -0,0 +1,7 @@
1
+ # pylint: disable=wildcard-import, unused-wildcard-import, unused-import
2
+ # flake8: noqa: F405
3
+ from .admin_schema import AdminSchema, AdminSchemaData
4
+ from .category import Category
5
+ from .graphs import *
6
+ from .group import Group
7
+ from .table import *