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.
Files changed (129) hide show
  1. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/.github/workflows/certbot.yml +12 -3
  2. brilliance_admin-0.44.0/LICENSE +21 -0
  3. brilliance_admin-0.44.0/PKG-INFO +155 -0
  4. brilliance_admin-0.44.0/README.md +124 -0
  5. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/integrations/sqlalchemy/__init__.py +1 -0
  6. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/integrations/sqlalchemy/autocomplete.py +6 -2
  7. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/integrations/sqlalchemy/fields.py +7 -5
  8. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/schema/__init__.py +2 -2
  9. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/schema/admin_schema.py +21 -19
  10. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/schema/category.py +86 -11
  11. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/schema/graphs/category_graphs.py +2 -3
  12. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/schema/table/category_table.py +4 -2
  13. brilliance_admin-0.43.6/brilliance_admin/static/index-D9axz5zK.js → brilliance_admin-0.44.0/brilliance_admin/static/index-MLuDem5W.js +131 -131
  14. 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
  15. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/templates/index.html +2 -2
  16. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/utils.py +38 -0
  17. brilliance_admin-0.44.0/brilliance_admin.egg-info/PKG-INFO +155 -0
  18. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin.egg-info/SOURCES.txt +2 -3
  19. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/example/locales/en.yml +1 -1
  20. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/example/locales/ru.yml +1 -0
  21. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/example/main.py +22 -11
  22. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/example/sections/models.py +4 -0
  23. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/example/sections/payments.py +0 -3
  24. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/example/sections/terminal.py +2 -1
  25. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/pyproject.toml +3 -3
  26. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/tests/test_payments_fields_schema.py +2 -0
  27. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/tests/test_sqlalcmeny_auth.py +2 -2
  28. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/tests/test_sqlalcmeny_crud.py +3 -0
  29. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/tests/test_sqlalcmeny_schema.py +12 -1
  30. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/uv.lock +1 -1
  31. brilliance_admin-0.43.6/LICENSE +0 -17
  32. brilliance_admin-0.43.6/PKG-INFO +0 -214
  33. brilliance_admin-0.43.6/README.md +0 -183
  34. brilliance_admin-0.43.6/brilliance_admin/schema/group.py +0 -67
  35. brilliance_admin-0.43.6/brilliance_admin.egg-info/PKG-INFO +0 -214
  36. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/.configs/docker/Dockerfile +0 -0
  37. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/.configs/docker/docker-compose.yml +0 -0
  38. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/.configs/nginx/example.conf +0 -0
  39. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/.env +0 -0
  40. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/.github/workflows/deploy.yml +0 -0
  41. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/.github/workflows/install-docker.yml +0 -0
  42. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/.gitignore +0 -0
  43. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/.isort.cfg +0 -0
  44. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/.python-version +0 -0
  45. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/__init__.py +0 -0
  46. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/api/__init__.py +0 -0
  47. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/api/routers.py +0 -0
  48. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/api/utils.py +0 -0
  49. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/api/views/__init__.py +0 -0
  50. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/api/views/auth.py +0 -0
  51. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/api/views/autocomplete.py +0 -0
  52. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/api/views/graphs.py +0 -0
  53. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/api/views/index.py +0 -0
  54. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/api/views/schema.py +0 -0
  55. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/api/views/settings.py +0 -0
  56. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/api/views/table.py +0 -0
  57. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/auth.py +0 -0
  58. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/docs.py +0 -0
  59. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/exceptions.py +0 -0
  60. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/integrations/__init__.py +0 -0
  61. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/integrations/sqlalchemy/auth.py +0 -0
  62. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/integrations/sqlalchemy/fields_schema.py +0 -0
  63. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/integrations/sqlalchemy/table/__init__.py +0 -0
  64. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/integrations/sqlalchemy/table/base.py +0 -0
  65. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/integrations/sqlalchemy/table/create.py +0 -0
  66. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/integrations/sqlalchemy/table/delete.py +0 -0
  67. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/integrations/sqlalchemy/table/list.py +0 -0
  68. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/integrations/sqlalchemy/table/retrieve.py +0 -0
  69. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/integrations/sqlalchemy/table/update.py +0 -0
  70. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/locales/en.yml +0 -0
  71. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/locales/ru.yml +0 -0
  72. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/schema/graphs/__init__.py +0 -0
  73. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/schema/table/__init__.py +0 -0
  74. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/schema/table/admin_action.py +0 -0
  75. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/schema/table/fields/__init__.py +0 -0
  76. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/schema/table/fields/base.py +0 -0
  77. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/schema/table/fields/function_field.py +0 -0
  78. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/schema/table/fields_schema.py +0 -0
  79. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/schema/table/table_models.py +0 -0
  80. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/static/materialdesignicons-webfont-CYDMK1kx.woff2 +0 -0
  81. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/static/materialdesignicons-webfont-CgCzGbLl.woff +0 -0
  82. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/static/materialdesignicons-webfont-D3kAzl71.ttf +0 -0
  83. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/static/materialdesignicons-webfont-DttUABo4.eot +0 -0
  84. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/static/tinymce/dark-first/content.min.css +0 -0
  85. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/static/tinymce/dark-first/skin.min.css +0 -0
  86. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/static/tinymce/dark-slim/content.min.css +0 -0
  87. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/static/tinymce/dark-slim/skin.min.css +0 -0
  88. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/static/tinymce/img/example.png +0 -0
  89. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/static/tinymce/img/tinymce.woff2 +0 -0
  90. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/static/tinymce/lightgray/content.min.css +0 -0
  91. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/static/tinymce/lightgray/fonts/tinymce.woff +0 -0
  92. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/static/tinymce/lightgray/skin.min.css +0 -0
  93. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/static/tinymce/plugins/accordion/css/accordion.css +0 -0
  94. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/static/tinymce/plugins/accordion/plugin.js +0 -0
  95. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/static/tinymce/plugins/codesample/css/prism.css +0 -0
  96. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/static/tinymce/plugins/customLink/css/link.css +0 -0
  97. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/static/tinymce/plugins/customLink/plugin.js +0 -0
  98. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/static/tinymce/tinymce.min.js +0 -0
  99. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/static/vanilla-picker-B6E6ObS_.js +0 -0
  100. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin/translations.py +0 -0
  101. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin.egg-info/dependency_links.txt +0 -0
  102. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin.egg-info/requires.txt +0 -0
  103. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/brilliance_admin.egg-info/top_level.txt +0 -0
  104. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/example/README.md +0 -0
  105. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/example/__init__.py +0 -0
  106. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/example/sections/__init__.py +0 -0
  107. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/example/sections/currency.py +0 -0
  108. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/example/sections/graphs.py +0 -0
  109. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/example/sections/merchant.py +0 -0
  110. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/example/sections/users.py +0 -0
  111. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/example/sqlite.py +0 -0
  112. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/example/static/favicon.ico +0 -0
  113. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/example/static/favicon.jpg +0 -0
  114. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/example/static/logo-outline.png +0 -0
  115. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/example/static/logo.png +0 -0
  116. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/example/utils.py +0 -0
  117. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/screenshots/PC-graphs.jpeg +0 -0
  118. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/screenshots/PC-table.jpeg +0 -0
  119. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/screenshots/iPad-edit.jpeg +0 -0
  120. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/screenshots/iPhone 15-edit.jpeg +0 -0
  121. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/screenshots/iPhone 15-login.jpeg +0 -0
  122. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/screenshots/websitemockupgenerator.png +0 -0
  123. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/setup.cfg +0 -0
  124. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/tests/__init__.py +0 -0
  125. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/tests/conftest.py +0 -0
  126. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/tests/test_action.py +0 -0
  127. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/tests/test_settings.py +0 -0
  128. {brilliance_admin-0.43.6 → brilliance_admin-0.44.0}/tests/test_sqlalcmeny_filters.py +0 -0
  129. {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
- if ! command -v certbot >/dev/null 2>&1; then
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 certbot python3-certbot-nginx
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
+ [![PyPI](https://img.shields.io/pypi/v/brilliance-admin)](https://pypi.org/project/brilliance-admin/)
38
+ [![CI](https://github.com/brilliance-admin/backend-python/actions/workflows/deploy.yml/badge.svg)](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
+ [![PyPI](https://img.shields.io/pypi/v/brilliance-admin)](https://pypi.org/project/brilliance-admin/)
7
+ [![CI](https://github.com/brilliance-admin/backend-python/actions/workflows/deploy.yml/badge.svg)](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
- async with self.db_async_session() as session:
36
- results = await field.autocomplete(self.model, data, user, extra={'db_session': session})
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 = 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('db_session') is None:
105
- msg = f'SQLAlchemyRelatedField.autocomplete {type(self).__name__} requires extra["db_session"] (AsyncSession)'
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
- session = extra['db_session']
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
- records = (await session.execute(stmt)).scalars().all()
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 Category
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.group import Group, GroupSchemaData
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
- groups: List[Group]
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 group in self.groups:
72
- if not issubclass(group.__class__, Group):
73
- raise TypeError(f'Group "{group}" is not instance of Group subclass')
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
- groups = {}
85
+ result = AdminSchemaData(profile=user)
85
86
 
86
- for group in self.groups:
87
- if not group.slug:
88
- msg = f'Category group {type(group).__name__}.slug is empty'
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
- groups[group.slug] = group.generate_schema(user, language_context)
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 AdminSchemaData(
94
- groups=groups,
95
- profile=user,
96
- )
98
+ return result
97
99
 
98
- def get_group(self, group_slug: str) -> Optional[Group]:
99
- for group in self.groups:
100
- if group.slug == group_slug:
101
- return group
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 Category(abc.ABC):
117
- slug: ClassVar[str]
118
- title: ClassVar[SupportsStr | None] = None
119
- description: ClassVar[SupportsStr | None] = None
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: ClassVar[str | None] = None
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
- return CategorySchemaData(
128
- title=language_context.get_text(self.title) or self.slug,
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=self._type_slug,
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) -> "Category":
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 Category
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(Category):
28
+ class CategoryGraphs(BaseCategory):
30
29
  _type_slug: str = 'graphs'
31
30
 
32
31
  search_enabled: bool = False
@@ -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 Category
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(Category):
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