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,191 @@
|
|
|
1
|
+
import importlib.metadata
|
|
2
|
+
import json
|
|
3
|
+
from importlib import resources
|
|
4
|
+
from typing import Any, Dict, List, Optional, Type
|
|
5
|
+
from urllib.parse import urljoin
|
|
6
|
+
|
|
7
|
+
from fastapi import FastAPI, Request
|
|
8
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
9
|
+
from fastapi.staticfiles import StaticFiles
|
|
10
|
+
from pydantic.dataclasses import dataclass
|
|
11
|
+
|
|
12
|
+
from admin_panel.auth import UserABC
|
|
13
|
+
from admin_panel.docs import build_redoc_docs, build_scalar_docs
|
|
14
|
+
from admin_panel.schema.group import Group, GroupSchemaData
|
|
15
|
+
from admin_panel.translations import LanguageManager, TranslateText
|
|
16
|
+
from admin_panel.utils import DataclassBase
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class AdminSchemaData(DataclassBase):
|
|
21
|
+
groups: Dict[str, GroupSchemaData]
|
|
22
|
+
profile: UserABC | Any
|
|
23
|
+
|
|
24
|
+
def __post_init__(self):
|
|
25
|
+
if not isinstance(self.profile, UserABC):
|
|
26
|
+
self.profile = UserABC(username=self.profile.username)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# pylint: disable=too-many-instance-attributes
|
|
30
|
+
@dataclass
|
|
31
|
+
class AdminSettingsData(DataclassBase):
|
|
32
|
+
title: str | TranslateText
|
|
33
|
+
description: str | TranslateText | None
|
|
34
|
+
login_greetings_message: str | TranslateText | None
|
|
35
|
+
navbar_density: str
|
|
36
|
+
languages: Dict[str, str] | None
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass
|
|
40
|
+
class AdminIndexContextData(DataclassBase):
|
|
41
|
+
title: str
|
|
42
|
+
favicon_image: str
|
|
43
|
+
settings_json: str
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass
|
|
47
|
+
class AdminSchema:
|
|
48
|
+
groups: List[Group]
|
|
49
|
+
auth: Any
|
|
50
|
+
|
|
51
|
+
title: str | TranslateText | None = 'Admin'
|
|
52
|
+
description: str | TranslateText | None = None
|
|
53
|
+
login_greetings_message: str | TranslateText | None = None
|
|
54
|
+
|
|
55
|
+
logo_image: str | None = None
|
|
56
|
+
favicon_image: str = '/admin/static/favicon.jpg'
|
|
57
|
+
|
|
58
|
+
navbar_density: str = 'default'
|
|
59
|
+
|
|
60
|
+
backend_prefix = None
|
|
61
|
+
static_prefix = None
|
|
62
|
+
|
|
63
|
+
language_manager_class: Type[LanguageManager] = LanguageManager
|
|
64
|
+
|
|
65
|
+
def __post_init__(self):
|
|
66
|
+
for group in self.groups:
|
|
67
|
+
if not issubclass(group.__class__, Group):
|
|
68
|
+
raise TypeError(f'Group "{group}" is not instance of Group subclass')
|
|
69
|
+
|
|
70
|
+
def get_language_manager(self, language_slug: str | None) -> LanguageManager:
|
|
71
|
+
return self.language_manager_class(language_slug)
|
|
72
|
+
|
|
73
|
+
def generate_schema(self, user: UserABC, language_slug: str | None) -> AdminSchemaData:
|
|
74
|
+
language_manager: LanguageManager = self.get_language_manager(language_slug)
|
|
75
|
+
|
|
76
|
+
groups = {}
|
|
77
|
+
|
|
78
|
+
for group in self.groups:
|
|
79
|
+
if not group.slug:
|
|
80
|
+
msg = f'Category group {type(group).__name__}.slug is empty'
|
|
81
|
+
raise AttributeError(msg)
|
|
82
|
+
|
|
83
|
+
groups[group.slug] = group.generate_schema(user, language_manager)
|
|
84
|
+
|
|
85
|
+
return AdminSchemaData(
|
|
86
|
+
groups=groups,
|
|
87
|
+
profile=user,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
def get_group(self, group_slug: str) -> Optional[Group]:
|
|
91
|
+
for group in self.groups:
|
|
92
|
+
if group.slug == group_slug:
|
|
93
|
+
return group
|
|
94
|
+
|
|
95
|
+
return None
|
|
96
|
+
|
|
97
|
+
async def get_settings(self, request: Request) -> AdminSettingsData:
|
|
98
|
+
language_slug = request.headers.get('Accept-Language')
|
|
99
|
+
language_manager: LanguageManager = self.get_language_manager(language_slug)
|
|
100
|
+
|
|
101
|
+
languages = None
|
|
102
|
+
if language_manager.languages:
|
|
103
|
+
languages = {}
|
|
104
|
+
for k, v in language_manager.languages.items():
|
|
105
|
+
languages[k] = language_manager.get_text(v)
|
|
106
|
+
|
|
107
|
+
return AdminSettingsData(
|
|
108
|
+
title=self.title,
|
|
109
|
+
description=self.description,
|
|
110
|
+
login_greetings_message=self.login_greetings_message,
|
|
111
|
+
navbar_density=self.navbar_density,
|
|
112
|
+
languages=languages,
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
# pylint: disable=too-many-arguments
|
|
116
|
+
# pylint: disable=too-many-positional-arguments
|
|
117
|
+
def generate_app(
|
|
118
|
+
self,
|
|
119
|
+
debug=False,
|
|
120
|
+
allow_cors=True,
|
|
121
|
+
|
|
122
|
+
include_scalar=False,
|
|
123
|
+
include_docs=False,
|
|
124
|
+
include_redoc=False,
|
|
125
|
+
) -> FastAPI:
|
|
126
|
+
# pylint: disable=unused-variable
|
|
127
|
+
language_manager = self.get_language_manager(language_slug=None)
|
|
128
|
+
|
|
129
|
+
app = FastAPI(
|
|
130
|
+
title=language_manager.get_text(self.title),
|
|
131
|
+
description=language_manager.get_text(self.description),
|
|
132
|
+
debug=debug,
|
|
133
|
+
docs_url='/docs' if include_docs else None,
|
|
134
|
+
redoc_url=None,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
if allow_cors:
|
|
138
|
+
app.add_middleware(
|
|
139
|
+
CORSMiddleware,
|
|
140
|
+
allow_origins=["*"],
|
|
141
|
+
allow_credentials=True,
|
|
142
|
+
allow_methods=["*"],
|
|
143
|
+
allow_headers=["*"]
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
static_dir = resources.files("admin_panel").joinpath("static")
|
|
147
|
+
app.mount("/static", StaticFiles(directory=str(static_dir)), name="static")
|
|
148
|
+
|
|
149
|
+
app.state.schema = self
|
|
150
|
+
|
|
151
|
+
if include_scalar:
|
|
152
|
+
app.include_router(build_scalar_docs(app))
|
|
153
|
+
|
|
154
|
+
if include_redoc:
|
|
155
|
+
app.include_router(build_redoc_docs(app, redoc_url='/redoc'))
|
|
156
|
+
|
|
157
|
+
# pylint: disable=import-outside-toplevel
|
|
158
|
+
from admin_panel.api.routers import admin_panel_router
|
|
159
|
+
app.include_router(admin_panel_router)
|
|
160
|
+
|
|
161
|
+
return app
|
|
162
|
+
|
|
163
|
+
async def get_index_context_data(self, request: Request) -> dict:
|
|
164
|
+
language_manager = self.get_language_manager(language_slug=None)
|
|
165
|
+
context = {'language_manager': language_manager}
|
|
166
|
+
|
|
167
|
+
backend_prefix = self.backend_prefix
|
|
168
|
+
if not backend_prefix:
|
|
169
|
+
backend_prefix = urljoin(str(request.base_url), '/admin/')
|
|
170
|
+
|
|
171
|
+
static_prefix = self.static_prefix
|
|
172
|
+
if not static_prefix:
|
|
173
|
+
static_prefix = urljoin(str(request.base_url), '/admin/static/')
|
|
174
|
+
|
|
175
|
+
logo_image = self.logo_image
|
|
176
|
+
if logo_image and logo_image.startswith('/'):
|
|
177
|
+
logo_image = urljoin(str(request.base_url), logo_image)
|
|
178
|
+
|
|
179
|
+
settings_json = {
|
|
180
|
+
'backend_prefix': backend_prefix,
|
|
181
|
+
'static_prefix': static_prefix,
|
|
182
|
+
'version': importlib.metadata.version('brilliance-admin'),
|
|
183
|
+
'api_timeout_ms': 1000 * 5,
|
|
184
|
+
'logo_image': logo_image,
|
|
185
|
+
}
|
|
186
|
+
data = AdminIndexContextData(
|
|
187
|
+
title=str(self.title),
|
|
188
|
+
favicon_image=self.favicon_image,
|
|
189
|
+
settings_json=json.dumps(settings_json),
|
|
190
|
+
)
|
|
191
|
+
return data.model_dump(mode='json', context=context)
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
from typing import Any, ClassVar, Dict, List
|
|
3
|
+
|
|
4
|
+
from pydantic import Field
|
|
5
|
+
from pydantic.dataclasses import dataclass
|
|
6
|
+
from pydantic_core import core_schema
|
|
7
|
+
|
|
8
|
+
from admin_panel.auth import UserABC
|
|
9
|
+
from admin_panel.translations import LanguageManager, TranslateText
|
|
10
|
+
from admin_panel.utils import DataclassBase
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# pylint: disable=too-many-instance-attributes
|
|
14
|
+
@dataclass
|
|
15
|
+
class FieldSchemaData(DataclassBase):
|
|
16
|
+
type: str = None
|
|
17
|
+
|
|
18
|
+
label: str | None = None
|
|
19
|
+
help_text: str | None = None
|
|
20
|
+
|
|
21
|
+
# Table header parameters
|
|
22
|
+
header: dict = Field(default_factory=dict)
|
|
23
|
+
|
|
24
|
+
read_only: bool = False
|
|
25
|
+
default: Any | None = None
|
|
26
|
+
required: bool = False
|
|
27
|
+
|
|
28
|
+
max_length: int | None = None
|
|
29
|
+
min_length: int | None = None
|
|
30
|
+
|
|
31
|
+
choices: List[dict] | None = None
|
|
32
|
+
|
|
33
|
+
tag_colors: dict | None = None
|
|
34
|
+
variant: str | None = None
|
|
35
|
+
size: str | None = None
|
|
36
|
+
|
|
37
|
+
preview_max_height: int | None = None
|
|
38
|
+
preview_max_width: int | None = None
|
|
39
|
+
|
|
40
|
+
# StringField
|
|
41
|
+
multilined: bool | None = None
|
|
42
|
+
ckeditor: bool | None = None
|
|
43
|
+
tinymce: bool | None = None
|
|
44
|
+
|
|
45
|
+
# ArrayField
|
|
46
|
+
array_type: str | None = None
|
|
47
|
+
|
|
48
|
+
# SQLAlchemyRelatedField
|
|
49
|
+
many: bool | None = None
|
|
50
|
+
rel_name: str | None = None
|
|
51
|
+
dual_list: bool | None = None
|
|
52
|
+
|
|
53
|
+
# IntegerField
|
|
54
|
+
inputmode: str | None = None
|
|
55
|
+
precision: int | None = None
|
|
56
|
+
scale: int | None = None
|
|
57
|
+
|
|
58
|
+
# DateTimeField
|
|
59
|
+
range: bool | None = None
|
|
60
|
+
include_date: bool | None = None
|
|
61
|
+
include_time: bool | None = None
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@dataclass
|
|
65
|
+
class FieldsSchemaData(DataclassBase):
|
|
66
|
+
fields: Dict[str, dict] = Field(default_factory=dict)
|
|
67
|
+
list_display: List[str] = Field(default_factory=list)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
# pylint: disable=too-many-instance-attributes
|
|
71
|
+
@dataclass
|
|
72
|
+
class TableInfoSchemaData(DataclassBase):
|
|
73
|
+
table_schema: FieldsSchemaData
|
|
74
|
+
|
|
75
|
+
search_enabled: bool = Field(default=False)
|
|
76
|
+
search_help: str | None = Field(default=None)
|
|
77
|
+
|
|
78
|
+
pk_name: str | None = Field(default=None)
|
|
79
|
+
can_retrieve: bool = Field(default=False)
|
|
80
|
+
|
|
81
|
+
can_create: bool = Field(default=False)
|
|
82
|
+
can_update: bool = Field(default=False)
|
|
83
|
+
|
|
84
|
+
table_filters: FieldsSchemaData | None = Field(default=None)
|
|
85
|
+
|
|
86
|
+
ordering_fields: List[str] = Field(default_factory=list)
|
|
87
|
+
default_ordering: str | None = None
|
|
88
|
+
|
|
89
|
+
actions: Dict[str, dict] | None = Field(default_factory=dict)
|
|
90
|
+
|
|
91
|
+
def __repr__(self):
|
|
92
|
+
return f'<TableInfoSchemaData id={id(self)}>'
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@dataclass
|
|
96
|
+
class GraphInfoSchemaData(DataclassBase):
|
|
97
|
+
search_enabled: bool
|
|
98
|
+
search_help: str | None
|
|
99
|
+
|
|
100
|
+
table_filters: FieldsSchemaData | None = None
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@dataclass
|
|
104
|
+
class CategorySchemaData(DataclassBase):
|
|
105
|
+
title: str | None
|
|
106
|
+
description: str | None
|
|
107
|
+
icon: str | None
|
|
108
|
+
type: str
|
|
109
|
+
|
|
110
|
+
table_info: TableInfoSchemaData | None = None
|
|
111
|
+
graph_info: GraphInfoSchemaData | None = None
|
|
112
|
+
|
|
113
|
+
def __repr__(self):
|
|
114
|
+
return f'<CategorySchemaData type={self.type} "{self.title}">'
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class Category(abc.ABC):
|
|
118
|
+
slug: ClassVar[str]
|
|
119
|
+
title: ClassVar[str | TranslateText | None] = None
|
|
120
|
+
description: ClassVar[str | TranslateText | None] = None
|
|
121
|
+
|
|
122
|
+
# https://pictogrammers.com/library/mdi/
|
|
123
|
+
icon: ClassVar[str | None] = None
|
|
124
|
+
|
|
125
|
+
_type_slug: ClassVar[str]
|
|
126
|
+
|
|
127
|
+
def generate_schema(self, user: UserABC, language_manager: LanguageManager) -> CategorySchemaData:
|
|
128
|
+
return CategorySchemaData(
|
|
129
|
+
title=language_manager.get_text(self.title) or self.slug,
|
|
130
|
+
description=language_manager.get_text(self.description),
|
|
131
|
+
icon=self.icon,
|
|
132
|
+
type=self._type_slug,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
@classmethod
|
|
136
|
+
def __get_pydantic_core_schema__(cls, source_type: Any, handler: Any) -> core_schema.CoreSchema:
|
|
137
|
+
def validate(v: Any) -> "Category":
|
|
138
|
+
if isinstance(v, cls):
|
|
139
|
+
return v
|
|
140
|
+
raise TypeError(f"Expected {cls.__name__} instance")
|
|
141
|
+
|
|
142
|
+
return core_schema.no_info_plain_validator_function(
|
|
143
|
+
validate,
|
|
144
|
+
serialization=core_schema.plain_serializer_function_ser_schema(
|
|
145
|
+
lambda v: repr(v),
|
|
146
|
+
info_arg=False,
|
|
147
|
+
return_schema=core_schema.str_schema(),
|
|
148
|
+
),
|
|
149
|
+
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .category_graphs import CategoryGraphs
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from typing import Any, Dict, List
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, Field
|
|
4
|
+
|
|
5
|
+
from admin_panel.schema import Category
|
|
6
|
+
from admin_panel.schema.category import GraphInfoSchemaData
|
|
7
|
+
from admin_panel.schema.table.fields_schema import FieldsSchema
|
|
8
|
+
from admin_panel.translations import LanguageManager, TranslateText
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class GraphData(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
|
+
|
|
24
|
+
class GraphsDataResult(BaseModel):
|
|
25
|
+
charts: List[ChartData]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class CategoryGraphs(Category):
|
|
29
|
+
_type_slug: str = 'graphs'
|
|
30
|
+
|
|
31
|
+
search_enabled: bool = False
|
|
32
|
+
search_help: str | TranslateText | None = None
|
|
33
|
+
|
|
34
|
+
table_filters: FieldsSchema | None = None
|
|
35
|
+
|
|
36
|
+
def generate_schema(self, user, language_manager: LanguageManager) -> GraphInfoSchemaData:
|
|
37
|
+
schema = super().generate_schema(user, language_manager)
|
|
38
|
+
graph = GraphInfoSchemaData(
|
|
39
|
+
search_enabled=self.search_enabled,
|
|
40
|
+
search_help=language_manager.get_text(self.search_help),
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
if self.table_filters:
|
|
44
|
+
graph.table_filters = self.table_filters.generate_schema(user, language_manager)
|
|
45
|
+
|
|
46
|
+
schema.graph_info = graph
|
|
47
|
+
return schema
|
|
48
|
+
|
|
49
|
+
async def get_data(self, data: GraphData, user) -> GraphsDataResult:
|
|
50
|
+
raise NotImplementedError('get_data is not implemented')
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
from typing import Dict, List
|
|
3
|
+
|
|
4
|
+
from pydantic.dataclasses import dataclass
|
|
5
|
+
|
|
6
|
+
from admin_panel.auth import UserABC
|
|
7
|
+
from admin_panel.schema.category import Category, CategorySchemaData
|
|
8
|
+
from admin_panel.translations import LanguageManager, TranslateText
|
|
9
|
+
from admin_panel.utils import DataclassBase, get_logger
|
|
10
|
+
|
|
11
|
+
logger = get_logger()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class GroupSchemaData(DataclassBase):
|
|
16
|
+
title: str | None
|
|
17
|
+
description: str | None
|
|
18
|
+
icon: str | None
|
|
19
|
+
categories: Dict[str, CategorySchemaData]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class Group(abc.ABC):
|
|
24
|
+
categories: List[Category]
|
|
25
|
+
slug: str
|
|
26
|
+
title: str | TranslateText | None = None
|
|
27
|
+
description: str | TranslateText | None = None
|
|
28
|
+
|
|
29
|
+
# https://pictogrammers.com/library/mdi/
|
|
30
|
+
icon: str | None = None
|
|
31
|
+
|
|
32
|
+
def __post_init__(self):
|
|
33
|
+
for category in self.categories:
|
|
34
|
+
if not issubclass(category.__class__, Category):
|
|
35
|
+
raise TypeError(f'Category "{category}" is not instance of Category subclass')
|
|
36
|
+
|
|
37
|
+
def generate_schema(self, user: UserABC, language_manager: LanguageManager) -> GroupSchemaData:
|
|
38
|
+
result = GroupSchemaData(
|
|
39
|
+
title=language_manager.get_text(self.title) or self.slug,
|
|
40
|
+
description=language_manager.get_text(self.description),
|
|
41
|
+
icon=self.icon,
|
|
42
|
+
categories={},
|
|
43
|
+
)
|
|
44
|
+
if not self.categories:
|
|
45
|
+
logger.warning('Group "%s" %s.categories is empty!', self.slug, type(self).__name__)
|
|
46
|
+
|
|
47
|
+
for category in self.categories:
|
|
48
|
+
|
|
49
|
+
if not category.slug:
|
|
50
|
+
msg = f'Category {type(category).__name__}.slug is empty'
|
|
51
|
+
raise AttributeError(msg)
|
|
52
|
+
|
|
53
|
+
if category.slug in result.categories:
|
|
54
|
+
exists = result.categories[category.slug]
|
|
55
|
+
msg = f'Category {type(category).__name__}.slug "{self.slug}" already registered by "{exists.title}"'
|
|
56
|
+
raise KeyError(msg)
|
|
57
|
+
|
|
58
|
+
result.categories[category.slug] = category.generate_schema(user, language_manager)
|
|
59
|
+
|
|
60
|
+
return result
|
|
61
|
+
|
|
62
|
+
def get_category(self, category_slug: str) -> Category | None:
|
|
63
|
+
for category in self.categories:
|
|
64
|
+
if category.slug == category_slug:
|
|
65
|
+
return category
|
|
66
|
+
|
|
67
|
+
return None
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# pylint: disable=wildcard-import, unused-wildcard-import, unused-import
|
|
2
|
+
# flake8: noqa: F405
|
|
3
|
+
from .admin_action import admin_action
|
|
4
|
+
from .category_table import CategoryTable
|
|
5
|
+
from .fields import *
|
|
6
|
+
from .fields_schema import FieldsSchema
|
|
7
|
+
from .table_models import (
|
|
8
|
+
AutocompleteData, AutocompleteResult, CreateResult, ListData, RetrieveResult, TableListResult, UpdateResult)
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import functools
|
|
2
|
+
from typing import Any, Dict, List, Optional
|
|
3
|
+
|
|
4
|
+
from pydantic import BaseModel, Field, validate_call
|
|
5
|
+
from pydantic.dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
from admin_panel.schema.table.fields_schema import FieldsSchema
|
|
8
|
+
from admin_panel.translations import DataclassBase, TranslateText
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ActionData(BaseModel):
|
|
12
|
+
pks: List[Any] = Field(default_factory=list)
|
|
13
|
+
form_data: dict = Field(default_factory=dict)
|
|
14
|
+
|
|
15
|
+
search: str | None = None
|
|
16
|
+
filters: Dict[str, Any] = Field(default_factory=dict)
|
|
17
|
+
|
|
18
|
+
send_to_all: bool = False
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class ActionMessage(DataclassBase):
|
|
23
|
+
text: str | TranslateText
|
|
24
|
+
type: str = 'success'
|
|
25
|
+
position: str = 'top-center'
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class ActionResult(DataclassBase):
|
|
30
|
+
message: ActionMessage | None = None
|
|
31
|
+
persistent_message: str | TranslateText | None = None
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# pylint: disable=too-many-arguments
|
|
35
|
+
# pylint: disable=too-many-positional-arguments
|
|
36
|
+
@validate_call
|
|
37
|
+
def admin_action(
|
|
38
|
+
title: str | TranslateText,
|
|
39
|
+
description: Optional[str | TranslateText] = None,
|
|
40
|
+
confirmation_text: Optional[str | TranslateText] = None,
|
|
41
|
+
|
|
42
|
+
# https://vuetifyjs.com/en/styles/colors/#material-colors
|
|
43
|
+
base_color: Optional[str] = None,
|
|
44
|
+
|
|
45
|
+
# https://pictogrammers.com/library/mdi/
|
|
46
|
+
icon: Optional[str] = None,
|
|
47
|
+
|
|
48
|
+
# elevated, flat, tonal, outlined, text, and plain.
|
|
49
|
+
variant: Optional[str] = None,
|
|
50
|
+
|
|
51
|
+
allow_empty_selection: bool = False,
|
|
52
|
+
form_schema: Optional[FieldsSchema] = None,
|
|
53
|
+
):
|
|
54
|
+
def wrapper(func):
|
|
55
|
+
func.__action__ = True
|
|
56
|
+
|
|
57
|
+
func.action_info = {
|
|
58
|
+
'title': title,
|
|
59
|
+
'description': description,
|
|
60
|
+
'confirmation_text': confirmation_text,
|
|
61
|
+
|
|
62
|
+
'icon': icon,
|
|
63
|
+
'base_color': base_color,
|
|
64
|
+
'variant': variant,
|
|
65
|
+
|
|
66
|
+
'allow_empty_selection': allow_empty_selection,
|
|
67
|
+
'form_schema': form_schema,
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
@functools.wraps(func)
|
|
71
|
+
async def wrapped(*args):
|
|
72
|
+
return await func(*args)
|
|
73
|
+
|
|
74
|
+
return wrapped
|
|
75
|
+
|
|
76
|
+
return wrapper
|