brilliance-admin 0.43.6__tar.gz → 0.44.0__tar.gz
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-0.43.6 → brilliance_admin-0.44.0}/.github/workflows/certbot.yml +12 -3
- brilliance_admin-0.44.0/LICENSE +21 -0
- brilliance_admin-0.44.0/PKG-INFO +155 -0
- brilliance_admin-0.44.0/README.md +124 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/integrations/sqlalchemy/__init__.py +1 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/integrations/sqlalchemy/autocomplete.py +6 -2
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/integrations/sqlalchemy/fields.py +7 -5
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/schema/__init__.py +2 -2
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/schema/admin_schema.py +21 -19
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/schema/category.py +86 -11
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/schema/graphs/category_graphs.py +2 -3
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/schema/table/category_table.py +4 -2
- brilliance_admin-0.43.6/brilliance_admin/static/index-D9axz5zK.js → brilliance_admin-0.44.0/brilliance_admin/static/index-MLuDem5W.js +131 -131
- brilliance_admin-0.43.6/brilliance_admin/static/index-vlBToOhT.css → brilliance_admin-0.44.0/brilliance_admin/static/index-P_wdMBbz.css +1 -1
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/templates/index.html +2 -2
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/utils.py +38 -0
- brilliance_admin-0.44.0/brilliance_admin.egg-info/PKG-INFO +155 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin.egg-info/SOURCES.txt +2 -3
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/example/locales/en.yml +1 -1
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/example/locales/ru.yml +1 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/example/main.py +22 -11
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/example/sections/models.py +4 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/example/sections/payments.py +0 -3
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/example/sections/terminal.py +2 -1
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/pyproject.toml +3 -3
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/tests/test_payments_fields_schema.py +2 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/tests/test_sqlalcmeny_auth.py +2 -2
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/tests/test_sqlalcmeny_crud.py +3 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/tests/test_sqlalcmeny_schema.py +12 -1
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/uv.lock +1 -1
- brilliance_admin-0.43.6/LICENSE +0 -17
- brilliance_admin-0.43.6/PKG-INFO +0 -214
- brilliance_admin-0.43.6/README.md +0 -183
- brilliance_admin-0.43.6/brilliance_admin/schema/group.py +0 -67
- brilliance_admin-0.43.6/brilliance_admin.egg-info/PKG-INFO +0 -214
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/.configs/docker/Dockerfile +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/.configs/docker/docker-compose.yml +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/.configs/nginx/example.conf +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/.env +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/.github/workflows/deploy.yml +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/.github/workflows/install-docker.yml +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/.gitignore +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/.isort.cfg +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/.python-version +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/__init__.py +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/api/__init__.py +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/api/routers.py +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/api/utils.py +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/api/views/__init__.py +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/api/views/auth.py +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/api/views/autocomplete.py +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/api/views/graphs.py +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/api/views/index.py +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/api/views/schema.py +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/api/views/settings.py +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/api/views/table.py +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/auth.py +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/docs.py +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/exceptions.py +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/integrations/__init__.py +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/integrations/sqlalchemy/auth.py +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/integrations/sqlalchemy/fields_schema.py +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/integrations/sqlalchemy/table/__init__.py +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/integrations/sqlalchemy/table/base.py +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/integrations/sqlalchemy/table/create.py +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/integrations/sqlalchemy/table/delete.py +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/integrations/sqlalchemy/table/list.py +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/integrations/sqlalchemy/table/retrieve.py +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/integrations/sqlalchemy/table/update.py +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/locales/en.yml +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/locales/ru.yml +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/schema/graphs/__init__.py +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/schema/table/__init__.py +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/schema/table/admin_action.py +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/schema/table/fields/__init__.py +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/schema/table/fields/base.py +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/schema/table/fields/function_field.py +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/schema/table/fields_schema.py +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/schema/table/table_models.py +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/static/materialdesignicons-webfont-CYDMK1kx.woff2 +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/static/materialdesignicons-webfont-CgCzGbLl.woff +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/static/materialdesignicons-webfont-D3kAzl71.ttf +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/static/materialdesignicons-webfont-DttUABo4.eot +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/static/tinymce/dark-first/content.min.css +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/static/tinymce/dark-first/skin.min.css +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/static/tinymce/dark-slim/content.min.css +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/static/tinymce/dark-slim/skin.min.css +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/static/tinymce/img/example.png +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/static/tinymce/img/tinymce.woff2 +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/static/tinymce/lightgray/content.min.css +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/static/tinymce/lightgray/fonts/tinymce.woff +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/static/tinymce/lightgray/skin.min.css +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/static/tinymce/plugins/accordion/css/accordion.css +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/static/tinymce/plugins/accordion/plugin.js +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/static/tinymce/plugins/codesample/css/prism.css +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/static/tinymce/plugins/customLink/css/link.css +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/static/tinymce/plugins/customLink/plugin.js +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/static/tinymce/tinymce.min.js +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/static/vanilla-picker-B6E6ObS_.js +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/translations.py +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin.egg-info/dependency_links.txt +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin.egg-info/requires.txt +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin.egg-info/top_level.txt +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/example/README.md +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/example/__init__.py +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/example/sections/__init__.py +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/example/sections/currency.py +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/example/sections/graphs.py +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/example/sections/merchant.py +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/example/sections/users.py +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/example/sqlite.py +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/example/static/favicon.ico +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/example/static/favicon.jpg +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/example/static/logo-outline.png +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/example/static/logo.png +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/example/utils.py +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/screenshots/PC-graphs.jpeg +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/screenshots/PC-table.jpeg +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/screenshots/iPad-edit.jpeg +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/screenshots/iPhone 15-edit.jpeg +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/screenshots/iPhone 15-login.jpeg +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/screenshots/websitemockupgenerator.png +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/setup.cfg +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/tests/__init__.py +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/tests/conftest.py +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/tests/test_action.py +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/tests/test_settings.py +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/tests/test_sqlalcmeny_filters.py +0 -0
- {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/tests/test_translations.py +0 -0
|
@@ -7,20 +7,29 @@ jobs:
|
|
|
7
7
|
setup-https:
|
|
8
8
|
runs-on: ubuntu-latest
|
|
9
9
|
steps:
|
|
10
|
-
- name: Setup HTTPS with certbot
|
|
10
|
+
- name: Setup HTTPS with certbot (snap)
|
|
11
11
|
uses: appleboy/ssh-action@master
|
|
12
12
|
with:
|
|
13
13
|
host: ${{ vars.SERVER_HOST }}
|
|
14
14
|
username: deploy
|
|
15
15
|
key: ${{ secrets.SERVER_SSH_KEY }}
|
|
16
16
|
script: |
|
|
17
|
-
|
|
17
|
+
sudo apt remove -y certbot python3-certbot-nginx || true
|
|
18
|
+
|
|
19
|
+
if ! command -v snap >/dev/null 2>&1; then
|
|
18
20
|
sudo apt update
|
|
19
|
-
sudo apt install -y
|
|
21
|
+
sudo apt install -y snapd
|
|
20
22
|
fi
|
|
23
|
+
|
|
24
|
+
sudo snap install core || sudo snap refresh core
|
|
25
|
+
sudo snap install --classic certbot || true
|
|
26
|
+
sudo ln -sf /snap/bin/certbot /usr/bin/certbot
|
|
27
|
+
|
|
21
28
|
sudo certbot --nginx \
|
|
22
29
|
-d brilliance-admin.com \
|
|
23
30
|
-d www.brilliance-admin.com \
|
|
31
|
+
-d docs.brilliance-admin.com \
|
|
32
|
+
--expand \
|
|
24
33
|
--non-interactive \
|
|
25
34
|
--agree-tos \
|
|
26
35
|
-m admin@brilliance-admin.com \
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Brilliance Admin
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: brilliance-admin
|
|
3
|
+
Version: 0.44.0
|
|
4
|
+
Summary: Simple and lightweight data managment framework powered by FastAPI and Vue3 Vuetify all-in-one. Some call it heavenly in its brilliance.
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Requires-Python: >=3.10
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Requires-Dist: asgiref>=3.11
|
|
10
|
+
Requires-Dist: fastapi>=0.115
|
|
11
|
+
Requires-Dist: jinja2>=3.1
|
|
12
|
+
Requires-Dist: PyYAML>=6.0
|
|
13
|
+
Provides-Extra: example
|
|
14
|
+
Requires-Dist: uvicorn>=0.34.0; extra == "example"
|
|
15
|
+
Requires-Dist: faker>=38.2.0; extra == "example"
|
|
16
|
+
Requires-Dist: pyjwt>=2.10.1; extra == "example"
|
|
17
|
+
Requires-Dist: structlog>=25.5.0; extra == "example"
|
|
18
|
+
Requires-Dist: rich>=14.2.0; extra == "example"
|
|
19
|
+
Provides-Extra: tests
|
|
20
|
+
Requires-Dist: pytest>=8.4.2; extra == "tests"
|
|
21
|
+
Requires-Dist: pytest-asyncio>=1.2.0; extra == "tests"
|
|
22
|
+
Requires-Dist: httpx>=0.28.1; extra == "tests"
|
|
23
|
+
Requires-Dist: pytest-mock>=3.15.1; extra == "tests"
|
|
24
|
+
Requires-Dist: sqlalchemy>=2.0.41; extra == "tests"
|
|
25
|
+
Requires-Dist: aiosqlite>=0.22.1; extra == "tests"
|
|
26
|
+
Requires-Dist: factory-boy>=3.3.3; extra == "tests"
|
|
27
|
+
Requires-Dist: pyjwt>=2.10.1; extra == "tests"
|
|
28
|
+
Provides-Extra: scalar
|
|
29
|
+
Requires-Dist: scalar-fastapi>=1.5.0; extra == "scalar"
|
|
30
|
+
Dynamic: license-file
|
|
31
|
+
|
|
32
|
+
<div align="center">
|
|
33
|
+
<img src="https://github.com/brilliance-admin/backend-python/blob/main/example/static/logo-outline.png?raw=true"
|
|
34
|
+
alt="Brilliance Admin"
|
|
35
|
+
width="600">
|
|
36
|
+
|
|
37
|
+
[](https://pypi.org/project/brilliance-admin/)
|
|
38
|
+
[](https://github.com/brilliance-admin/backend-python/actions)
|
|
39
|
+
|
|
40
|
+
Simple and lightweight data managment framework powered by `FastAPI` and `Vue3` `Vuetify` all-in-one. \
|
|
41
|
+
Integrated with `SQLAlchemy`. Inspaired by Django Admin and DRF.\
|
|
42
|
+
_Some call it heavenly in its brilliance._
|
|
43
|
+
|
|
44
|
+
### [Live Demo](https://brilliance-admin.com/) | [Demo Sources](https://github.com/brilliance-admin/backend-python/tree/main/example) | [Documentation](https://docs.brilliance-admin.com/)
|
|
45
|
+
|
|
46
|
+
<img src="https://github.com/brilliance-admin/backend-python/blob/main/screenshots/websitemockupgenerator.png?raw=true"
|
|
47
|
+
alt="Preview">
|
|
48
|
+
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
### Brilliance Admin provides
|
|
52
|
+
|
|
53
|
+
A quick way to create a data management interface using:
|
|
54
|
+
|
|
55
|
+
- Admin page - endpoint with a prebuilt SPA [frontend Vue3 + Vuetify](https://github.com/brilliance-admin/frontend) <br>
|
|
56
|
+
This endpoint can be added to any ASGI compatable backend. For existing project or standalone admin app.
|
|
57
|
+
- API to fetch the UI JSON schema
|
|
58
|
+
- API methods for that UI to work with (to read and modify data)
|
|
59
|
+
|
|
60
|
+
## Key ideas
|
|
61
|
+
|
|
62
|
+
- **API Oriented** <br>
|
|
63
|
+
Data generation/updating API separated from rendering fontend with zero hardcode, this makes it possible to have a single frontend with multiple backend implementations in different languages and makes test coverage easier.
|
|
64
|
+
- **Rich visualization** <br>
|
|
65
|
+
Providing rich and convenient ways to display and manage data (tables, charts, etc) from any data source.
|
|
66
|
+
- **UI JSON Schema** <br>
|
|
67
|
+
Represents the data describing the structure of entire admin panel UI. <br>
|
|
68
|
+
You only need to specify what should be rendered. The frontend will display it and automatically request data from the backend for rendering or updates.
|
|
69
|
+
- **ORM** <br>
|
|
70
|
+
Automatic generation from ORM for schema UI frontend and backend methods for CRUD operations.
|
|
71
|
+
- **Minimal boilerplate** <br>
|
|
72
|
+
Focused on simplified, but rich configuration.
|
|
73
|
+
|
|
74
|
+
## Features
|
|
75
|
+
|
|
76
|
+
* Tables with full CRUD support, including filtering, sorting, and pagination.
|
|
77
|
+
* Ability to define custom table actions with forms, response messages, and file downloads.
|
|
78
|
+
* Graphs via ChartJS
|
|
79
|
+
* Localization support
|
|
80
|
+
* Adapted for different screen sizes and mobile devices
|
|
81
|
+
* Auth via any account data source
|
|
82
|
+
|
|
83
|
+
**Integrations:**
|
|
84
|
+
|
|
85
|
+
* **SQLAlchemy** - schema autogeneration for tables + CRUD operations + authorization
|
|
86
|
+
|
|
87
|
+
**Planned:**
|
|
88
|
+
|
|
89
|
+
* Dashboard features
|
|
90
|
+
* Role-based access permissions system via interface
|
|
91
|
+
* Backend interface for storing and viewing action history in the admin interface
|
|
92
|
+
* Nested data support for creation and detail views (inline editing), nested CRUD workflows
|
|
93
|
+
* Django ORM integration
|
|
94
|
+
* Support for Oauth providers
|
|
95
|
+
|
|
96
|
+
## Installation:
|
|
97
|
+
``` shell
|
|
98
|
+
pip install brilliance-admin
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Usage example
|
|
102
|
+
|
|
103
|
+
You need to generate `AdminSchema` instance:
|
|
104
|
+
``` python
|
|
105
|
+
from brilliance_admin import schema
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class CategoryExample(schema.CategoryTable):
|
|
109
|
+
"Implementation of get_list and retrieve; update and create are optional"
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
admin_schema = schema.AdminSchema(
|
|
113
|
+
title='Admin Panel',
|
|
114
|
+
auth=YourAdminAuthentication(),
|
|
115
|
+
categories=[
|
|
116
|
+
schema.Category(
|
|
117
|
+
slug='example',
|
|
118
|
+
categories=[
|
|
119
|
+
CategoryExample(),
|
|
120
|
+
]
|
|
121
|
+
),
|
|
122
|
+
],
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
admin_app = admin_schema.generate_app()
|
|
126
|
+
|
|
127
|
+
# Your FastAPI app (Any ASGI framework can be used)
|
|
128
|
+
app = FastAPI()
|
|
129
|
+
app.mount('/admin', admin_app)
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
For more details, check out our [how-to-start documentation](https://docs.brilliance-admin.com/how-to-start/)
|
|
133
|
+
|
|
134
|
+
## Comparison of Similar Projects
|
|
135
|
+
|
|
136
|
+
The project closest in concept is [React Admin](https://github.com/marmelab/react-admin). <br>
|
|
137
|
+
It is an SPA frontend that store the schema UI inside and works with separate API backend providers.
|
|
138
|
+
|
|
139
|
+
The key difference of Brilliance Admin is that its all-in-one. <br>
|
|
140
|
+
It is more focused on rapid setup for data management, without the need to work with frontend configuration, while it still available.
|
|
141
|
+
|
|
142
|
+
## Comparison of Similar Python Projects
|
|
143
|
+
|
|
144
|
+
| Criterion | Brilliance Admin | Django Admin | FastAPI Admin | Starlette Admin | SQLAdmin |
|
|
145
|
+
|---------|------------------|--------------|---------------|-----------------|----------|
|
|
146
|
+
| Base framework | FastAPI | Django | FastAPI | Starlette | FastAPI |
|
|
147
|
+
| ASGI compatible | Yes | Partial | Yes | Yes | Yes |
|
|
148
|
+
| Rendering model | Prebuilt Vue 3 + Vuetify SPA + Jinja2 | Server-side Django templates | Server-side Jinja2 templates + Tabler UI | Server-side Jinja2 templates + Tabler UI | Server-side Jinja2 templates + Bootstrap |
|
|
149
|
+
| Frontend architecture | Separate frontend (SPA) | Classic server-rendered UI | Server-rendered UI with JS interactivity | Server-rendered UI with JS interactivity | Server-rendered UI |
|
|
150
|
+
| Data source | Any source + SQLAlchemy | Django ORM | Tortoise ORM | Any source + SQLAlchemy, MongoDB | SQLAlchemy |
|
|
151
|
+
| Multiple databases per model | Yes | Database routers | No (global engine) | Yes (session per ModelView) | No (single engine per Admin) |
|
|
152
|
+
| Schema generation | User-defined format | From Django models | From ORM models | User-defined format | From SQLAlchemy models |
|
|
153
|
+
| Async support | Yes | No | Yes | Yes | Yes |
|
|
154
|
+
| API-first approach | Yes | No | Partially | Partially | No |
|
|
155
|
+
| Built-in Localization | Yes | Yes | No | No | No |
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<img src="https://github.com/brilliance-admin/backend-python/blob/main/example/static/logo-outline.png?raw=true"
|
|
3
|
+
alt="Brilliance Admin"
|
|
4
|
+
width="600">
|
|
5
|
+
|
|
6
|
+
[](https://pypi.org/project/brilliance-admin/)
|
|
7
|
+
[](https://github.com/brilliance-admin/backend-python/actions)
|
|
8
|
+
|
|
9
|
+
Simple and lightweight data managment framework powered by `FastAPI` and `Vue3` `Vuetify` all-in-one. \
|
|
10
|
+
Integrated with `SQLAlchemy`. Inspaired by Django Admin and DRF.\
|
|
11
|
+
_Some call it heavenly in its brilliance._
|
|
12
|
+
|
|
13
|
+
### [Live Demo](https://brilliance-admin.com/) | [Demo Sources](https://github.com/brilliance-admin/backend-python/tree/main/example) | [Documentation](https://docs.brilliance-admin.com/)
|
|
14
|
+
|
|
15
|
+
<img src="https://github.com/brilliance-admin/backend-python/blob/main/screenshots/websitemockupgenerator.png?raw=true"
|
|
16
|
+
alt="Preview">
|
|
17
|
+
|
|
18
|
+
</div>
|
|
19
|
+
|
|
20
|
+
### Brilliance Admin provides
|
|
21
|
+
|
|
22
|
+
A quick way to create a data management interface using:
|
|
23
|
+
|
|
24
|
+
- Admin page - endpoint with a prebuilt SPA [frontend Vue3 + Vuetify](https://github.com/brilliance-admin/frontend) <br>
|
|
25
|
+
This endpoint can be added to any ASGI compatable backend. For existing project or standalone admin app.
|
|
26
|
+
- API to fetch the UI JSON schema
|
|
27
|
+
- API methods for that UI to work with (to read and modify data)
|
|
28
|
+
|
|
29
|
+
## Key ideas
|
|
30
|
+
|
|
31
|
+
- **API Oriented** <br>
|
|
32
|
+
Data generation/updating API separated from rendering fontend with zero hardcode, this makes it possible to have a single frontend with multiple backend implementations in different languages and makes test coverage easier.
|
|
33
|
+
- **Rich visualization** <br>
|
|
34
|
+
Providing rich and convenient ways to display and manage data (tables, charts, etc) from any data source.
|
|
35
|
+
- **UI JSON Schema** <br>
|
|
36
|
+
Represents the data describing the structure of entire admin panel UI. <br>
|
|
37
|
+
You only need to specify what should be rendered. The frontend will display it and automatically request data from the backend for rendering or updates.
|
|
38
|
+
- **ORM** <br>
|
|
39
|
+
Automatic generation from ORM for schema UI frontend and backend methods for CRUD operations.
|
|
40
|
+
- **Minimal boilerplate** <br>
|
|
41
|
+
Focused on simplified, but rich configuration.
|
|
42
|
+
|
|
43
|
+
## Features
|
|
44
|
+
|
|
45
|
+
* Tables with full CRUD support, including filtering, sorting, and pagination.
|
|
46
|
+
* Ability to define custom table actions with forms, response messages, and file downloads.
|
|
47
|
+
* Graphs via ChartJS
|
|
48
|
+
* Localization support
|
|
49
|
+
* Adapted for different screen sizes and mobile devices
|
|
50
|
+
* Auth via any account data source
|
|
51
|
+
|
|
52
|
+
**Integrations:**
|
|
53
|
+
|
|
54
|
+
* **SQLAlchemy** - schema autogeneration for tables + CRUD operations + authorization
|
|
55
|
+
|
|
56
|
+
**Planned:**
|
|
57
|
+
|
|
58
|
+
* Dashboard features
|
|
59
|
+
* Role-based access permissions system via interface
|
|
60
|
+
* Backend interface for storing and viewing action history in the admin interface
|
|
61
|
+
* Nested data support for creation and detail views (inline editing), nested CRUD workflows
|
|
62
|
+
* Django ORM integration
|
|
63
|
+
* Support for Oauth providers
|
|
64
|
+
|
|
65
|
+
## Installation:
|
|
66
|
+
``` shell
|
|
67
|
+
pip install brilliance-admin
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Usage example
|
|
71
|
+
|
|
72
|
+
You need to generate `AdminSchema` instance:
|
|
73
|
+
``` python
|
|
74
|
+
from brilliance_admin import schema
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class CategoryExample(schema.CategoryTable):
|
|
78
|
+
"Implementation of get_list and retrieve; update and create are optional"
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
admin_schema = schema.AdminSchema(
|
|
82
|
+
title='Admin Panel',
|
|
83
|
+
auth=YourAdminAuthentication(),
|
|
84
|
+
categories=[
|
|
85
|
+
schema.Category(
|
|
86
|
+
slug='example',
|
|
87
|
+
categories=[
|
|
88
|
+
CategoryExample(),
|
|
89
|
+
]
|
|
90
|
+
),
|
|
91
|
+
],
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
admin_app = admin_schema.generate_app()
|
|
95
|
+
|
|
96
|
+
# Your FastAPI app (Any ASGI framework can be used)
|
|
97
|
+
app = FastAPI()
|
|
98
|
+
app.mount('/admin', admin_app)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
For more details, check out our [how-to-start documentation](https://docs.brilliance-admin.com/how-to-start/)
|
|
102
|
+
|
|
103
|
+
## Comparison of Similar Projects
|
|
104
|
+
|
|
105
|
+
The project closest in concept is [React Admin](https://github.com/marmelab/react-admin). <br>
|
|
106
|
+
It is an SPA frontend that store the schema UI inside and works with separate API backend providers.
|
|
107
|
+
|
|
108
|
+
The key difference of Brilliance Admin is that its all-in-one. <br>
|
|
109
|
+
It is more focused on rapid setup for data management, without the need to work with frontend configuration, while it still available.
|
|
110
|
+
|
|
111
|
+
## Comparison of Similar Python Projects
|
|
112
|
+
|
|
113
|
+
| Criterion | Brilliance Admin | Django Admin | FastAPI Admin | Starlette Admin | SQLAdmin |
|
|
114
|
+
|---------|------------------|--------------|---------------|-----------------|----------|
|
|
115
|
+
| Base framework | FastAPI | Django | FastAPI | Starlette | FastAPI |
|
|
116
|
+
| ASGI compatible | Yes | Partial | Yes | Yes | Yes |
|
|
117
|
+
| Rendering model | Prebuilt Vue 3 + Vuetify SPA + Jinja2 | Server-side Django templates | Server-side Jinja2 templates + Tabler UI | Server-side Jinja2 templates + Tabler UI | Server-side Jinja2 templates + Bootstrap |
|
|
118
|
+
| Frontend architecture | Separate frontend (SPA) | Classic server-rendered UI | Server-rendered UI with JS interactivity | Server-rendered UI with JS interactivity | Server-rendered UI |
|
|
119
|
+
| Data source | Any source + SQLAlchemy | Django ORM | Tortoise ORM | Any source + SQLAlchemy, MongoDB | SQLAlchemy |
|
|
120
|
+
| Multiple databases per model | Yes | Database routers | No (global engine) | Yes (session per ModelView) | No (single engine per Admin) |
|
|
121
|
+
| Schema generation | User-defined format | From Django models | From ORM models | User-defined format | From SQLAlchemy models |
|
|
122
|
+
| Async support | Yes | No | Yes | Yes | Yes |
|
|
123
|
+
| API-first approach | Yes | No | Partially | Partially | No |
|
|
124
|
+
| Built-in Localization | Yes | Yes | No | No | No |
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# pylint: disable=wildcard-import, unused-wildcard-import, unused-import
|
|
2
2
|
# flake8: noqa: F405
|
|
3
|
+
from .fields import SQLAlchemyRelatedField
|
|
3
4
|
from .auth import SQLAlchemyJWTAdminAuthentication
|
|
4
5
|
from .autocomplete import SQLAlchemyAdminAutocompleteMixin
|
|
5
6
|
from .fields_schema import SQLAlchemyFieldsSchema
|
|
@@ -32,7 +32,11 @@ class SQLAlchemyAdminAutocompleteMixin:
|
|
|
32
32
|
if not field:
|
|
33
33
|
raise Exception(f'Field "{data.field_slug}" is not found')
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
results = await field.autocomplete(
|
|
36
|
+
self.model,
|
|
37
|
+
data,
|
|
38
|
+
user,
|
|
39
|
+
extra={'db_async_session': self.db_async_session},
|
|
40
|
+
)
|
|
37
41
|
|
|
38
42
|
return AutocompleteResult(results=results)
|
|
@@ -43,7 +43,7 @@ class SQLAlchemyRelatedField(TableField):
|
|
|
43
43
|
# - для доступа к связи через ORM
|
|
44
44
|
# getattr(record, rel_name)
|
|
45
45
|
# - для записи и чтения связанных объектов
|
|
46
|
-
rel_name: str | None
|
|
46
|
+
rel_name: str | None
|
|
47
47
|
|
|
48
48
|
# Класс связанной SQLAlchemy-модели.
|
|
49
49
|
# Откуда берётся:
|
|
@@ -101,11 +101,11 @@ class SQLAlchemyRelatedField(TableField):
|
|
|
101
101
|
from sqlalchemy import select
|
|
102
102
|
from sqlalchemy.sql import expression
|
|
103
103
|
|
|
104
|
-
if extra is None or extra.get('
|
|
105
|
-
msg = f'SQLAlchemyRelatedField.autocomplete {type(self).__name__} requires extra["
|
|
104
|
+
if extra is None or extra.get('db_async_session') is None:
|
|
105
|
+
msg = f'SQLAlchemyRelatedField.autocomplete {type(self).__name__} requires extra["db_async_session"] (AsyncSession)'
|
|
106
106
|
raise AttributeError(msg)
|
|
107
107
|
|
|
108
|
-
|
|
108
|
+
db_async_session = extra['db_async_session']
|
|
109
109
|
|
|
110
110
|
results = []
|
|
111
111
|
|
|
@@ -125,7 +125,9 @@ class SQLAlchemyRelatedField(TableField):
|
|
|
125
125
|
if existed_choices and hasattr(target_model, 'id'):
|
|
126
126
|
stmt = stmt.where(getattr(target_model, 'id').in_(existed_choices) | expression.true())
|
|
127
127
|
|
|
128
|
-
|
|
128
|
+
async with db_async_session() as session:
|
|
129
|
+
records = (await session.execute(stmt)).scalars().all()
|
|
130
|
+
|
|
129
131
|
for record in records:
|
|
130
132
|
results.append(Record(key=getattr(record, 'id'), title=str(record)))
|
|
131
133
|
|
|
@@ -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
|
|
4
|
+
from .category import CategoryGroup, CategoryLink
|
|
5
5
|
from .graphs import *
|
|
6
|
-
from .group import Group
|
|
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.
|
|
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):
|
|
@@ -50,7 +51,7 @@ class AdminIndexContextData(DataclassBase):
|
|
|
50
51
|
|
|
51
52
|
@dataclass
|
|
52
53
|
class AdminSchema:
|
|
53
|
-
|
|
54
|
+
categories: List[BaseCategory]
|
|
54
55
|
auth: Any
|
|
55
56
|
|
|
56
57
|
title: SupportsStr | None = 'Admin'
|
|
@@ -68,9 +69,9 @@ class AdminSchema:
|
|
|
68
69
|
language_manager: LanguageManager | None = None
|
|
69
70
|
|
|
70
71
|
def __post_init__(self):
|
|
71
|
-
for
|
|
72
|
-
if not issubclass(
|
|
73
|
-
raise TypeError(f'
|
|
72
|
+
for category in self.categories:
|
|
73
|
+
if not issubclass(category.__class__, BaseCategory):
|
|
74
|
+
raise TypeError(f'Root category "{category}" is not instance of BaseCategory subclass')
|
|
74
75
|
|
|
75
76
|
if not self.language_manager:
|
|
76
77
|
self.language_manager = LanguageManager(DEFAULT_LANGUAGES)
|
|
@@ -81,24 +82,25 @@ class AdminSchema:
|
|
|
81
82
|
def generate_schema(self, user: UserABC, language_slug: str | None) -> AdminSchemaData:
|
|
82
83
|
language_context: LanguageContext = self.get_language_context(language_slug)
|
|
83
84
|
|
|
84
|
-
|
|
85
|
+
result = AdminSchemaData(profile=user)
|
|
85
86
|
|
|
86
|
-
for
|
|
87
|
-
if not
|
|
88
|
-
msg = f'Category
|
|
87
|
+
for category in self.categories:
|
|
88
|
+
if not category.slug:
|
|
89
|
+
msg = f'Category {type(category).__name__}.slug is empty'
|
|
89
90
|
raise AttributeError(msg)
|
|
90
91
|
|
|
91
|
-
|
|
92
|
+
try:
|
|
93
|
+
result.categories[category.slug] = category.generate_schema(user, language_context).to_dict(keep_none=False)
|
|
94
|
+
except Exception as e:
|
|
95
|
+
msg = f'Root category "{category.slug}" generate_schema error: {e}'
|
|
96
|
+
raise Exception(msg) from e
|
|
92
97
|
|
|
93
|
-
return
|
|
94
|
-
groups=groups,
|
|
95
|
-
profile=user,
|
|
96
|
-
)
|
|
98
|
+
return result
|
|
97
99
|
|
|
98
|
-
def get_group(self, group_slug: str) -> Optional[
|
|
99
|
-
for
|
|
100
|
-
if
|
|
101
|
-
return
|
|
100
|
+
def get_group(self, group_slug: str) -> Optional[BaseCategory]:
|
|
101
|
+
for category in self.categories:
|
|
102
|
+
if category.slug == group_slug:
|
|
103
|
+
return category
|
|
102
104
|
|
|
103
105
|
return None
|
|
104
106
|
|
|
@@ -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
|
|
@@ -106,37 +109,56 @@ class CategorySchemaData(DataclassBase):
|
|
|
106
109
|
icon: str | None
|
|
107
110
|
type: str
|
|
108
111
|
|
|
112
|
+
categories: dict = Field(default_factory=dict)
|
|
113
|
+
|
|
109
114
|
table_info: TableInfoSchemaData | None = None
|
|
110
115
|
graph_info: GraphInfoSchemaData | None = None
|
|
111
116
|
|
|
117
|
+
link: str | None = None
|
|
118
|
+
|
|
112
119
|
def __repr__(self):
|
|
113
120
|
return f'<CategorySchemaData type={self.type} "{self.title}">'
|
|
114
121
|
|
|
115
122
|
|
|
116
|
-
class
|
|
117
|
-
slug:
|
|
118
|
-
title:
|
|
119
|
-
description:
|
|
123
|
+
class BaseCategory(KwargsInitMixin, abc.ABC):
|
|
124
|
+
slug: str
|
|
125
|
+
title: SupportsStr | None = None
|
|
126
|
+
description: SupportsStr | None = None
|
|
120
127
|
|
|
121
128
|
# https://pictogrammers.com/library/mdi/
|
|
122
|
-
icon:
|
|
129
|
+
icon: str | None = None
|
|
123
130
|
|
|
124
131
|
_type_slug: ClassVar[str]
|
|
125
132
|
|
|
126
133
|
def generate_schema(self, user: UserABC, language_context: LanguageContext) -> CategorySchemaData:
|
|
127
|
-
|
|
128
|
-
|
|
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),
|
|
129
141
|
description=language_context.get_text(self.description),
|
|
130
142
|
icon=self.icon,
|
|
131
|
-
type=
|
|
143
|
+
type=type_slug,
|
|
132
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')
|
|
133
155
|
|
|
134
156
|
@classmethod
|
|
135
157
|
def __get_pydantic_core_schema__(cls, source_type: Any, handler: Any) -> core_schema.CoreSchema:
|
|
136
|
-
def validate(v: Any) -> "
|
|
158
|
+
def validate(v: Any) -> "BaseCategory":
|
|
137
159
|
if isinstance(v, cls):
|
|
138
160
|
return v
|
|
139
|
-
raise TypeError(f"Expected {cls.__name__} instance")
|
|
161
|
+
raise TypeError(f"Expected {cls.__name__} instance, recieved: {type(v)} {v}")
|
|
140
162
|
|
|
141
163
|
return core_schema.no_info_plain_validator_function(
|
|
142
164
|
validate,
|
|
@@ -146,3 +168,56 @@ class Category(abc.ABC):
|
|
|
146
168
|
return_schema=core_schema.str_schema(),
|
|
147
169
|
),
|
|
148
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
|
|
@@ -2,8 +2,7 @@ from typing import Any, Dict, List
|
|
|
2
2
|
|
|
3
3
|
from pydantic import BaseModel, Field
|
|
4
4
|
|
|
5
|
-
from brilliance_admin.schema import
|
|
6
|
-
from brilliance_admin.schema.category import GraphInfoSchemaData
|
|
5
|
+
from brilliance_admin.schema.category import BaseCategory, GraphInfoSchemaData
|
|
7
6
|
from brilliance_admin.schema.table.fields_schema import FieldsSchema
|
|
8
7
|
from brilliance_admin.translations import LanguageContext
|
|
9
8
|
from brilliance_admin.utils import SupportsStr
|
|
@@ -26,7 +25,7 @@ class GraphsDataResult(BaseModel):
|
|
|
26
25
|
charts: List[ChartData]
|
|
27
26
|
|
|
28
27
|
|
|
29
|
-
class CategoryGraphs(
|
|
28
|
+
class CategoryGraphs(BaseCategory):
|
|
30
29
|
_type_slug: str = 'graphs'
|
|
31
30
|
|
|
32
31
|
search_enabled: bool = False
|
{brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/schema/table/category_table.py
RENAMED
|
@@ -8,7 +8,7 @@ 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
|
|
11
|
+
from brilliance_admin.schema.category import BaseCategory
|
|
12
12
|
from brilliance_admin.schema.category import TableInfoSchemaData
|
|
13
13
|
from brilliance_admin.schema.table.admin_action import ActionData, ActionResult
|
|
14
14
|
from brilliance_admin.schema.table.fields_schema import FieldsSchema
|
|
@@ -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(
|
|
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
|
|