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.
- brilliance_admin/api/routers.py +2 -2
- brilliance_admin/api/views/autocomplete.py +1 -1
- brilliance_admin/api/views/{graphs.py → dashboard.py} +5 -5
- brilliance_admin/api/views/table.py +7 -6
- brilliance_admin/exceptions.py +1 -0
- brilliance_admin/integrations/sqlalchemy/__init__.py +1 -0
- brilliance_admin/integrations/sqlalchemy/auth.py +3 -4
- brilliance_admin/integrations/sqlalchemy/autocomplete.py +12 -3
- brilliance_admin/integrations/sqlalchemy/fields.py +36 -19
- brilliance_admin/integrations/sqlalchemy/fields_schema.py +21 -12
- brilliance_admin/integrations/sqlalchemy/table/base.py +4 -4
- brilliance_admin/integrations/sqlalchemy/table/create.py +6 -3
- brilliance_admin/integrations/sqlalchemy/table/delete.py +2 -2
- brilliance_admin/integrations/sqlalchemy/table/list.py +44 -14
- brilliance_admin/integrations/sqlalchemy/table/retrieve.py +41 -10
- brilliance_admin/integrations/sqlalchemy/table/update.py +10 -5
- brilliance_admin/locales/en.yml +20 -10
- brilliance_admin/locales/ru.yml +20 -10
- brilliance_admin/schema/__init__.py +3 -3
- brilliance_admin/schema/admin_schema.py +31 -23
- brilliance_admin/schema/category.py +88 -14
- brilliance_admin/schema/dashboard/__init__.py +1 -0
- brilliance_admin/schema/dashboard/category_dashboard.py +87 -0
- brilliance_admin/schema/table/category_table.py +13 -8
- brilliance_admin/schema/table/fields/base.py +102 -19
- brilliance_admin/schema/table/fields_schema.py +9 -2
- brilliance_admin/static/{index-D9axz5zK.js → index-8ahvKI6W.js} +190 -190
- brilliance_admin/static/{index-vlBToOhT.css → index-B8JOx1Ps.css} +1 -1
- brilliance_admin/templates/index.html +2 -2
- brilliance_admin/translations.py +6 -3
- brilliance_admin/utils.py +41 -3
- brilliance_admin-0.44.12.dist-info/METADATA +165 -0
- {brilliance_admin-0.43.1.dist-info → brilliance_admin-0.44.12.dist-info}/RECORD +36 -37
- {brilliance_admin-0.43.1.dist-info → brilliance_admin-0.44.12.dist-info}/WHEEL +1 -1
- brilliance_admin-0.44.12.dist-info/licenses/LICENSE +21 -0
- brilliance_admin/schema/graphs/__init__.py +0 -1
- brilliance_admin/schema/graphs/category_graphs.py +0 -51
- brilliance_admin/schema/group.py +0 -67
- brilliance_admin-0.43.1.dist-info/METADATA +0 -214
- brilliance_admin-0.43.1.dist-info/licenses/LICENSE +0 -17
- {brilliance_admin-0.43.1.dist-info → brilliance_admin-0.44.12.dist-info}/top_level.txt +0 -0
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import abc
|
|
2
2
|
import datetime
|
|
3
|
-
from
|
|
3
|
+
from enum import Enum
|
|
4
|
+
from typing import Any, ClassVar
|
|
4
5
|
|
|
5
6
|
from pydantic.dataclasses import dataclass
|
|
6
7
|
|
|
7
8
|
from brilliance_admin.exceptions import FieldError
|
|
8
9
|
from brilliance_admin.schema.category import FieldSchemaData
|
|
9
10
|
from brilliance_admin.translations import LanguageContext
|
|
11
|
+
from brilliance_admin.translations import TranslateText as _
|
|
10
12
|
from brilliance_admin.utils import DeserializeAction, SupportsStr, humanize_field_name
|
|
11
13
|
|
|
12
14
|
|
|
@@ -52,6 +54,8 @@ class TableField(abc.ABC, FieldSchemaData):
|
|
|
52
54
|
class IntegerField(TableField):
|
|
53
55
|
_type = 'integer'
|
|
54
56
|
|
|
57
|
+
choices: Any | None = None
|
|
58
|
+
|
|
55
59
|
max_value: int | None = None
|
|
56
60
|
min_value: int | None = None
|
|
57
61
|
|
|
@@ -62,8 +66,6 @@ class IntegerField(TableField):
|
|
|
62
66
|
def generate_schema(self, user, field_slug, language_context: LanguageContext) -> FieldSchemaData:
|
|
63
67
|
schema = super().generate_schema(user, field_slug, language_context)
|
|
64
68
|
|
|
65
|
-
schema.choices = self.choices
|
|
66
|
-
|
|
67
69
|
if self.max_value is not None:
|
|
68
70
|
schema.max_value = self.max_value
|
|
69
71
|
|
|
@@ -79,7 +81,7 @@ class IntegerField(TableField):
|
|
|
79
81
|
async def deserialize(self, value, action: DeserializeAction, extra: dict, *args, **kwargs) -> Any:
|
|
80
82
|
value = await super().deserialize(value, action, extra, *args, **kwargs)
|
|
81
83
|
if value and not isinstance(value, int):
|
|
82
|
-
raise FieldError(
|
|
84
|
+
raise FieldError(_('errors.bad_type_error') % {'type': type(value), 'expected': 'init'})
|
|
83
85
|
|
|
84
86
|
return value
|
|
85
87
|
|
|
@@ -95,13 +97,12 @@ class StringField(TableField):
|
|
|
95
97
|
min_length: int | None = None
|
|
96
98
|
max_length: int | None = None
|
|
97
99
|
|
|
98
|
-
choices:
|
|
100
|
+
choices: Any | None = None
|
|
99
101
|
|
|
100
102
|
def generate_schema(self, user, field_slug, language_context: LanguageContext) -> FieldSchemaData:
|
|
101
103
|
schema = super().generate_schema(user, field_slug, language_context)
|
|
102
104
|
|
|
103
105
|
schema.multilined = self.multilined
|
|
104
|
-
schema.choices = self.choices
|
|
105
106
|
schema.ckeditor = self.ckeditor
|
|
106
107
|
schema.tinymce = self.tinymce
|
|
107
108
|
|
|
@@ -116,7 +117,7 @@ class StringField(TableField):
|
|
|
116
117
|
async def deserialize(self, value, action: DeserializeAction, extra: dict, *args, **kwargs) -> Any:
|
|
117
118
|
value = await super().deserialize(value, action, extra, *args, **kwargs)
|
|
118
119
|
if value and not isinstance(value, str):
|
|
119
|
-
raise FieldError(
|
|
120
|
+
raise FieldError(_('errors.bad_type_error') % {'type': type(value), 'expected': 'string'})
|
|
120
121
|
|
|
121
122
|
return value
|
|
122
123
|
|
|
@@ -126,6 +127,18 @@ class BooleanField(TableField):
|
|
|
126
127
|
_type = 'boolean'
|
|
127
128
|
|
|
128
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
|
+
|
|
129
142
|
@dataclass
|
|
130
143
|
class DateTimeField(TableField):
|
|
131
144
|
_type = 'datetime'
|
|
@@ -151,28 +164,40 @@ class DateTimeField(TableField):
|
|
|
151
164
|
return
|
|
152
165
|
|
|
153
166
|
if value and not isinstance(value, (str, dict)):
|
|
154
|
-
raise FieldError(
|
|
167
|
+
raise FieldError(_('errors.bad_type_error') % {'type': type(value), 'expected': 'datetime'})
|
|
155
168
|
|
|
156
169
|
if isinstance(value, str):
|
|
157
|
-
return
|
|
170
|
+
return _parse_iso(value)
|
|
158
171
|
|
|
159
172
|
if isinstance(value, dict):
|
|
160
173
|
if not value.get('from') or not value.get('to'):
|
|
161
|
-
|
|
162
|
-
|
|
174
|
+
raise FieldError(
|
|
175
|
+
f'{type(self).__name__} value must be dict with from,to values: {value}'
|
|
176
|
+
)
|
|
163
177
|
|
|
164
178
|
return {
|
|
165
|
-
'from':
|
|
166
|
-
'to':
|
|
179
|
+
'from': _parse_iso(value['from']),
|
|
180
|
+
'to': _parse_iso(value['to']),
|
|
167
181
|
}
|
|
168
182
|
|
|
169
|
-
raise
|
|
183
|
+
raise FieldError(_('errors.bad_type_error') % {'type': type(value), 'expected': 'datetime'})
|
|
170
184
|
|
|
171
185
|
|
|
172
186
|
@dataclass
|
|
173
187
|
class JSONField(TableField):
|
|
174
188
|
_type = 'json'
|
|
175
189
|
|
|
190
|
+
async def deserialize(self, value, action: DeserializeAction, extra: dict, *args, **kwargs) -> Any:
|
|
191
|
+
value = await super().deserialize(value, action, extra, *args, **kwargs)
|
|
192
|
+
|
|
193
|
+
if value is None:
|
|
194
|
+
return
|
|
195
|
+
|
|
196
|
+
if not isinstance(value, (dict, list)):
|
|
197
|
+
raise FieldError(_('errors.bad_type_error') % {'type': type(value), 'expected': 'JSON'})
|
|
198
|
+
|
|
199
|
+
return value
|
|
200
|
+
|
|
176
201
|
|
|
177
202
|
@dataclass
|
|
178
203
|
class ArrayField(TableField):
|
|
@@ -190,8 +215,11 @@ class ArrayField(TableField):
|
|
|
190
215
|
async def deserialize(self, value, action: DeserializeAction, extra: dict, *args, **kwargs) -> Any:
|
|
191
216
|
value = await super().deserialize(value, action, extra, *args, **kwargs)
|
|
192
217
|
|
|
193
|
-
if value
|
|
194
|
-
|
|
218
|
+
if value is None:
|
|
219
|
+
return
|
|
220
|
+
|
|
221
|
+
if not isinstance(value, list):
|
|
222
|
+
raise FieldError(_('errors.bad_type_error') % {'type': type(value), 'expected': 'Array'})
|
|
195
223
|
|
|
196
224
|
return value
|
|
197
225
|
|
|
@@ -227,23 +255,78 @@ class ImageField(TableField):
|
|
|
227
255
|
class ChoiceField(TableField):
|
|
228
256
|
_type = 'choice'
|
|
229
257
|
|
|
258
|
+
# Tag color available:
|
|
230
259
|
# https://vuetifyjs.com/en/styles/colors/#classes
|
|
231
|
-
|
|
260
|
+
choices: Any | None = None
|
|
232
261
|
|
|
233
262
|
# https://vuetifyjs.com/en/components/chips/#color-and-variants
|
|
234
263
|
variant: str = 'elevated'
|
|
235
264
|
size: str = 'default'
|
|
236
265
|
|
|
266
|
+
def __post_init__(self):
|
|
267
|
+
self.choices = self.generate_choices()
|
|
268
|
+
|
|
269
|
+
def generate_choices(self):
|
|
270
|
+
if not self.choices:
|
|
271
|
+
return None
|
|
272
|
+
|
|
273
|
+
if issubclass(self.choices, Enum):
|
|
274
|
+
return [
|
|
275
|
+
{'value': c.value, 'title': c.label, 'tag_color': getattr(c, 'tag_color', None)}
|
|
276
|
+
for c in self.choices
|
|
277
|
+
]
|
|
278
|
+
|
|
279
|
+
msg = f'Field choices is not suppored: {self.choices}'
|
|
280
|
+
raise NotImplementedError(msg)
|
|
281
|
+
|
|
282
|
+
def find_choice(self, value):
|
|
283
|
+
if not self.choices:
|
|
284
|
+
return None
|
|
285
|
+
|
|
286
|
+
return next((c for c in self.choices if c.get('value') == value), None)
|
|
287
|
+
|
|
237
288
|
def generate_schema(self, user, field_slug, language_context: LanguageContext) -> FieldSchemaData:
|
|
238
289
|
schema = super().generate_schema(user, field_slug, language_context)
|
|
239
290
|
|
|
240
291
|
schema.choices = self.choices
|
|
241
292
|
|
|
242
|
-
schema.tag_colors = self.tag_colors
|
|
243
293
|
schema.size = self.size
|
|
244
294
|
schema.variant = self.variant
|
|
245
295
|
|
|
246
296
|
return schema
|
|
247
297
|
|
|
248
298
|
async def serialize(self, value, extra: dict, *args, **kwargs) -> Any:
|
|
249
|
-
|
|
299
|
+
if not value:
|
|
300
|
+
return
|
|
301
|
+
|
|
302
|
+
choice = self.find_choice(value)
|
|
303
|
+
return {
|
|
304
|
+
'value': value,
|
|
305
|
+
'title': choice.get('title') or value if choice else value.capitalize(),
|
|
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
|
|
@@ -148,9 +148,10 @@ class FieldsSchema:
|
|
|
148
148
|
list_display=self.list_display,
|
|
149
149
|
)
|
|
150
150
|
|
|
151
|
+
context = {'language_context': language_context}
|
|
151
152
|
for field_slug, field in self.get_fields().items():
|
|
152
153
|
field_schema: FieldSchemaData = field.generate_schema(user, field_slug, language_context)
|
|
153
|
-
fields_schema.fields[field_slug] = field_schema.to_dict(keep_none=False)
|
|
154
|
+
fields_schema.fields[field_slug] = field_schema.to_dict(keep_none=False, context=context)
|
|
154
155
|
|
|
155
156
|
return fields_schema
|
|
156
157
|
|
|
@@ -158,7 +159,13 @@ class FieldsSchema:
|
|
|
158
159
|
result = {}
|
|
159
160
|
for field_slug, field in self.get_fields().items():
|
|
160
161
|
value = data.get(field_slug)
|
|
161
|
-
|
|
162
|
+
|
|
163
|
+
try:
|
|
164
|
+
result[field_slug] = await field.serialize(value, extra)
|
|
165
|
+
except FieldError as e:
|
|
166
|
+
e.field_slug = field_slug
|
|
167
|
+
raise e
|
|
168
|
+
|
|
162
169
|
return result
|
|
163
170
|
|
|
164
171
|
async def deserialize(self, data: dict, action: DeserializeAction, extra) -> dict:
|