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,216 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from typing import Any, ClassVar, Dict, List
|
|
3
|
+
|
|
4
|
+
from pydantic_core import core_schema
|
|
5
|
+
|
|
6
|
+
from admin_panel.auth import UserABC
|
|
7
|
+
from admin_panel.exceptions import AdminAPIException, APIError, FieldError
|
|
8
|
+
from admin_panel.schema.category import FieldSchemaData, FieldsSchemaData
|
|
9
|
+
from admin_panel.schema.table.fields.base import TableField
|
|
10
|
+
from admin_panel.schema.table.fields.function_field import FunctionField
|
|
11
|
+
from admin_panel.translations import LanguageManager
|
|
12
|
+
from admin_panel.utils import DeserializeAction
|
|
13
|
+
|
|
14
|
+
NOT_FUND_EXCEPTION = '''Field slug "{field_slug}" not found inside generated fields inside {class_name}
|
|
15
|
+
Available options: {available_fields}
|
|
16
|
+
'''
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class DeserializeError(Exception):
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class FieldsSchema:
|
|
24
|
+
# Список полей
|
|
25
|
+
fields: List[str] | None = None
|
|
26
|
+
|
|
27
|
+
# Список колонок, которые будут отображаться в таблице
|
|
28
|
+
list_display: List[str] | None = None
|
|
29
|
+
|
|
30
|
+
# Для передачи параметра read_only = True внутрь поля
|
|
31
|
+
readonly_fields: ClassVar[List | None] = None
|
|
32
|
+
|
|
33
|
+
# Generated fields
|
|
34
|
+
_generated_fields: dict = None
|
|
35
|
+
|
|
36
|
+
def __init__(self, *args, table_schema=None, list_display=None, readonly_fields=None, fields=None, **kwargs):
|
|
37
|
+
if fields:
|
|
38
|
+
self.fields = fields
|
|
39
|
+
|
|
40
|
+
if self.fields and not isinstance(self.fields, list):
|
|
41
|
+
msg = f'{type(self).__name__}.fields must be a list instance; found: {self.fields}'
|
|
42
|
+
raise AttributeError(msg)
|
|
43
|
+
|
|
44
|
+
if table_schema:
|
|
45
|
+
self.table_schema = table_schema
|
|
46
|
+
|
|
47
|
+
if list_display:
|
|
48
|
+
self.list_display = list_display
|
|
49
|
+
|
|
50
|
+
if readonly_fields:
|
|
51
|
+
self.readonly_fields = readonly_fields
|
|
52
|
+
|
|
53
|
+
generated_fields = self.generate_fields(kwargs)
|
|
54
|
+
|
|
55
|
+
available_fields = list(generated_fields.keys())
|
|
56
|
+
if self.fields is None:
|
|
57
|
+
self.fields = available_fields
|
|
58
|
+
|
|
59
|
+
self._generated_fields = {}
|
|
60
|
+
for field_slug in self.fields:
|
|
61
|
+
if not isinstance(field_slug, str):
|
|
62
|
+
msg = f'{type(self).__name__} field "{field_slug}" must be string'
|
|
63
|
+
raise AttributeError(msg)
|
|
64
|
+
|
|
65
|
+
if field_slug not in generated_fields:
|
|
66
|
+
msg = NOT_FUND_EXCEPTION.format(
|
|
67
|
+
field_slug=field_slug,
|
|
68
|
+
available_fields=available_fields,
|
|
69
|
+
class_name=type(self).__name__,
|
|
70
|
+
)
|
|
71
|
+
raise AttributeError(msg)
|
|
72
|
+
|
|
73
|
+
self._generated_fields[field_slug] = generated_fields[field_slug]
|
|
74
|
+
|
|
75
|
+
self.validate_fields(*args, **kwargs)
|
|
76
|
+
|
|
77
|
+
def validate_fields(self, *args, **kwargs):
|
|
78
|
+
if not self.fields:
|
|
79
|
+
msg = f'Schema {type(self).__name__}.fields is empty'
|
|
80
|
+
raise AttributeError(msg)
|
|
81
|
+
|
|
82
|
+
# Check for fields not listed in self.fields
|
|
83
|
+
for attribute_name in dir(self):
|
|
84
|
+
if '__' in attribute_name:
|
|
85
|
+
continue
|
|
86
|
+
|
|
87
|
+
attribute = getattr(self, attribute_name)
|
|
88
|
+
if issubclass(attribute.__class__, TableField) and attribute_name not in self.fields:
|
|
89
|
+
msg = f'Schema {type(self).__name__} attribute "{attribute_name}" {type(attribute).__name__} presented, but not listed inside fields list: {self.fields}'
|
|
90
|
+
raise AttributeError(msg)
|
|
91
|
+
|
|
92
|
+
if self.readonly_fields:
|
|
93
|
+
for field_slug in self.readonly_fields:
|
|
94
|
+
field = self.get_field(field_slug)
|
|
95
|
+
if not field:
|
|
96
|
+
msg = f'{type(self).__name__} field "{field_slug}" from readonly_fields is not found inside fields; available options: {self.fields}'
|
|
97
|
+
raise AttributeError(msg)
|
|
98
|
+
|
|
99
|
+
field.read_only = True
|
|
100
|
+
|
|
101
|
+
# Fill list_display
|
|
102
|
+
if self.list_display is None:
|
|
103
|
+
self.list_display = self.fields
|
|
104
|
+
|
|
105
|
+
for field_slug in self.list_display:
|
|
106
|
+
if field_slug not in self.fields:
|
|
107
|
+
msg = f'Field "{field_slug}" inside {type(self).__name__}.list_display, but not presented as field; available options: {self.fields}'
|
|
108
|
+
raise AttributeError(msg)
|
|
109
|
+
|
|
110
|
+
def generate_fields(self, kwargs) -> dict:
|
|
111
|
+
generated_fields = {}
|
|
112
|
+
|
|
113
|
+
# Fields from kwargs
|
|
114
|
+
for k, v in kwargs.items():
|
|
115
|
+
if issubclass(type(v), TableField) and not hasattr(self, k):
|
|
116
|
+
generated_fields[k] = v
|
|
117
|
+
|
|
118
|
+
# Autogenerate fields from instance attributes
|
|
119
|
+
for attribute_name in dir(self):
|
|
120
|
+
if '__' in attribute_name:
|
|
121
|
+
continue
|
|
122
|
+
|
|
123
|
+
attribute = getattr(self, attribute_name)
|
|
124
|
+
if issubclass(type(attribute), TableField):
|
|
125
|
+
generated_fields[attribute_name] = attribute
|
|
126
|
+
|
|
127
|
+
# Generation FunctionField
|
|
128
|
+
for attribute_name in dir(self):
|
|
129
|
+
if '__' in attribute_name:
|
|
130
|
+
continue
|
|
131
|
+
|
|
132
|
+
attribute = getattr(self, attribute_name)
|
|
133
|
+
if getattr(attribute, '__function_field__', False):
|
|
134
|
+
field = FunctionField(fn=attribute, **attribute.__kwargs__)
|
|
135
|
+
field.read_only = True
|
|
136
|
+
generated_fields[attribute_name] = field
|
|
137
|
+
|
|
138
|
+
return generated_fields
|
|
139
|
+
|
|
140
|
+
def get_field(self, field_slug) -> TableField | None:
|
|
141
|
+
return self.get_fields().get(field_slug)
|
|
142
|
+
|
|
143
|
+
def get_fields(self) -> Dict[str, TableField]:
|
|
144
|
+
return self._generated_fields
|
|
145
|
+
|
|
146
|
+
def generate_schema(self, user: UserABC, language_manager: LanguageManager) -> FieldsSchemaData:
|
|
147
|
+
fields_schema = FieldsSchemaData(
|
|
148
|
+
list_display=self.list_display,
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
for field_slug, field in self.get_fields().items():
|
|
152
|
+
field_schema: FieldSchemaData = field.generate_schema(user, field_slug, language_manager)
|
|
153
|
+
fields_schema.fields[field_slug] = field_schema.to_dict(keep_none=False)
|
|
154
|
+
|
|
155
|
+
return fields_schema
|
|
156
|
+
|
|
157
|
+
async def serialize(self, data: Any, extra: dict) -> dict:
|
|
158
|
+
result = {}
|
|
159
|
+
for field_slug, field in self.get_fields().items():
|
|
160
|
+
value = data.get(field_slug)
|
|
161
|
+
result[field_slug] = await field.serialize(value, extra)
|
|
162
|
+
return result
|
|
163
|
+
|
|
164
|
+
async def deserialize(self, data: dict, action: DeserializeAction, extra) -> dict:
|
|
165
|
+
result = {}
|
|
166
|
+
errors = {}
|
|
167
|
+
for field_slug, field in self.get_fields().items():
|
|
168
|
+
|
|
169
|
+
if field.read_only and action != DeserializeAction.FILTERS:
|
|
170
|
+
continue
|
|
171
|
+
|
|
172
|
+
# Skip update if fields is not presented in data
|
|
173
|
+
if action in [DeserializeAction.UPDATE, DeserializeAction.FILTERS] and field_slug not in data:
|
|
174
|
+
continue
|
|
175
|
+
|
|
176
|
+
value = data.get(field_slug)
|
|
177
|
+
try:
|
|
178
|
+
deserialized_value = await field.deserialize(value, action, extra)
|
|
179
|
+
|
|
180
|
+
validate_method = getattr(self, f'validate_{field_slug}', None)
|
|
181
|
+
if callable(validate_method):
|
|
182
|
+
if not asyncio.iscoroutinefunction(validate_method):
|
|
183
|
+
msg = f'Validate method {type(self).__name__}.{field_slug} must be async'
|
|
184
|
+
raise AttributeError(msg)
|
|
185
|
+
deserialized_value = await validate_method(value)
|
|
186
|
+
|
|
187
|
+
field.set_deserialized_value(result, field_slug, deserialized_value, action, extra)
|
|
188
|
+
except FieldError as e:
|
|
189
|
+
errors[field_slug] = e
|
|
190
|
+
|
|
191
|
+
if errors:
|
|
192
|
+
raise AdminAPIException(
|
|
193
|
+
APIError(
|
|
194
|
+
message='Validation error',
|
|
195
|
+
code='validation_error',
|
|
196
|
+
field_errors=errors,
|
|
197
|
+
),
|
|
198
|
+
status_code=400,
|
|
199
|
+
)
|
|
200
|
+
return result
|
|
201
|
+
|
|
202
|
+
@classmethod
|
|
203
|
+
def __get_pydantic_core_schema__(cls, source_type: Any, handler: Any) -> core_schema.CoreSchema:
|
|
204
|
+
def validate(v: Any) -> "FieldsSchema":
|
|
205
|
+
if isinstance(v, cls):
|
|
206
|
+
return v
|
|
207
|
+
raise TypeError(f"Expected {cls.__name__} instance")
|
|
208
|
+
|
|
209
|
+
return core_schema.no_info_plain_validator_function(
|
|
210
|
+
validate,
|
|
211
|
+
serialization=core_schema.plain_serializer_function_ser_schema(
|
|
212
|
+
lambda v: repr(v),
|
|
213
|
+
info_arg=False,
|
|
214
|
+
return_schema=core_schema.str_schema(),
|
|
215
|
+
),
|
|
216
|
+
)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from typing import Any, Dict, List
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, Field
|
|
4
|
+
from pydantic.dataclasses import dataclass
|
|
5
|
+
|
|
6
|
+
from admin_panel.utils import DataclassBase
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class TableListResult(DataclassBase):
|
|
11
|
+
data: List[dict]
|
|
12
|
+
total_count: int
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class AutocompleteData(BaseModel):
|
|
16
|
+
field_slug: str
|
|
17
|
+
search_string: str = ''
|
|
18
|
+
is_filter: bool = False
|
|
19
|
+
form_data: dict = Field(default_factory=dict)
|
|
20
|
+
existed_choices: List[Any] = Field(default_factory=list)
|
|
21
|
+
action_name: str | None = None
|
|
22
|
+
limit: int = 25
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class Record(BaseModel):
|
|
26
|
+
key: Any
|
|
27
|
+
title: str
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class AutocompleteResult(BaseModel):
|
|
31
|
+
results: List[Record] = Field(default_factory=list)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class ListData(BaseModel):
|
|
35
|
+
page: int = 1
|
|
36
|
+
limit: int = 25
|
|
37
|
+
|
|
38
|
+
search: str | None = None
|
|
39
|
+
filters: Dict[str, Any] = Field(default_factory=dict)
|
|
40
|
+
|
|
41
|
+
ordering: str | None = None
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class RetrieveResult(BaseModel):
|
|
45
|
+
data: dict
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class CreateResult(BaseModel):
|
|
49
|
+
pk: Any
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class UpdateResult(BaseModel):
|
|
53
|
+
pk: Any
|
|
Binary file
|