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.
- admin_panel/__init__.py +4 -0
- admin_panel/api/__init__.py +0 -0
- admin_panel/api/routers.py +18 -0
- admin_panel/api/utils.py +28 -0
- admin_panel/api/views/__init__.py +0 -0
- admin_panel/api/views/auth.py +29 -0
- admin_panel/api/views/autocomplete.py +33 -0
- admin_panel/api/views/graphs.py +30 -0
- admin_panel/api/views/index.py +38 -0
- admin_panel/api/views/schema.py +29 -0
- admin_panel/api/views/settings.py +29 -0
- admin_panel/api/views/table.py +136 -0
- admin_panel/auth.py +32 -0
- admin_panel/docs.py +37 -0
- admin_panel/exceptions.py +38 -0
- admin_panel/integrations/__init__.py +0 -0
- admin_panel/integrations/sqlalchemy/__init__.py +6 -0
- admin_panel/integrations/sqlalchemy/auth.py +144 -0
- admin_panel/integrations/sqlalchemy/autocomplete.py +38 -0
- admin_panel/integrations/sqlalchemy/fields.py +254 -0
- admin_panel/integrations/sqlalchemy/fields_schema.py +316 -0
- admin_panel/integrations/sqlalchemy/table/__init__.py +19 -0
- admin_panel/integrations/sqlalchemy/table/base.py +141 -0
- admin_panel/integrations/sqlalchemy/table/create.py +73 -0
- admin_panel/integrations/sqlalchemy/table/delete.py +18 -0
- admin_panel/integrations/sqlalchemy/table/list.py +178 -0
- admin_panel/integrations/sqlalchemy/table/retrieve.py +61 -0
- admin_panel/integrations/sqlalchemy/table/update.py +95 -0
- admin_panel/schema/__init__.py +7 -0
- admin_panel/schema/admin_schema.py +191 -0
- admin_panel/schema/category.py +149 -0
- admin_panel/schema/graphs/__init__.py +1 -0
- admin_panel/schema/graphs/category_graphs.py +50 -0
- admin_panel/schema/group.py +67 -0
- admin_panel/schema/table/__init__.py +8 -0
- admin_panel/schema/table/admin_action.py +76 -0
- admin_panel/schema/table/category_table.py +175 -0
- admin_panel/schema/table/fields/__init__.py +5 -0
- admin_panel/schema/table/fields/base.py +249 -0
- admin_panel/schema/table/fields/function_field.py +65 -0
- admin_panel/schema/table/fields_schema.py +216 -0
- admin_panel/schema/table/table_models.py +53 -0
- admin_panel/static/favicon.jpg +0 -0
- admin_panel/static/index-BeniOHDv.js +525 -0
- admin_panel/static/index-vlBToOhT.css +8 -0
- admin_panel/static/materialdesignicons-webfont-CYDMK1kx.woff2 +0 -0
- admin_panel/static/materialdesignicons-webfont-CgCzGbLl.woff +0 -0
- admin_panel/static/materialdesignicons-webfont-D3kAzl71.ttf +0 -0
- admin_panel/static/materialdesignicons-webfont-DttUABo4.eot +0 -0
- admin_panel/static/tinymce/dark-first/content.min.css +250 -0
- admin_panel/static/tinymce/dark-first/skin.min.css +2820 -0
- admin_panel/static/tinymce/dark-slim/content.min.css +249 -0
- admin_panel/static/tinymce/dark-slim/skin.min.css +2821 -0
- admin_panel/static/tinymce/img/example.png +0 -0
- admin_panel/static/tinymce/img/tinymce.woff2 +0 -0
- admin_panel/static/tinymce/lightgray/content.min.css +1 -0
- admin_panel/static/tinymce/lightgray/fonts/tinymce.woff +0 -0
- admin_panel/static/tinymce/lightgray/skin.min.css +1 -0
- admin_panel/static/tinymce/plugins/accordion/css/accordion.css +17 -0
- admin_panel/static/tinymce/plugins/accordion/plugin.js +48 -0
- admin_panel/static/tinymce/plugins/codesample/css/prism.css +1 -0
- admin_panel/static/tinymce/plugins/customLink/css/link.css +3 -0
- admin_panel/static/tinymce/plugins/customLink/plugin.js +147 -0
- admin_panel/static/tinymce/tinymce.min.js +2 -0
- admin_panel/static/vanilla-picker-B6E6ObS_.js +8 -0
- admin_panel/templates/index.html +25 -0
- admin_panel/translations.py +145 -0
- admin_panel/utils.py +50 -0
- brilliance_admin-0.42.0.dist-info/METADATA +155 -0
- brilliance_admin-0.42.0.dist-info/RECORD +73 -0
- brilliance_admin-0.42.0.dist-info/WHEEL +5 -0
- brilliance_admin-0.42.0.dist-info/licenses/LICENSE +17 -0
- brilliance_admin-0.42.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
import asyncio
|
|
3
|
+
import copy
|
|
4
|
+
from typing import Awaitable, List
|
|
5
|
+
|
|
6
|
+
from fastapi import HTTPException, Request
|
|
7
|
+
from pydantic import Field
|
|
8
|
+
|
|
9
|
+
from admin_panel.auth import UserABC
|
|
10
|
+
from admin_panel.exceptions import AdminAPIException, APIError
|
|
11
|
+
from admin_panel.schema import Category
|
|
12
|
+
from admin_panel.schema.category import TableInfoSchemaData
|
|
13
|
+
from admin_panel.schema.table.admin_action import ActionData, ActionResult
|
|
14
|
+
from admin_panel.schema.table.fields_schema import FieldsSchema
|
|
15
|
+
from admin_panel.schema.table.table_models import AutocompleteData, AutocompleteResult, ListData, TableListResult
|
|
16
|
+
from admin_panel.translations import LanguageManager, TranslateText
|
|
17
|
+
from admin_panel.utils import DeserializeAction
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class CategoryTable(Category):
|
|
21
|
+
_type_slug: str = 'table'
|
|
22
|
+
|
|
23
|
+
search_enabled: bool = False
|
|
24
|
+
search_help: str | TranslateText | None = None
|
|
25
|
+
|
|
26
|
+
table_schema: FieldsSchema = None
|
|
27
|
+
table_filters: FieldsSchema | None = None
|
|
28
|
+
|
|
29
|
+
ordering_fields: List[str] = Field(default_factory=list)
|
|
30
|
+
default_ordering: str | None = None
|
|
31
|
+
|
|
32
|
+
pk_name: str | None = None
|
|
33
|
+
|
|
34
|
+
def __init__(self, *args, table_schema=None, table_filters=None, **kwargs):
|
|
35
|
+
if table_schema:
|
|
36
|
+
self.table_schema = table_schema
|
|
37
|
+
|
|
38
|
+
if table_filters:
|
|
39
|
+
self.table_filters = table_filters
|
|
40
|
+
|
|
41
|
+
if self.slug is None:
|
|
42
|
+
msg = f'Category table attribute {type(self).__name__}.slug must be set'
|
|
43
|
+
raise Exception(msg)
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def has_retrieve(self):
|
|
47
|
+
if not self.pk_name:
|
|
48
|
+
return False
|
|
49
|
+
|
|
50
|
+
fn = getattr(self, 'retrieve', None)
|
|
51
|
+
return asyncio.iscoroutinefunction(fn)
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def has_create(self):
|
|
55
|
+
fn = getattr(self, 'create', None)
|
|
56
|
+
return asyncio.iscoroutinefunction(fn)
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def has_update(self):
|
|
60
|
+
fn = getattr(self, 'update', None)
|
|
61
|
+
return asyncio.iscoroutinefunction(fn)
|
|
62
|
+
|
|
63
|
+
def generate_schema(self, user, language_manager: LanguageManager) -> dict:
|
|
64
|
+
schema = super().generate_schema(user, language_manager)
|
|
65
|
+
|
|
66
|
+
table_schema = getattr(self, 'table_schema', None)
|
|
67
|
+
if not table_schema or not issubclass(table_schema.__class__, FieldsSchema):
|
|
68
|
+
raise AttributeError(f'Admin category {self.__class__} must have table_schema instance of FieldsSchema')
|
|
69
|
+
|
|
70
|
+
table = TableInfoSchemaData(
|
|
71
|
+
table_schema=self.table_schema.generate_schema(user, language_manager),
|
|
72
|
+
ordering_fields=self.ordering_fields,
|
|
73
|
+
default_ordering=self.default_ordering,
|
|
74
|
+
|
|
75
|
+
search_enabled=self.search_enabled,
|
|
76
|
+
search_help=language_manager.get_text(self.search_help),
|
|
77
|
+
|
|
78
|
+
pk_name=self.pk_name,
|
|
79
|
+
can_retrieve=self.has_retrieve,
|
|
80
|
+
|
|
81
|
+
can_create=self.has_create,
|
|
82
|
+
can_update=self.has_update,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
if self.table_filters:
|
|
86
|
+
table.table_filters = self.table_filters.generate_schema(user, language_manager)
|
|
87
|
+
|
|
88
|
+
actions = {}
|
|
89
|
+
for attribute_name in dir(self):
|
|
90
|
+
if '__' in attribute_name:
|
|
91
|
+
continue
|
|
92
|
+
|
|
93
|
+
attribute = getattr(self, attribute_name)
|
|
94
|
+
if asyncio.iscoroutinefunction(attribute) and getattr(attribute, '__action__', False):
|
|
95
|
+
action = copy.copy(attribute.action_info)
|
|
96
|
+
|
|
97
|
+
action['title'] = language_manager.get_text(action.get('title'))
|
|
98
|
+
action['description'] = language_manager.get_text(action.get('description'))
|
|
99
|
+
action['confirmation_text'] = language_manager.get_text(action.get('confirmation_text'))
|
|
100
|
+
|
|
101
|
+
form_schema = action['form_schema']
|
|
102
|
+
if form_schema:
|
|
103
|
+
try:
|
|
104
|
+
action['form_schema'] = form_schema.generate_schema(user, language_manager)
|
|
105
|
+
except Exception as e:
|
|
106
|
+
msg = f'Action {attribute} form schema {form_schema} error: {e}'
|
|
107
|
+
raise Exception(msg) from e
|
|
108
|
+
|
|
109
|
+
actions[attribute_name] = action
|
|
110
|
+
|
|
111
|
+
table.actions = actions
|
|
112
|
+
schema.table_info = table
|
|
113
|
+
return schema
|
|
114
|
+
|
|
115
|
+
def _get_action_fn(self, action: str) -> Awaitable | None:
|
|
116
|
+
attribute = getattr(self, action)
|
|
117
|
+
if not asyncio.iscoroutinefunction(attribute) or not getattr(attribute, '__action__', False):
|
|
118
|
+
return None
|
|
119
|
+
|
|
120
|
+
return attribute
|
|
121
|
+
|
|
122
|
+
# pylint: disable=too-many-arguments
|
|
123
|
+
# pylint: disable=too-many-positional-arguments
|
|
124
|
+
async def _perform_action(
|
|
125
|
+
self,
|
|
126
|
+
request: Request,
|
|
127
|
+
action: str,
|
|
128
|
+
action_data: ActionData,
|
|
129
|
+
language_manager: LanguageManager,
|
|
130
|
+
user: UserABC,
|
|
131
|
+
) -> ActionResult:
|
|
132
|
+
action_fn = self._get_action_fn(action)
|
|
133
|
+
if action_fn is None:
|
|
134
|
+
raise HTTPException(status_code=404, detail=f'Action "{action}" is not found')
|
|
135
|
+
|
|
136
|
+
try:
|
|
137
|
+
form_schema = action_fn.action_info['form_schema']
|
|
138
|
+
if form_schema:
|
|
139
|
+
deserialized_data = await form_schema.deserialize(
|
|
140
|
+
action_data.form_data,
|
|
141
|
+
action=DeserializeAction.TABLE_ACTION,
|
|
142
|
+
extra={'user': user, 'request': request}
|
|
143
|
+
)
|
|
144
|
+
action_data.form_data = deserialized_data
|
|
145
|
+
|
|
146
|
+
result: ActionResult = await action_fn(action_data)
|
|
147
|
+
except AdminAPIException as e:
|
|
148
|
+
raise e
|
|
149
|
+
except Exception as e:
|
|
150
|
+
raise AdminAPIException(
|
|
151
|
+
APIError(message=str(e), code='user_action_error'),
|
|
152
|
+
status_code=500,
|
|
153
|
+
) from e
|
|
154
|
+
|
|
155
|
+
return result
|
|
156
|
+
|
|
157
|
+
async def autocomplete(self, data: AutocompleteData, user: UserABC) -> AutocompleteResult:
|
|
158
|
+
"""
|
|
159
|
+
Retrieves list of found options to select.
|
|
160
|
+
"""
|
|
161
|
+
raise NotImplementedError('autocomplete is not implemented')
|
|
162
|
+
|
|
163
|
+
# pylint: disable=too-many-arguments
|
|
164
|
+
@abc.abstractmethod
|
|
165
|
+
async def get_list(self, list_data: ListData, user: UserABC, language_manager: LanguageManager) -> TableListResult:
|
|
166
|
+
raise NotImplementedError()
|
|
167
|
+
|
|
168
|
+
# async def retrieve(self, pk: Any, user: UserABC) -> RetrieveResult:
|
|
169
|
+
# raise NotImplementedError()
|
|
170
|
+
|
|
171
|
+
# async def create(self, data: dict, user: UserABC) -> CreateResult:
|
|
172
|
+
# raise NotImplementedError()
|
|
173
|
+
|
|
174
|
+
# async def update(self, pk: Any, data: dict, user: UserABC) -> UpdateResult:
|
|
175
|
+
# raise NotImplementedError()
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# pylint: disable=wildcard-import, unused-wildcard-import, unused-import
|
|
2
|
+
# flake8: noqa: F405
|
|
3
|
+
from .base import (
|
|
4
|
+
ArrayField, BooleanField, ChoiceField, DateTimeField, FileField, ImageField, IntegerField, JSONField, StringField)
|
|
5
|
+
from .function_field import FunctionField, function_field
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
import datetime
|
|
3
|
+
from typing import Any, ClassVar, List, Tuple
|
|
4
|
+
|
|
5
|
+
from pydantic.dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
from admin_panel.exceptions import FieldError
|
|
8
|
+
from admin_panel.schema.category import FieldSchemaData
|
|
9
|
+
from admin_panel.translations import LanguageManager, TranslateText
|
|
10
|
+
from admin_panel.utils import DeserializeAction, humanize_field_name
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class TableField(abc.ABC, FieldSchemaData):
|
|
15
|
+
_type: ClassVar[str]
|
|
16
|
+
|
|
17
|
+
label: str | TranslateText | None = None
|
|
18
|
+
help_text: str | TranslateText | None = None
|
|
19
|
+
|
|
20
|
+
def generate_schema(self, user, field_slug, language_manager: LanguageManager) -> FieldSchemaData:
|
|
21
|
+
schema = FieldSchemaData(
|
|
22
|
+
type=self._type,
|
|
23
|
+
label=language_manager.get_text(self.label) or humanize_field_name(field_slug),
|
|
24
|
+
help_text=language_manager.get_text(self.help_text),
|
|
25
|
+
header=self.header,
|
|
26
|
+
read_only=self.read_only,
|
|
27
|
+
default=self.default,
|
|
28
|
+
required=self.required,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
return schema
|
|
32
|
+
|
|
33
|
+
async def serialize(self, value, extra: dict, *args, **kwargs) -> Any:
|
|
34
|
+
return value
|
|
35
|
+
|
|
36
|
+
async def deserialize(self, value, action: DeserializeAction, extra: dict, *args, **kwargs) -> Any:
|
|
37
|
+
if self.required and value is None:
|
|
38
|
+
raise FieldError('Field is required', 'field_required')
|
|
39
|
+
|
|
40
|
+
return value
|
|
41
|
+
|
|
42
|
+
async def autocomplete(self, model, data, user):
|
|
43
|
+
raise NotImplementedError('autocomplete is not implemented')
|
|
44
|
+
|
|
45
|
+
# pylint: disable=too-many-arguments
|
|
46
|
+
# pylint: disable=too-many-positional-arguments
|
|
47
|
+
def set_deserialized_value(self, result: dict, field_slug, deserialized_value, action, extra):
|
|
48
|
+
result[field_slug] = deserialized_value
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass
|
|
52
|
+
class IntegerField(TableField):
|
|
53
|
+
_type = 'integer'
|
|
54
|
+
|
|
55
|
+
max_value: int | None = None
|
|
56
|
+
min_value: int | None = None
|
|
57
|
+
|
|
58
|
+
inputmode: str | None = None
|
|
59
|
+
precision: int | None = None
|
|
60
|
+
scale: int | None = None
|
|
61
|
+
|
|
62
|
+
def generate_schema(self, user, field_slug, language_manager: LanguageManager) -> FieldSchemaData:
|
|
63
|
+
schema = super().generate_schema(user, field_slug, language_manager)
|
|
64
|
+
|
|
65
|
+
schema.choices = self.choices
|
|
66
|
+
|
|
67
|
+
if self.max_value is not None:
|
|
68
|
+
schema.max_value = self.max_value
|
|
69
|
+
|
|
70
|
+
if self.min_value is not None:
|
|
71
|
+
schema.min_value = self.min_value
|
|
72
|
+
|
|
73
|
+
schema.inputmode = self.inputmode
|
|
74
|
+
schema.precision = self.precision
|
|
75
|
+
schema.scale = self.scale
|
|
76
|
+
|
|
77
|
+
return schema
|
|
78
|
+
|
|
79
|
+
async def deserialize(self, value, action: DeserializeAction, extra: dict, *args, **kwargs) -> Any:
|
|
80
|
+
value = await super().deserialize(value, action, extra, *args, **kwargs)
|
|
81
|
+
if value and not isinstance(value, int):
|
|
82
|
+
raise FieldError(f'bad int type: {type(value)}')
|
|
83
|
+
|
|
84
|
+
return value
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@dataclass
|
|
88
|
+
class StringField(TableField):
|
|
89
|
+
_type = 'string'
|
|
90
|
+
|
|
91
|
+
multilined: bool | None = None
|
|
92
|
+
ckeditor: bool | None = None
|
|
93
|
+
tinymce: bool | None = None
|
|
94
|
+
|
|
95
|
+
min_length: int | None = None
|
|
96
|
+
max_length: int | None = None
|
|
97
|
+
|
|
98
|
+
choices: List[Tuple[str, str]] | None = None
|
|
99
|
+
|
|
100
|
+
def generate_schema(self, user, field_slug, language_manager: LanguageManager) -> FieldSchemaData:
|
|
101
|
+
schema = super().generate_schema(user, field_slug, language_manager)
|
|
102
|
+
|
|
103
|
+
schema.multilined = self.multilined
|
|
104
|
+
schema.choices = self.choices
|
|
105
|
+
schema.ckeditor = self.ckeditor
|
|
106
|
+
schema.tinymce = self.tinymce
|
|
107
|
+
|
|
108
|
+
if self.min_length is not None:
|
|
109
|
+
schema.min_length = self.min_length
|
|
110
|
+
|
|
111
|
+
if self.max_length is not None:
|
|
112
|
+
schema.max_length = self.max_length
|
|
113
|
+
|
|
114
|
+
return schema
|
|
115
|
+
|
|
116
|
+
async def deserialize(self, value, action: DeserializeAction, extra: dict, *args, **kwargs) -> Any:
|
|
117
|
+
value = await super().deserialize(value, action, extra, *args, **kwargs)
|
|
118
|
+
if value and not isinstance(value, str):
|
|
119
|
+
raise FieldError(f'bad string type: {type(value)}')
|
|
120
|
+
|
|
121
|
+
return value
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@dataclass
|
|
125
|
+
class BooleanField(TableField):
|
|
126
|
+
_type = 'boolean'
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
@dataclass
|
|
130
|
+
class DateTimeField(TableField):
|
|
131
|
+
_type = 'datetime'
|
|
132
|
+
|
|
133
|
+
format: str = '%Y-%m-%dT%H:%M:%S'
|
|
134
|
+
range: bool | None = None
|
|
135
|
+
include_date: bool | None = True
|
|
136
|
+
include_time: bool | None = True
|
|
137
|
+
|
|
138
|
+
def generate_schema(self, user, field_slug, language_manager: LanguageManager) -> FieldSchemaData:
|
|
139
|
+
schema = super().generate_schema(user, field_slug, language_manager)
|
|
140
|
+
|
|
141
|
+
schema.range = self.range
|
|
142
|
+
schema.include_date = self.include_date
|
|
143
|
+
schema.include_time = self.include_time
|
|
144
|
+
|
|
145
|
+
return schema
|
|
146
|
+
|
|
147
|
+
async def deserialize(self, value, action: DeserializeAction, extra: dict, *args, **kwargs) -> Any:
|
|
148
|
+
value = await super().deserialize(value, action, extra, *args, **kwargs)
|
|
149
|
+
|
|
150
|
+
if not value:
|
|
151
|
+
return
|
|
152
|
+
|
|
153
|
+
if value and not isinstance(value, (str, dict)):
|
|
154
|
+
raise FieldError(f'bad datetime type: {type(value)}')
|
|
155
|
+
|
|
156
|
+
if isinstance(value, str):
|
|
157
|
+
return datetime.datetime.strptime(value, self.format)
|
|
158
|
+
|
|
159
|
+
if isinstance(value, dict):
|
|
160
|
+
if not value.get('from') or not value.get('to'):
|
|
161
|
+
msg = f'{type(self).__name__} value must be dict with from,to values: {value}'
|
|
162
|
+
raise FieldError(msg)
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
'from': datetime.datetime.strptime(value.get('from'), self.format),
|
|
166
|
+
'to': datetime.datetime.strptime(value.get('to'), self.format),
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
raise NotImplementedError(f'Value "{value}" is not supporetd for datetime')
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
@dataclass
|
|
173
|
+
class JSONField(TableField):
|
|
174
|
+
_type = 'json'
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
@dataclass
|
|
178
|
+
class ArrayField(TableField):
|
|
179
|
+
_type = 'array'
|
|
180
|
+
|
|
181
|
+
array_type: str | None
|
|
182
|
+
|
|
183
|
+
def generate_schema(self, user, field_slug, language_manager: LanguageManager) -> FieldSchemaData:
|
|
184
|
+
schema = super().generate_schema(user, field_slug, language_manager)
|
|
185
|
+
|
|
186
|
+
schema.array_type = self.array_type
|
|
187
|
+
|
|
188
|
+
return schema
|
|
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 and not isinstance(value, list):
|
|
194
|
+
raise FieldError(f'bad array type: {type(value)}')
|
|
195
|
+
|
|
196
|
+
return value
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
@dataclass
|
|
200
|
+
class FileField(TableField):
|
|
201
|
+
_type = 'file'
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
@dataclass
|
|
205
|
+
class ImageField(TableField):
|
|
206
|
+
_type = 'image'
|
|
207
|
+
|
|
208
|
+
preview_max_height: int = 100
|
|
209
|
+
preview_max_width: int = 100
|
|
210
|
+
|
|
211
|
+
def generate_schema(self, user, field_slug, language_manager: LanguageManager) -> FieldSchemaData:
|
|
212
|
+
schema = super().generate_schema(user, field_slug, language_manager)
|
|
213
|
+
|
|
214
|
+
if self.preview_max_height is not None:
|
|
215
|
+
schema.preview_max_height = self.preview_max_height
|
|
216
|
+
|
|
217
|
+
if self.preview_max_width is not None:
|
|
218
|
+
schema.preview_max_width = self.preview_max_width
|
|
219
|
+
|
|
220
|
+
return schema
|
|
221
|
+
|
|
222
|
+
async def serialize(self, value, extra: dict, *args, **kwargs) -> Any:
|
|
223
|
+
return {'url': value}
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
@dataclass
|
|
227
|
+
class ChoiceField(TableField):
|
|
228
|
+
_type = 'choice'
|
|
229
|
+
|
|
230
|
+
# https://vuetifyjs.com/en/styles/colors/#classes
|
|
231
|
+
tag_colors: dict | None = None
|
|
232
|
+
|
|
233
|
+
# https://vuetifyjs.com/en/components/chips/#color-and-variants
|
|
234
|
+
variant: str = 'elevated'
|
|
235
|
+
size: str = 'default'
|
|
236
|
+
|
|
237
|
+
def generate_schema(self, user, field_slug, language_manager: LanguageManager) -> FieldSchemaData:
|
|
238
|
+
schema = super().generate_schema(user, field_slug, language_manager)
|
|
239
|
+
|
|
240
|
+
schema.choices = self.choices
|
|
241
|
+
|
|
242
|
+
schema.tag_colors = self.tag_colors
|
|
243
|
+
schema.size = self.size
|
|
244
|
+
schema.variant = self.variant
|
|
245
|
+
|
|
246
|
+
return schema
|
|
247
|
+
|
|
248
|
+
async def serialize(self, value, extra: dict, *args, **kwargs) -> Any:
|
|
249
|
+
return {'value': value, 'title': value.capitalize() if value else value}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import functools
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from pydantic.dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
from admin_panel.exceptions import AdminAPIException, APIError
|
|
8
|
+
from admin_panel.schema.table.fields.base import TableField
|
|
9
|
+
from admin_panel.utils import get_logger
|
|
10
|
+
|
|
11
|
+
logger = get_logger()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def function_field(**kwargs):
|
|
15
|
+
'''
|
|
16
|
+
The same as decaring:
|
|
17
|
+
field = FunctionField(fn=attribute)
|
|
18
|
+
|
|
19
|
+
but available directly and converted after to the FunctionField
|
|
20
|
+
'''
|
|
21
|
+
def wrapper(func):
|
|
22
|
+
func.__function_field__ = True
|
|
23
|
+
|
|
24
|
+
field_type = kwargs.pop('type', None)
|
|
25
|
+
if field_type:
|
|
26
|
+
# pylint: disable=protected-access
|
|
27
|
+
kwargs['_type'] = field_type._type
|
|
28
|
+
|
|
29
|
+
func.__kwargs__ = kwargs
|
|
30
|
+
|
|
31
|
+
@functools.wraps(func)
|
|
32
|
+
async def wrapped(*args, **kwargs):
|
|
33
|
+
return await func(*args, **kwargs)
|
|
34
|
+
|
|
35
|
+
return wrapped
|
|
36
|
+
|
|
37
|
+
return wrapper
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass
|
|
41
|
+
class FunctionField(TableField):
|
|
42
|
+
_type: str = 'function_field'
|
|
43
|
+
read_only = True
|
|
44
|
+
|
|
45
|
+
fn: Any = None
|
|
46
|
+
|
|
47
|
+
def __post_init__(self):
|
|
48
|
+
if not asyncio.iscoroutinefunction(self.fn):
|
|
49
|
+
msg = f'{type(self).__name__}.fn {self.fn} must be coroutine function'
|
|
50
|
+
raise AttributeError(msg)
|
|
51
|
+
|
|
52
|
+
async def serialize(self, value, extra: dict, *args, **kwargs) -> Any:
|
|
53
|
+
try:
|
|
54
|
+
return await self.fn(**extra)
|
|
55
|
+
except Exception as e:
|
|
56
|
+
logger.exception(
|
|
57
|
+
'Function field %s label=%s error from function="%s": %s',
|
|
58
|
+
type(self).__name__,
|
|
59
|
+
self.label,
|
|
60
|
+
self.fn,
|
|
61
|
+
e,
|
|
62
|
+
)
|
|
63
|
+
raise AdminAPIException(
|
|
64
|
+
APIError(message=f'Error: {e}', code='function_field_error'), status_code=400,
|
|
65
|
+
) from e
|