brilliance-admin 0.43.1__py3-none-any.whl → 0.44.12__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 (41) hide show
  1. brilliance_admin/api/routers.py +2 -2
  2. brilliance_admin/api/views/autocomplete.py +1 -1
  3. brilliance_admin/api/views/{graphs.py → dashboard.py} +5 -5
  4. brilliance_admin/api/views/table.py +7 -6
  5. brilliance_admin/exceptions.py +1 -0
  6. brilliance_admin/integrations/sqlalchemy/__init__.py +1 -0
  7. brilliance_admin/integrations/sqlalchemy/auth.py +3 -4
  8. brilliance_admin/integrations/sqlalchemy/autocomplete.py +12 -3
  9. brilliance_admin/integrations/sqlalchemy/fields.py +36 -19
  10. brilliance_admin/integrations/sqlalchemy/fields_schema.py +21 -12
  11. brilliance_admin/integrations/sqlalchemy/table/base.py +4 -4
  12. brilliance_admin/integrations/sqlalchemy/table/create.py +6 -3
  13. brilliance_admin/integrations/sqlalchemy/table/delete.py +2 -2
  14. brilliance_admin/integrations/sqlalchemy/table/list.py +44 -14
  15. brilliance_admin/integrations/sqlalchemy/table/retrieve.py +41 -10
  16. brilliance_admin/integrations/sqlalchemy/table/update.py +10 -5
  17. brilliance_admin/locales/en.yml +20 -10
  18. brilliance_admin/locales/ru.yml +20 -10
  19. brilliance_admin/schema/__init__.py +3 -3
  20. brilliance_admin/schema/admin_schema.py +31 -23
  21. brilliance_admin/schema/category.py +88 -14
  22. brilliance_admin/schema/dashboard/__init__.py +1 -0
  23. brilliance_admin/schema/dashboard/category_dashboard.py +87 -0
  24. brilliance_admin/schema/table/category_table.py +13 -8
  25. brilliance_admin/schema/table/fields/base.py +102 -19
  26. brilliance_admin/schema/table/fields_schema.py +9 -2
  27. brilliance_admin/static/{index-D9axz5zK.js → index-8ahvKI6W.js} +190 -190
  28. brilliance_admin/static/{index-vlBToOhT.css → index-B8JOx1Ps.css} +1 -1
  29. brilliance_admin/templates/index.html +2 -2
  30. brilliance_admin/translations.py +6 -3
  31. brilliance_admin/utils.py +41 -3
  32. brilliance_admin-0.44.12.dist-info/METADATA +165 -0
  33. {brilliance_admin-0.43.1.dist-info → brilliance_admin-0.44.12.dist-info}/RECORD +36 -37
  34. {brilliance_admin-0.43.1.dist-info → brilliance_admin-0.44.12.dist-info}/WHEEL +1 -1
  35. brilliance_admin-0.44.12.dist-info/licenses/LICENSE +21 -0
  36. brilliance_admin/schema/graphs/__init__.py +0 -1
  37. brilliance_admin/schema/graphs/category_graphs.py +0 -51
  38. brilliance_admin/schema/group.py +0 -67
  39. brilliance_admin-0.43.1.dist-info/METADATA +0 -214
  40. brilliance_admin-0.43.1.dist-info/licenses/LICENSE +0 -17
  41. {brilliance_admin-0.43.1.dist-info → brilliance_admin-0.44.12.dist-info}/top_level.txt +0 -0
@@ -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,9 +20,10 @@ 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
- raise AdminAPIException(APIError(message=_('method_not_allowed')), status_code=500)
26
+ raise AdminAPIException(APIError(message=_('errors.method_not_allowed')), status_code=500)
25
27
 
26
28
  # pylint: disable=import-outside-toplevel
27
29
  from sqlalchemy import inspect
@@ -29,7 +31,7 @@ class SQLAlchemyAdminUpdate:
29
31
 
30
32
  if pk is None:
31
33
  raise AdminAPIException(
32
- APIError(message=_('pk_not_found') % {'pk_name': self.pk_name}, code='pk_not_found'),
34
+ APIError(message=_('errors.pk_not_found') % {'pk_name': self.pk_name}, code='pk_not_found'),
33
35
  status_code=400,
34
36
  )
