brilliance-admin 0.43.7__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.7 → brilliance_admin-0.44.0}/.github/workflows/certbot.yml +12 -3
- brilliance_admin-0.44.0/LICENSE +21 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/PKG-INFO +46 -108
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/README.md +43 -105
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/brilliance_admin/schema/__init__.py +2 -2
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/brilliance_admin/schema/admin_schema.py +21 -19
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/brilliance_admin/schema/category.py +85 -10
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/brilliance_admin/schema/graphs/category_graphs.py +2 -3
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/brilliance_admin/schema/table/category_table.py +4 -2
- brilliance_admin-0.43.7/brilliance_admin/static/index-BnnESruI.js → brilliance_admin-0.44.0/brilliance_admin/static/index-MLuDem5W.js +57 -57
- brilliance_admin-0.43.7/brilliance_admin/static/index-vlBToOhT.css → brilliance_admin-0.44.0/brilliance_admin/static/index-P_wdMBbz.css +1 -1
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/brilliance_admin/templates/index.html +2 -2
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/brilliance_admin/utils.py +38 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/brilliance_admin.egg-info/PKG-INFO +46 -108
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/brilliance_admin.egg-info/SOURCES.txt +2 -3
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/example/locales/en.yml +1 -1
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/example/locales/ru.yml +1 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/example/main.py +22 -11
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/pyproject.toml +3 -3
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/tests/test_payments_fields_schema.py +2 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/tests/test_sqlalcmeny_auth.py +2 -2
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/tests/test_sqlalcmeny_crud.py +3 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/tests/test_sqlalcmeny_schema.py +12 -1
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/uv.lock +1 -1
- brilliance_admin-0.43.7/LICENSE +0 -17
- brilliance_admin-0.43.7/brilliance_admin/schema/group.py +0 -67
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/.configs/docker/Dockerfile +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/.configs/docker/docker-compose.yml +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/.configs/nginx/example.conf +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/.env +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/.github/workflows/deploy.yml +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/.github/workflows/install-docker.yml +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/.gitignore +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/.isort.cfg +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/.python-version +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/brilliance_admin/__init__.py +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/brilliance_admin/api/__init__.py +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/brilliance_admin/api/routers.py +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/brilliance_admin/api/utils.py +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/brilliance_admin/api/views/__init__.py +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/brilliance_admin/api/views/auth.py +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/brilliance_admin/api/views/autocomplete.py +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/brilliance_admin/api/views/graphs.py +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/brilliance_admin/api/views/index.py +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/brilliance_admin/api/views/schema.py +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/brilliance_admin/api/views/settings.py +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/brilliance_admin/api/views/table.py +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/brilliance_admin/auth.py +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/brilliance_admin/docs.py +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/brilliance_admin/exceptions.py +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/brilliance_admin/integrations/__init__.py +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/brilliance_admin/integrations/sqlalchemy/__init__.py +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/brilliance_admin/integrations/sqlalchemy/auth.py +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/brilliance_admin/integrations/sqlalchemy/autocomplete.py +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/brilliance_admin/integrations/sqlalchemy/fields.py +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/brilliance_admin/integrations/sqlalchemy/fields_schema.py +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/brilliance_admin/integrations/sqlalchemy/table/__init__.py +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/brilliance_admin/integrations/sqlalchemy/table/base.py +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/brilliance_admin/integrations/sqlalchemy/table/create.py +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/brilliance_admin/integrations/sqlalchemy/table/delete.py +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/brilliance_admin/integrations/sqlalchemy/table/list.py +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/brilliance_admin/integrations/sqlalchemy/table/retrieve.py +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/brilliance_admin/integrations/sqlalchemy/table/update.py +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/brilliance_admin/locales/en.yml +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/brilliance_admin/locales/ru.yml +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/brilliance_admin/schema/graphs/__init__.py +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/brilliance_admin/schema/table/__init__.py +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/brilliance_admin/schema/table/admin_action.py +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/brilliance_admin/schema/table/fields/__init__.py +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/brilliance_admin/schema/table/fields/base.py +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/brilliance_admin/schema/table/fields/function_field.py +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/brilliance_admin/schema/table/fields_schema.py +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/brilliance_admin/schema/table/table_models.py +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/brilliance_admin/static/materialdesignicons-webfont-CYDMK1kx.woff2 +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/brilliance_admin/static/materialdesignicons-webfont-CgCzGbLl.woff +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/brilliance_admin/static/materialdesignicons-webfont-D3kAzl71.ttf +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/brilliance_admin/static/materialdesignicons-webfont-DttUABo4.eot +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/brilliance_admin/static/tinymce/dark-first/content.min.css +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/brilliance_admin/static/tinymce/dark-first/skin.min.css +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/brilliance_admin/static/tinymce/dark-slim/content.min.css +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/brilliance_admin/static/tinymce/dark-slim/skin.min.css +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/brilliance_admin/static/tinymce/img/example.png +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/brilliance_admin/static/tinymce/img/tinymce.woff2 +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/brilliance_admin/static/tinymce/lightgray/content.min.css +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/brilliance_admin/static/tinymce/lightgray/fonts/tinymce.woff +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/brilliance_admin/static/tinymce/lightgray/skin.min.css +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/brilliance_admin/static/tinymce/plugins/accordion/css/accordion.css +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/brilliance_admin/static/tinymce/plugins/accordion/plugin.js +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/brilliance_admin/static/tinymce/plugins/codesample/css/prism.css +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/brilliance_admin/static/tinymce/plugins/customLink/css/link.css +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/brilliance_admin/static/tinymce/plugins/customLink/plugin.js +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/brilliance_admin/static/tinymce/tinymce.min.js +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/brilliance_admin/static/vanilla-picker-B6E6ObS_.js +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/brilliance_admin/translations.py +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/brilliance_admin.egg-info/dependency_links.txt +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/brilliance_admin.egg-info/requires.txt +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/brilliance_admin.egg-info/top_level.txt +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/example/README.md +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/example/__init__.py +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/example/sections/__init__.py +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/example/sections/currency.py +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/example/sections/graphs.py +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/example/sections/merchant.py +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/example/sections/models.py +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/example/sections/payments.py +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/example/sections/terminal.py +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/example/sections/users.py +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/example/sqlite.py +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/example/static/favicon.ico +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/example/static/favicon.jpg +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/example/static/logo-outline.png +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/example/static/logo.png +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/example/utils.py +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/screenshots/PC-graphs.jpeg +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/screenshots/PC-table.jpeg +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/screenshots/iPad-edit.jpeg +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/screenshots/iPhone 15-edit.jpeg +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/screenshots/iPhone 15-login.jpeg +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/screenshots/websitemockupgenerator.png +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/setup.cfg +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/tests/__init__.py +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/tests/conftest.py +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/tests/test_action.py +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/tests/test_settings.py +0 -0
- {brilliance_admin-0.43.7 → brilliance_admin-0.44.0}/tests/test_sqlalcmeny_filters.py +0 -0
- {brilliance_admin-0.43.7 → 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.
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: brilliance-admin
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary: Simple and lightweight
|
|
5
|
-
License-Expression:
|
|
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
6
|
Requires-Python: >=3.10
|
|
7
7
|
Description-Content-Type: text/markdown
|
|
8
8
|
License-File: LICENSE
|
|
@@ -37,58 +37,69 @@ Dynamic: license-file
|
|
|
37
37
|
[](https://pypi.org/project/brilliance-admin/)
|
|
38
38
|
[](https://github.com/brilliance-admin/backend-python/actions)
|
|
39
39
|
|
|
40
|
-
Simple and lightweight
|
|
40
|
+
Simple and lightweight data managment framework powered by `FastAPI` and `Vue3` `Vuetify` all-in-one. \
|
|
41
41
|
Integrated with `SQLAlchemy`. Inspaired by Django Admin and DRF.\
|
|
42
42
|
_Some call it heavenly in its brilliance._
|
|
43
43
|
|
|
44
|
-
### [Live Demo](https://brilliance-admin.com/) | [Demo Sources](https://github.com/brilliance-admin/backend-python/tree/main/example) | Documentation
|
|
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
45
|
|
|
46
46
|
<img src="https://github.com/brilliance-admin/backend-python/blob/main/screenshots/websitemockupgenerator.png?raw=true"
|
|
47
47
|
alt="Preview">
|
|
48
48
|
|
|
49
49
|
</div>
|
|
50
50
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
-
|
|
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>
|
|
56
65
|
Providing rich and convenient ways to display and manage data (tables, charts, etc) from any data source.
|
|
57
|
-
- **
|
|
58
|
-
|
|
59
|
-
|
|
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>
|
|
60
72
|
Focused on simplified, but rich configuration.
|
|
61
73
|
|
|
62
|
-
|
|
63
|
-
- After authentication, the user receives the admin panel schema, and the frontend renders it
|
|
64
|
-
- The frontend communicates with the backend via API to fetch and modify data
|
|
65
|
-
|
|
66
|
-
### Features:
|
|
74
|
+
## Features
|
|
67
75
|
|
|
68
76
|
* Tables with full CRUD support, including filtering, sorting, and pagination.
|
|
69
77
|
* Ability to define custom table actions with forms, response messages, and file downloads.
|
|
70
78
|
* Graphs via ChartJS
|
|
71
79
|
* Localization support
|
|
72
80
|
* Adapted for different screen sizes and mobile devices
|
|
73
|
-
*
|
|
81
|
+
* Auth via any account data source
|
|
74
82
|
|
|
75
83
|
**Integrations:**
|
|
84
|
+
|
|
76
85
|
* **SQLAlchemy** - schema autogeneration for tables + CRUD operations + authorization
|
|
77
86
|
|
|
78
87
|
**Planned:**
|
|
88
|
+
|
|
79
89
|
* Dashboard features
|
|
80
90
|
* Role-based access permissions system via interface
|
|
81
91
|
* Backend interface for storing and viewing action history in the admin interface
|
|
82
92
|
* Nested data support for creation and detail views (inline editing), nested CRUD workflows
|
|
83
93
|
* Django ORM integration
|
|
94
|
+
* Support for Oauth providers
|
|
84
95
|
|
|
85
|
-
##
|
|
86
|
-
|
|
87
|
-
Installation:
|
|
96
|
+
## Installation:
|
|
88
97
|
``` shell
|
|
89
98
|
pip install brilliance-admin
|
|
90
99
|
```
|
|
91
100
|
|
|
101
|
+
## Usage example
|
|
102
|
+
|
|
92
103
|
You need to generate `AdminSchema` instance:
|
|
93
104
|
``` python
|
|
94
105
|
from brilliance_admin import schema
|
|
@@ -101,11 +112,9 @@ class CategoryExample(schema.CategoryTable):
|
|
|
101
112
|
admin_schema = schema.AdminSchema(
|
|
102
113
|
title='Admin Panel',
|
|
103
114
|
auth=YourAdminAuthentication(),
|
|
104
|
-
|
|
105
|
-
schema.
|
|
115
|
+
categories=[
|
|
116
|
+
schema.Category(
|
|
106
117
|
slug='example',
|
|
107
|
-
title='Example',
|
|
108
|
-
icon='mdi-star',
|
|
109
118
|
categories=[
|
|
110
119
|
CategoryExample(),
|
|
111
120
|
]
|
|
@@ -115,98 +124,27 @@ admin_schema = schema.AdminSchema(
|
|
|
115
124
|
|
|
116
125
|
admin_app = admin_schema.generate_app()
|
|
117
126
|
|
|
118
|
-
# Your FastAPI app
|
|
127
|
+
# Your FastAPI app (Any ASGI framework can be used)
|
|
119
128
|
app = FastAPI()
|
|
120
129
|
app.mount('/admin', admin_app)
|
|
121
130
|
```
|
|
122
131
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
Supports automatic schema generation for CRUD tables:
|
|
126
|
-
|
|
127
|
-
``` python
|
|
128
|
-
category = sqlalchemy.SQLAlchemyAdmin(db_async_session=async_sessionmaker, model=Terminal)
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
> [!NOTE]
|
|
132
|
-
> If `table_schema` is not specified, it will be generated automatically with all discovered fields and relationships
|
|
133
|
-
|
|
134
|
-
Now, the `category` instance can be passed to `categories`.
|
|
135
|
-
|
|
136
|
-
### DRF class style schema
|
|
137
|
-
|
|
138
|
-
``` python
|
|
139
|
-
from brilliance_admin import sqlalchemy
|
|
140
|
-
from brilliance_admin.translations import TranslateText as _
|
|
141
|
-
|
|
142
|
-
from your_project.models import Terminal
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
class TerminalFiltersSchema(sqlalchemy.SQLAlchemyFieldsSchema):
|
|
146
|
-
model = Terminal
|
|
147
|
-
fields = ['id', 'created_at']
|
|
148
|
-
created_at = schema.DateTimeField(range=True)
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
class TerminalSchema(sqlalchemy.SQLAlchemyFieldsSchema):
|
|
152
|
-
model = Terminal
|
|
153
|
-
list_display = ['id', 'merchant_id']
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
class TerminalAdmin(sqlalchemy.SQLAlchemyAdmin):
|
|
157
|
-
db_async_session = async_sessionmaker
|
|
158
|
-
model = Terminal
|
|
159
|
-
title = _('terminals')
|
|
160
|
-
icon = 'mdi-console-network-outline'
|
|
161
|
-
|
|
162
|
-
ordering_fields = ['id']
|
|
163
|
-
search_fields = ['id', 'title']
|
|
164
|
-
|
|
165
|
-
table_schema = TerminalSchema()
|
|
166
|
-
table_filters = TerminalFiltersSchema()
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
category = TerminalAdmin()
|
|
170
|
-
```
|
|
171
|
-
|
|
172
|
-
### Can be used both via inheritance and instancing
|
|
132
|
+
For more details, check out our [how-to-start documentation](https://docs.brilliance-admin.com/how-to-start/)
|
|
173
133
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
Availiable for `SQLAlchemyAdmin` and `SQLAlchemyFieldsSchema`
|
|
177
|
-
|
|
178
|
-
``` python
|
|
179
|
-
category = sqlalchemy.SQLAlchemyAdmin(
|
|
180
|
-
db_async_session=async_sessionmaker,
|
|
181
|
-
model=Terminal,
|
|
182
|
-
|
|
183
|
-
table_schema = sqlalchemy.SQLAlchemyFieldsSchema(
|
|
184
|
-
model=Terminal,
|
|
185
|
-
list_display=['id', 'merchant_id'],
|
|
186
|
-
),
|
|
187
|
-
table_filters = sqlalchemy.SQLAlchemyFieldsSchema(
|
|
188
|
-
model=Terminal,
|
|
189
|
-
fields=['id', 'created_at'],
|
|
190
|
-
created_at=schema.DateTimeField(range=True),
|
|
191
|
-
),
|
|
192
|
-
)
|
|
193
|
-
```
|
|
134
|
+
## Comparison of Similar Projects
|
|
194
135
|
|
|
195
|
-
|
|
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.
|
|
196
138
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
secret='auth_secret',
|
|
200
|
-
db_async_session=async_session,
|
|
201
|
-
user_model=User,
|
|
202
|
-
)
|
|
203
|
-
```
|
|
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.
|
|
204
141
|
|
|
205
|
-
## Comparison of Similar Projects
|
|
142
|
+
## Comparison of Similar Python Projects
|
|
206
143
|
|
|
207
144
|
| Criterion | Brilliance Admin | Django Admin | FastAPI Admin | Starlette Admin | SQLAdmin |
|
|
208
|
-
|
|
209
|
-
| Base framework | FastAPI | Django | FastAPI | Starlette
|
|
145
|
+
|---------|------------------|--------------|---------------|-----------------|----------|
|
|
146
|
+
| Base framework | FastAPI | Django | FastAPI | Starlette | FastAPI |
|
|
147
|
+
| ASGI compatible | Yes | Partial | Yes | Yes | Yes |
|
|
210
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 |
|
|
211
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 |
|
|
212
150
|
| Data source | Any source + SQLAlchemy | Django ORM | Tortoise ORM | Any source + SQLAlchemy, MongoDB | SQLAlchemy |
|
|
@@ -6,58 +6,69 @@
|
|
|
6
6
|
[](https://pypi.org/project/brilliance-admin/)
|
|
7
7
|
[](https://github.com/brilliance-admin/backend-python/actions)
|
|
8
8
|
|
|
9
|
-
Simple and lightweight
|
|
9
|
+
Simple and lightweight data managment framework powered by `FastAPI` and `Vue3` `Vuetify` all-in-one. \
|
|
10
10
|
Integrated with `SQLAlchemy`. Inspaired by Django Admin and DRF.\
|
|
11
11
|
_Some call it heavenly in its brilliance._
|
|
12
12
|
|
|
13
|
-
### [Live Demo](https://brilliance-admin.com/) | [Demo Sources](https://github.com/brilliance-admin/backend-python/tree/main/example) | Documentation
|
|
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
14
|
|
|
15
15
|
<img src="https://github.com/brilliance-admin/backend-python/blob/main/screenshots/websitemockupgenerator.png?raw=true"
|
|
16
16
|
alt="Preview">
|
|
17
17
|
|
|
18
18
|
</div>
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
-
|
|
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>
|
|
25
34
|
Providing rich and convenient ways to display and manage data (tables, charts, etc) from any data source.
|
|
26
|
-
- **
|
|
27
|
-
|
|
28
|
-
|
|
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>
|
|
29
41
|
Focused on simplified, but rich configuration.
|
|
30
42
|
|
|
31
|
-
|
|
32
|
-
- After authentication, the user receives the admin panel schema, and the frontend renders it
|
|
33
|
-
- The frontend communicates with the backend via API to fetch and modify data
|
|
34
|
-
|
|
35
|
-
### Features:
|
|
43
|
+
## Features
|
|
36
44
|
|
|
37
45
|
* Tables with full CRUD support, including filtering, sorting, and pagination.
|
|
38
46
|
* Ability to define custom table actions with forms, response messages, and file downloads.
|
|
39
47
|
* Graphs via ChartJS
|
|
40
48
|
* Localization support
|
|
41
49
|
* Adapted for different screen sizes and mobile devices
|
|
42
|
-
*
|
|
50
|
+
* Auth via any account data source
|
|
43
51
|
|
|
44
52
|
**Integrations:**
|
|
53
|
+
|
|
45
54
|
* **SQLAlchemy** - schema autogeneration for tables + CRUD operations + authorization
|
|
46
55
|
|
|
47
56
|
**Planned:**
|
|
57
|
+
|
|
48
58
|
* Dashboard features
|
|
49
59
|
* Role-based access permissions system via interface
|
|
50
60
|
* Backend interface for storing and viewing action history in the admin interface
|
|
51
61
|
* Nested data support for creation and detail views (inline editing), nested CRUD workflows
|
|
52
62
|
* Django ORM integration
|
|
63
|
+
* Support for Oauth providers
|
|
53
64
|
|
|
54
|
-
##
|
|
55
|
-
|
|
56
|
-
Installation:
|
|
65
|
+
## Installation:
|
|
57
66
|
``` shell
|
|
58
67
|
pip install brilliance-admin
|
|
59
68
|
```
|
|
60
69
|
|
|
70
|
+
## Usage example
|
|
71
|
+
|
|
61
72
|
You need to generate `AdminSchema` instance:
|
|
62
73
|
``` python
|
|
63
74
|
from brilliance_admin import schema
|
|
@@ -70,11 +81,9 @@ class CategoryExample(schema.CategoryTable):
|
|
|
70
81
|
admin_schema = schema.AdminSchema(
|
|
71
82
|
title='Admin Panel',
|
|
72
83
|
auth=YourAdminAuthentication(),
|
|
73
|
-
|
|
74
|
-
schema.
|
|
84
|
+
categories=[
|
|
85
|
+
schema.Category(
|
|
75
86
|
slug='example',
|
|
76
|
-
title='Example',
|
|
77
|
-
icon='mdi-star',
|
|
78
87
|
categories=[
|
|
79
88
|
CategoryExample(),
|
|
80
89
|
]
|
|
@@ -84,98 +93,27 @@ admin_schema = schema.AdminSchema(
|
|
|
84
93
|
|
|
85
94
|
admin_app = admin_schema.generate_app()
|
|
86
95
|
|
|
87
|
-
# Your FastAPI app
|
|
96
|
+
# Your FastAPI app (Any ASGI framework can be used)
|
|
88
97
|
app = FastAPI()
|
|
89
98
|
app.mount('/admin', admin_app)
|
|
90
99
|
```
|
|
91
100
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
Supports automatic schema generation for CRUD tables:
|
|
95
|
-
|
|
96
|
-
``` python
|
|
97
|
-
category = sqlalchemy.SQLAlchemyAdmin(db_async_session=async_sessionmaker, model=Terminal)
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
> [!NOTE]
|
|
101
|
-
> If `table_schema` is not specified, it will be generated automatically with all discovered fields and relationships
|
|
102
|
-
|
|
103
|
-
Now, the `category` instance can be passed to `categories`.
|
|
104
|
-
|
|
105
|
-
### DRF class style schema
|
|
106
|
-
|
|
107
|
-
``` python
|
|
108
|
-
from brilliance_admin import sqlalchemy
|
|
109
|
-
from brilliance_admin.translations import TranslateText as _
|
|
110
|
-
|
|
111
|
-
from your_project.models import Terminal
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
class TerminalFiltersSchema(sqlalchemy.SQLAlchemyFieldsSchema):
|
|
115
|
-
model = Terminal
|
|
116
|
-
fields = ['id', 'created_at']
|
|
117
|
-
created_at = schema.DateTimeField(range=True)
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
class TerminalSchema(sqlalchemy.SQLAlchemyFieldsSchema):
|
|
121
|
-
model = Terminal
|
|
122
|
-
list_display = ['id', 'merchant_id']
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
class TerminalAdmin(sqlalchemy.SQLAlchemyAdmin):
|
|
126
|
-
db_async_session = async_sessionmaker
|
|
127
|
-
model = Terminal
|
|
128
|
-
title = _('terminals')
|
|
129
|
-
icon = 'mdi-console-network-outline'
|
|
130
|
-
|
|
131
|
-
ordering_fields = ['id']
|
|
132
|
-
search_fields = ['id', 'title']
|
|
133
|
-
|
|
134
|
-
table_schema = TerminalSchema()
|
|
135
|
-
table_filters = TerminalFiltersSchema()
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
category = TerminalAdmin()
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
### Can be used both via inheritance and instancing
|
|
101
|
+
For more details, check out our [how-to-start documentation](https://docs.brilliance-admin.com/how-to-start/)
|
|
142
102
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
Availiable for `SQLAlchemyAdmin` and `SQLAlchemyFieldsSchema`
|
|
146
|
-
|
|
147
|
-
``` python
|
|
148
|
-
category = sqlalchemy.SQLAlchemyAdmin(
|
|
149
|
-
db_async_session=async_sessionmaker,
|
|
150
|
-
model=Terminal,
|
|
151
|
-
|
|
152
|
-
table_schema = sqlalchemy.SQLAlchemyFieldsSchema(
|
|
153
|
-
model=Terminal,
|
|
154
|
-
list_display=['id', 'merchant_id'],
|
|
155
|
-
),
|
|
156
|
-
table_filters = sqlalchemy.SQLAlchemyFieldsSchema(
|
|
157
|
-
model=Terminal,
|
|
158
|
-
fields=['id', 'created_at'],
|
|
159
|
-
created_at=schema.DateTimeField(range=True),
|
|
160
|
-
),
|
|
161
|
-
)
|
|
162
|
-
```
|
|
103
|
+
## Comparison of Similar Projects
|
|
163
104
|
|
|
164
|
-
|
|
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.
|
|
165
107
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
secret='auth_secret',
|
|
169
|
-
db_async_session=async_session,
|
|
170
|
-
user_model=User,
|
|
171
|
-
)
|
|
172
|
-
```
|
|
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.
|
|
173
110
|
|
|
174
|
-
## Comparison of Similar Projects
|
|
111
|
+
## Comparison of Similar Python Projects
|
|
175
112
|
|
|
176
113
|
| Criterion | Brilliance Admin | Django Admin | FastAPI Admin | Starlette Admin | SQLAdmin |
|
|
177
|
-
|
|
178
|
-
| Base framework | FastAPI | Django | FastAPI | Starlette
|
|
114
|
+
|---------|------------------|--------------|---------------|-----------------|----------|
|
|
115
|
+
| Base framework | FastAPI | Django | FastAPI | Starlette | FastAPI |
|
|
116
|
+
| ASGI compatible | Yes | Partial | Yes | Yes | Yes |
|
|
179
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 |
|
|
180
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 |
|
|
181
119
|
| Data source | Any source + SQLAlchemy | Django ORM | Tortoise ORM | Any source + SQLAlchemy, MongoDB | SQLAlchemy |
|
|
@@ -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
|
|