35
37
 
@@ -42,7 +44,7 @@ class SQLAlchemyAdminUpdate:
42
44
  async with self.db_async_session() as session:
43
45
  record = (await session.execute(stmt)).scalars().first()
44
46
  if record is None:
45
- msg = _('record_not_found') % {'pk_name': self.pk_name, 'pk': pk}
47
+ msg = _('errors.record_not_found') % {'pk_name': self.pk_name, 'pk': pk}
46
48
  raise AdminAPIException(
47
49
  APIError(message=msg, code='record_not_found'),
48
50
  status_code=400,
@@ -59,7 +61,7 @@ class SQLAlchemyAdminUpdate:
59
61
  type(self).__name__, self.table_schema.model.__name__, pk, e,
60
62
  extra={'data': data},
61
63
  )
62
- msg = _('connection_refused_error') % {'error': str(e)}
64
+ msg = _('errors.connection_refused_error') % {'error': str(e)}
63
65
  raise AdminAPIException(
64
66
  APIError(message=msg, code='connection_refused_error'),
65
67
  status_code=400,
@@ -83,8 +85,11 @@ class SQLAlchemyAdminUpdate:
83
85
  type(self).__name__, self.table_schema.model.__name__, pk, e,
84
86
  extra={'data': data}
85
87
  )
88
+ msg = _('errors.db_error_update') % {
89
+ 'error_type': str(e) if admin_schema.debug else type(e).__name__,
90
+ }
86
91
  raise AdminAPIException(
87
- APIError(message=_('db_error_update'), code='db_error_update'), status_code=500,
92
+ APIError(message=msg, code='db_error_update'), status_code=500,
88
93
  ) from e
89
94
 
90
95
  logger.info(
@@ -1,13 +1,26 @@
1
1
  delete: 'Delete'
2
2
  delete_confirmation_text: "Are you sure you want to delete those records?\nThis action cannot be undone."
3
3
  deleted_successfully: 'The entries were successfully deleted.'
4
- pk_not_found: 'The "%(pk_name)s" field was not found in the submitted data.'
5
- record_not_found: 'No record found for %(pk_name)s=%(pk)s.'
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
- connection_refused_error: 'Database connection error: %(error)s'
4
+
5
+ errors:
6
+ pk_not_found: 'The "%(pk_name)s" field was not found in the submitted data.'
7
+ record_not_found: 'No record found for %(pk_name)s=%(pk)s.'
8
+
9
+ db_error_create: 'Error creating a record in the database: %(error_type)s'
10
+ db_error_update: 'Error updating the record in the database: %(error_type)s'
11
+ db_error_retrieve: 'Error retrieving the record from the database: %(error_type)s'
12
+ db_error_list: 'Failed to retrieve table data from the database: %(error_type)s'
13
+
14
+ connection_refused_error: 'Database connection error: %(error)s'
15
+ filters_exception: 'An unknown technical error occurred while filtering data: %(error_type)s'
16
+ method_not_allowed: 'Error, method not allowed. This action is not permitted.'
17
+ filter_error: 'An error occurred during filtering: {error}'
18
+ bad_type_error: 'Invalid data type: %(type)s but %(expected)s expected'
19
+
20
+ serialize_error:
21
+ field_error: 'Field %(field_slug)s serialize error: %(error)s'
22
+ unexpected_error: 'Unexpected serialize error: %(error)s'
23
+
11
24
  search_help: 'Available search fields: %(fields)s'
12
25
  sqlalchemy_search_help: |
13
26
  <b>Available search fields:</b>
@@ -17,6 +30,3 @@ sqlalchemy_search_help: |
17
30
  <b>""</b> - quotes for exact match
18
31
  <b>%%</b> - any sequence of characters
19
32
  <b>_</b> - any single character
20
- filters_exception: 'An unknown technical error occurred while filtering data.'
21
- method_not_allowed: 'Error, method not allowed. This action is not permitted.'
22
- filter_error: 'An error occurred during filtering: {error}'
@@ -1,13 +1,26 @@
1
1
  delete: 'Удалить'
2
2
  delete_confirmation_text: "Вы уверены, что хотите удалить данные записи?\nДанное действие нельзя отменить."
3
3
  deleted_successfully: 'Записи успешно удалены.'
4
- pk_not_found: 'Поле "%(pk_name)s" не найдено среди переданных данных.'
5
- record_not_found: 'Запись по ключу %(pk_name)s=%(pk)s не найдена.'
6
- db_error_create: 'Ошибка создания записи в базе данных.'
7
- db_error_update: 'Ошибка обновления записи в базе данных.'
8
- db_error_retrieve: 'Ошибка получения записи из базы данных.'
9
- db_error_list: 'Ошибка получения данных таблицы из базы данных.'
10
- connection_refused_error: 'Ошибка подключения к базе данных: %(error)s'
4
+
5
+ errors:
6
+ pk_not_found: 'Поле "%(pk_name)s" не найдено среди переданных данных.'
7
+ record_not_found: 'Запись по ключу %(pk_name)s=%(pk)s не найдена.'
8
+
9
+ db_error_create: 'Ошибка создания записи в базе данных: %(error_type)s'
10
+ db_error_update: 'Ошибка обновления записи в базе данных: %(error_type)s'
11
+ db_error_retrieve: 'Ошибка получения записи из базы данных: %(error_type)s'
12
+ db_error_list: 'Ошибка получения данных таблицы из базы данных: %(error_type)s'
13
+
14
+ connection_refused_error: 'Ошибка подключения к базе данных: %(error)s'
15
+ filters_exception: 'Произошла неизвестная техническая ошибка при фильтрации данных: %(error_type)s'
16
+ method_not_allowed: 'Ошибка, данный метод недоступен.'
17
+ filter_error: 'Проишла ошибка при фильтрации: %(error)s'
18
+ bad_type_error: 'Некорректный тип данных: %(type)s; ожидается %(expected)s'
19
+
20
+ serialize_error:
21
+ field_error: 'ошибка чтения поля %(field_slug)s: %(error)s'
22
+ unexpected_error: 'Ошибка обработки данных: %(error)s'
23
+
11
24
  search_help: 'Доступные поля для поиска: %(fields)s'
12
25
  sqlalchemy_search_help: |
13
26
  <b>Доступные поля для поиска:</b>
@@ -17,6 +30,3 @@ sqlalchemy_search_help: |
17
30
  <b>""</b> - кавычки для точного совпадения
18
31
  <b>%%</b> - любая последовательность символов
19
32
  <b>_</b> - один любой символ
20
- filters_exception: 'Произошла неизвестная техническая ошибка при фильтрации данных.'
21
- method_not_allowed: 'Ошибка, данный метод недоступен.'
22
- filter_error: 'Проишла ошибка при фильтрации: {error}'
@@ -1,7 +1,7 @@
1
1
  # pylint: disable=wildcard-import, unused-wildcard-import, unused-import
2
2
  # flake8: noqa: F405
3
3
  from .admin_schema import AdminSchema, AdminSchemaData
4
- from .category import Category
5
- from .graphs import *
6
- from .group import Group
4
+ from .category import CategoryGroup, CategoryLink
5
+ from .dashboard import *
7
6
  from .table import *
7
+ from .table.category_table import CategoryTable
@@ -7,11 +7,12 @@ from urllib.parse import urljoin
7
7
  from fastapi import FastAPI, Request
8
8
  from fastapi.middleware.cors import CORSMiddleware
9
9
  from fastapi.staticfiles import StaticFiles
10
+ from pydantic import Field
10
11
  from pydantic.dataclasses import dataclass
11
12
 
12
13
  from brilliance_admin.auth import UserABC
13
14
  from brilliance_admin.docs import build_redoc_docs, build_scalar_docs
14
- from brilliance_admin.schema.group import Group, GroupSchemaData
15
+ from brilliance_admin.schema.category import BaseCategory, CategorySchemaData
15
16
  from brilliance_admin.translations import LanguageContext, LanguageManager
16
17
  from brilliance_admin.utils import DataclassBase, SupportsStr
17
18
 
@@ -23,8 +24,8 @@ DEFAULT_LANGUAGES = {
23
24
 
24
25
  @dataclass
25
26
  class AdminSchemaData(DataclassBase):
26
- groups: Dict[str, GroupSchemaData]
27
27
  profile: UserABC | Any
28
+ categories: Dict[str, CategorySchemaData] = Field(default_factory=dict)
28
29
 
29
30
  def __post_init__(self):
30
31
  if not isinstance(self.profile, UserABC):
@@ -39,6 +40,7 @@ class AdminSettingsData(DataclassBase):
39
40
  login_greetings_message: SupportsStr | None
40
41
  navbar_density: str
41
42
  languages: Dict[str, str] | None
43
+ main_page: str | None = None
42
44
 
43
45
 
44
46
  @dataclass
@@ -50,9 +52,11 @@ class AdminIndexContextData(DataclassBase):
50
52
 
51
53
  @dataclass
52
54
  class AdminSchema:
53
- groups: List[Group]
55
+ categories: List[BaseCategory]
54
56
  auth: Any
55
57
 
58
+ main_page: str | None = None
59
+
56
60
  title: SupportsStr | None = 'Admin'
57
61
  description: SupportsStr | None = None
58
62
  login_greetings_message: SupportsStr | None = None
@@ -62,15 +66,17 @@ class AdminSchema:
62
66
 
63
67
  navbar_density: str = 'default'
64
68
 
65
- backend_prefix = None
66
- static_prefix = None
69
+ backend_prefix: str | None = None
70
+ static_prefix: str | None = None
67
71
 
68
72
  language_manager: LanguageManager | None = None
69
73
 
74
+ debug: bool = False
75
+
70
76
  def __post_init__(self):
71
- for group in self.groups:
72
- if not issubclass(group.__class__, Group):
73
- raise TypeError(f'Group "{group}" is not instance of Group subclass')
77
+ for category in self.categories:
78
+ if not issubclass(category.__class__, BaseCategory):
79
+ raise TypeError(f'Root category "{category}" is not instance of BaseCategory subclass')
74
80
 
75
81
  if not self.language_manager:
76
82
  self.language_manager = LanguageManager(DEFAULT_LANGUAGES)
@@ -81,24 +87,25 @@ class AdminSchema:
81
87
  def generate_schema(self, user: UserABC, language_slug: str | None) -> AdminSchemaData:
82
88
  language_context: LanguageContext = self.get_language_context(language_slug)
83
89
 
84
- groups = {}
90
+ result = AdminSchemaData(profile=user)
85
91
 
86
- for group in self.groups:
87
- if not group.slug:
88
- msg = f'Category group {type(group).__name__}.slug is empty'
92
+ for category in self.categories:
93
+ if not category.slug:
94
+ msg = f'Category {type(category).__name__}.slug is empty'
89
95
  raise AttributeError(msg)
90
96
 
91
- groups[group.slug] = group.generate_schema(user, language_context)
97
+ try:
98
+ result.categories[category.slug] = category.generate_schema(user, language_context).to_dict(keep_none=False)
99
+ except Exception as e:
100
+ msg = f'Root category "{category.slug}" generate_schema error: {e}'
101
+ raise Exception(msg) from e
92
102
 
93
- return AdminSchemaData(
94
- groups=groups,
95
- profile=user,
96
- )
103
+ return result
97
104
 
98
- def get_group(self, group_slug: str) -> Optional[Group]:
99
- for group in self.groups:
100
- if group.slug == group_slug:
101
- return group
105
+ def get_group(self, group_slug: str) -> Optional[BaseCategory]:
106
+ for category in self.categories:
107
+ if category.slug == group_slug:
108
+ return category
102
109
 
103
110
  return None
104
111
 
@@ -114,14 +121,13 @@ class AdminSchema:
114
121
 
115
122
  return AdminSettingsData(
116
123
  title=self.title,
124
+ main_page=self.main_page,
117
125
  description=self.description,
118
126
  login_greetings_message=self.login_greetings_message,
119
127
  navbar_density=self.navbar_density,
120
128
  languages=languages,
121
129
  )
122
130
 
123
- # pylint: disable=too-many-arguments
124
- # pylint: disable=too-many-positional-arguments
125
131
  def generate_app(
126
132
  self,
127
133
  debug=False,
@@ -131,6 +137,8 @@ class AdminSchema:
131
137
  include_docs=False,
132
138
  include_redoc=False,
133
139
  ) -> FastAPI:
140
+ self.debug = debug
141
+
134
142
  # pylint: disable=unused-variable
135
143
  language_context = self.get_language_context(language_slug=None)
136
144
 
@@ -4,10 +4,13 @@ from typing import Any, ClassVar, Dict, List
4
4
  from pydantic import Field
5
5
  from pydantic.dataclasses import dataclass
6
6
  from pydantic_core import core_schema
7
+ from structlog import get_logger
7
8
 
8
9
  from brilliance_admin.auth import UserABC
9
10
  from brilliance_admin.translations import LanguageContext
10
- from brilliance_admin.utils import DataclassBase, SupportsStr
11
+ from brilliance_admin.utils import DataclassBase, KwargsInitMixin, SupportsStr, humanize_field_name
12
+
13
+ logger = get_logger()
11
14
 
12
15
 
13
16
  # pylint: disable=too-many-instance-attributes
@@ -30,7 +33,6 @@ class FieldSchemaData(DataclassBase):
30
33
 
31
34
  choices: List[dict] | None = None
32
35
 
33
- tag_colors: dict | None = None
34
36
  variant: str | None = None
35
37
  size: str | None = None
36
38
 
@@ -93,7 +95,7 @@ class TableInfoSchemaData(DataclassBase):
93
95
 
94
96
 
95
97
  @dataclass
96
- class GraphInfoSchemaData(DataclassBase):
98
+ class DashboardInfoSchemaData(DataclassBase):
97
99
  search_enabled: bool
98
100
  search_help: str | None
99
101
 
@@ -107,37 +109,56 @@ class CategorySchemaData(DataclassBase):
107
109
  icon: str | None
108
110
  type: str
109
111
 
112
+ categories: dict = Field(default_factory=dict)
113
+
110
114
  table_info: TableInfoSchemaData | None = None
111
- graph_info: GraphInfoSchemaData | None = None
115
+ dashboard_info: DashboardInfoSchemaData | None = None
116
+
117
+ link: str | None = None
112
118
 
113
119
  def __repr__(self):
114
120
  return f'<CategorySchemaData type={self.type} "{self.title}">'
115
121
 
116
122
 
117
- class Category(abc.ABC):
118
- slug: ClassVar[str]
119
- title: ClassVar[SupportsStr | None] = None
120
- description: ClassVar[SupportsStr | None] = None
123
+ class BaseCategory(KwargsInitMixin, abc.ABC):
124
+ slug: str
125
+ title: SupportsStr | None = None
126
+ description: SupportsStr | None = None
121
127
 
122
128
  # https://pictogrammers.com/library/mdi/
123
- icon: ClassVar[str | None] = None
129
+ icon: str | None = None
124
130
 
125
131
  _type_slug: ClassVar[str]
126
132
 
127
133
  def generate_schema(self, user: UserABC, language_context: LanguageContext) -> CategorySchemaData:
128
- return CategorySchemaData(
129
- title=language_context.get_text(self.title) or self.slug,
134
+ type_slug = getattr(type(self), '_type_slug', None)
135
+ if not type_slug:
136
+ msg = f'{type(self).__name__}._type_slug must be set!'
137
+ raise AttributeError(msg)
138
+
139
+ result = CategorySchemaData(
140
+ title=language_context.get_text(self.title) or humanize_field_name(self.slug),
130
141
  description=language_context.get_text(self.description),
131
142
  icon=self.icon,
132
- type=self._type_slug,
143
+ type=type_slug,
133
144
  )
145
+ return result
146
+
147
+ def __init_subclass__(cls, **kwargs):
148
+ super().__init_subclass__(**kwargs)
149
+
150
+ if cls is BaseCategory:
151
+ return
152
+
153
+ if not issubclass(cls, BaseCategory):
154
+ raise TypeError(f'{cls.__name__} must inherit from Category')
134
155
 
135
156
  @classmethod
136
157
  def __get_pydantic_core_schema__(cls, source_type: Any, handler: Any) -> core_schema.CoreSchema:
137
- def validate(v: Any) -> "Category":
158
+ def validate(v: Any) -> "BaseCategory":
138
159
  if isinstance(v, cls):
139
160
  return v
140
- raise TypeError(f"Expected {cls.__name__} instance")
161
+ raise TypeError(f"Expected {cls.__name__} instance, recieved: {type(v)} {v}")
141
162
 
142
163
  return core_schema.no_info_plain_validator_function(
143
164
  validate,
@@ -147,3 +168,56 @@ class Category(abc.ABC):
147
168
  return_schema=core_schema.str_schema(),
148
169
  ),
149
170
  )
171
+
172
+
173
+ class CategoryLink(BaseCategory):
174
+ _type_slug: str = 'link'
175
+
176
+ link: str
177
+
178
+ def generate_schema(self, user: UserABC, language_context: LanguageContext) -> CategorySchemaData:
179
+ result = super().generate_schema(user, language_context)
180
+ result.link = self.link
181
+ return result
182
+
183
+
184
+ class CategoryGroup(BaseCategory):
185
+ _type_slug: str = 'group'
186
+
187
+ subcategories: list = Field(default_factory=list)
188
+
189
+ def __init__(self, *args, **kwargs):
190
+ super().__init__(*args, **kwargs)
191
+
192
+ for category in self.subcategories:
193
+ if not isinstance(category, BaseCategory):
194
+ raise TypeError(f'Category "{category}" is not instance of BaseCategory subclass')
195
+
196
+ def generate_schema(self, user: UserABC, language_context: LanguageContext) -> CategorySchemaData:
197
+ result = super().generate_schema(user, language_context)
198
+
199
+ for category in self.subcategories:
200
+
201
+ if not category.slug:
202
+ msg = f'Category {type(category).__name__}.slug is empty'
203
+ raise AttributeError(msg)
204
+
205
+ if category.slug in result.categories:
206
+ exists = result.categories[category.slug]
207
+ msg = f'Category {type(category).__name__}.slug "{self.slug}" already registered by "{exists.title}"'
208
+ raise KeyError(msg)
209
+
210
+ try:
211
+ result.categories[category.slug] = category.generate_schema(user, language_context)
212
+ except Exception as e:
213
+ msg = f'Category "{category.slug}" {type(category)} generate_schema error: {e}'
214
+ raise Exception(msg) from e
215
+
216
+ return result
217
+
218
+ def get_category(self, category_slug: str):
219
+ for category in self.subcategories:
220
+ if category.slug == category_slug:
221
+ return category
222
+
223
+ return None
@@ -0,0 +1 @@
1
+ from .category_dashboard import CategoryDashboard
@@ -0,0 +1,87 @@
1
+ from typing import Any, Dict, List
2
+
3
+ from pydantic import BaseModel, Field, field_serializer
4
+
5
+ from brilliance_admin.schema.category import BaseCategory, DashboardInfoSchemaData
6
+ from brilliance_admin.schema.table.fields_schema import FieldsSchema
7
+ from brilliance_admin.translations import LanguageContext
8
+ from brilliance_admin.utils import SupportsStr
9
+
10
+
11
+ class DashboardData(BaseModel):
12
+ search: str | None = None
13
+ filters: Dict[str, Any] = Field(default_factory=dict)
14
+
15
+
16
+ class ChartData(BaseModel):
17
+ data: dict
18
+ options: dict
19
+ width: int | None = None
20
+ height: int = 50
21
+ type: str = 'line'
22
+
23
+ component_type: str = 'chart'
24
+
25
+
26
+ class Subcard(BaseModel):
27
+ title: SupportsStr
28
+ value: SupportsStr
29
+ color: str | None = None
30
+
31
+
32
+ class PeriodGraph(BaseModel):
33
+ title: SupportsStr
34
+ value: SupportsStr
35
+ change: int | float | None = None
36
+ subcards: List[Subcard] = Field(default_factory=list)
37
+ values: List[List[int | float]] = Field(default_factory=list)
38
+ vertical: List[SupportsStr] = Field(default_factory=list)
39
+ horizontal: List[SupportsStr] = Field(default_factory=list)
40
+ component_type: str = 'period_graph'
41
+
42
+ @field_serializer('horizontal', 'vertical')
43
+ def serialize_str_list(self, val: list) -> list:
44
+ return [str(v) for v in val]
45
+
46
+
47
+ class SmallGraph(BaseModel):
48
+ title: SupportsStr
49
+ value: SupportsStr
50
+ change: int | float | None = None
51
+ points: Dict[SupportsStr, float | int] = Field(default_factory=list)
52
+ component_type: str = 'small_graph'
53
+
54
+
55
+ class DashboardContainer(BaseModel):
56
+ cols: int | None = None
57
+ md: int | None = None
58
+ lg: int | None = None
59
+ sm: int | None = None
60
+
61
+ component_type: str = 'container'
62
+ components: List[Any] = Field(default_factory=list)
63
+
64
+
65
+ class CategoryDashboard(BaseCategory):
66
+ _type_slug: str = 'dashboard'
67
+
68
+ search_enabled: bool = False
69
+ search_help: SupportsStr | None = None
70
+
71
+ table_filters: FieldsSchema | None = None
72
+
73
+ def generate_schema(self, user, language_context: LanguageContext) -> DashboardInfoSchemaData:
74
+ schema = super().generate_schema(user, language_context)
75
+ dashboard_info = DashboardInfoSchemaData(
76
+ search_enabled=self.search_enabled,
77
+ search_help=language_context.get_text(self.search_help),
78
+ )
79
+
80
+ if self.table_filters:
81
+ dashboard_info.table_filters = self.table_filters.generate_schema(user, language_context)
82
+
83
+ schema.dashboard_info = dashboard_info
84
+ return schema
85
+
86
+ async def get_data(self, data: DashboardData, user) -> DashboardContainer:
87
+ raise NotImplementedError('get_data is not implemented')
@@ -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 import Category
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
@@ -17,7 +17,7 @@ from brilliance_admin.translations import LanguageContext
17
17
  from brilliance_admin.utils import DeserializeAction, SupportsStr
18
18
 
19
19
 
20
- class CategoryTable(Category):
20
+ class CategoryTable(BaseCategory):
21
21
  _type_slug: str = 'table'
22
22
 
23
23
  search_enabled: bool = False
@@ -32,6 +32,8 @@ class CategoryTable(Category):
32
32
  pk_name: str | None = None
33
33
 
34
34
  def __init__(self, *args, table_schema=None, table_filters=None, **kwargs):
35
+ super().__init__(*args, **kwargs)
36
+
35
37
  if table_schema:
36
38
  self.table_schema = table_schema
37
39
 
@@ -128,6 +130,7 @@ class CategoryTable(Category):
128
130
  action_data: ActionData,
129
131
  language_context: LanguageContext,
130
132
  user: UserABC,
133
+ admin_schema: AdminSchema,
131
134
  ) -> ActionResult:
132
135
  action_fn = self._get_action_fn(action)
133
136
  if action_fn is None:
@@ -154,7 +157,7 @@ class CategoryTable(Category):
154
157
 
155
158
  return result
156
159
 
157
- async def autocomplete(self, data: AutocompleteData, user: UserABC) -> AutocompleteResult:
160
+ async def autocomplete(self, data: AutocompleteData, user: UserABC, schema: AdminSchema) -> AutocompleteResult:
158
161
  """
159
162
  Retrieves list of found options to select.
160
163
  """
@@ -162,14 +165,16 @@ class CategoryTable(Category):
162
165
 
163
166
  # pylint: disable=too-many-arguments
164
167
  @abc.abstractmethod
165
- 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:
166
171
  raise NotImplementedError()
167
172
 
168
- # 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:
169
174
  # raise NotImplementedError()
170
175
 
171
- # 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:
172
177
  # raise NotImplementedError()
173
178
 
174
- # 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:
175
180
  # raise NotImplementedError()