brilliance-admin 0.42.0__py3-none-any.whl → 0.43.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. {admin_panel → brilliance_admin}/__init__.py +2 -2
  2. brilliance_admin/api/routers.py +18 -0
  3. {admin_panel → brilliance_admin}/api/utils.py +1 -1
  4. {admin_panel → brilliance_admin}/api/views/auth.py +6 -6
  5. {admin_panel → brilliance_admin}/api/views/autocomplete.py +9 -9
  6. {admin_panel → brilliance_admin}/api/views/graphs.py +8 -8
  7. {admin_panel → brilliance_admin}/api/views/index.py +11 -4
  8. {admin_panel → brilliance_admin}/api/views/schema.py +3 -3
  9. {admin_panel → brilliance_admin}/api/views/settings.py +5 -5
  10. {admin_panel → brilliance_admin}/api/views/table.py +23 -23
  11. {admin_panel → brilliance_admin}/auth.py +1 -1
  12. {admin_panel → brilliance_admin}/exceptions.py +3 -4
  13. {admin_panel → brilliance_admin}/integrations/sqlalchemy/auth.py +4 -4
  14. {admin_panel → brilliance_admin}/integrations/sqlalchemy/autocomplete.py +4 -4
  15. {admin_panel → brilliance_admin}/integrations/sqlalchemy/fields.py +10 -10
  16. {admin_panel → brilliance_admin}/integrations/sqlalchemy/fields_schema.py +6 -6
  17. {admin_panel → brilliance_admin}/integrations/sqlalchemy/table/base.py +4 -4
  18. {admin_panel → brilliance_admin}/integrations/sqlalchemy/table/create.py +7 -7
  19. {admin_panel → brilliance_admin}/integrations/sqlalchemy/table/delete.py +3 -3
  20. {admin_panel → brilliance_admin}/integrations/sqlalchemy/table/list.py +7 -7
  21. {admin_panel → brilliance_admin}/integrations/sqlalchemy/table/retrieve.py +6 -6
  22. {admin_panel → brilliance_admin}/integrations/sqlalchemy/table/update.py +6 -6
  23. brilliance_admin/locales/en.yml +22 -0
  24. brilliance_admin/locales/ru.yml +22 -0
  25. {admin_panel → brilliance_admin}/schema/admin_schema.py +39 -31
  26. {admin_panel → brilliance_admin}/schema/category.py +8 -8
  27. {admin_panel → brilliance_admin}/schema/graphs/category_graphs.py +10 -9
  28. {admin_panel → brilliance_admin}/schema/group.py +10 -10
  29. {admin_panel → brilliance_admin}/schema/table/admin_action.py +8 -7
  30. {admin_panel → brilliance_admin}/schema/table/category_table.py +21 -21
  31. {admin_panel → brilliance_admin}/schema/table/fields/base.py +21 -21
  32. {admin_panel → brilliance_admin}/schema/table/fields/function_field.py +3 -3
  33. {admin_panel → brilliance_admin}/schema/table/fields_schema.py +9 -9
  34. {admin_panel → brilliance_admin}/schema/table/table_models.py +1 -1
  35. admin_panel/static/index-BeniOHDv.js → brilliance_admin/static/index-D9axz5zK.js +24 -24
  36. {admin_panel → brilliance_admin}/templates/index.html +1 -1
  37. brilliance_admin/translations.py +115 -0
  38. brilliance_admin/utils.py +153 -0
  39. brilliance_admin-0.43.1.dist-info/METADATA +214 -0
  40. brilliance_admin-0.43.1.dist-info/RECORD +74 -0
  41. brilliance_admin-0.43.1.dist-info/top_level.txt +1 -0
  42. admin_panel/api/routers.py +0 -18
  43. admin_panel/static/favicon.jpg +0 -0
  44. admin_panel/translations.py +0 -145
  45. admin_panel/utils.py +0 -50
  46. brilliance_admin-0.42.0.dist-info/METADATA +0 -155
  47. brilliance_admin-0.42.0.dist-info/RECORD +0 -73
  48. brilliance_admin-0.42.0.dist-info/top_level.txt +0 -1
  49. {admin_panel → brilliance_admin}/api/__init__.py +0 -0
  50. {admin_panel → brilliance_admin}/api/views/__init__.py +0 -0
  51. {admin_panel → brilliance_admin}/docs.py +0 -0
  52. {admin_panel → brilliance_admin}/integrations/__init__.py +0 -0
  53. {admin_panel → brilliance_admin}/integrations/sqlalchemy/__init__.py +0 -0
  54. {admin_panel → brilliance_admin}/integrations/sqlalchemy/table/__init__.py +0 -0
  55. {admin_panel → brilliance_admin}/schema/__init__.py +0 -0
  56. {admin_panel → brilliance_admin}/schema/graphs/__init__.py +0 -0
  57. {admin_panel → brilliance_admin}/schema/table/__init__.py +0 -0
  58. {admin_panel → brilliance_admin}/schema/table/fields/__init__.py +0 -0
  59. {admin_panel → brilliance_admin}/static/index-vlBToOhT.css +0 -0
  60. {admin_panel → brilliance_admin}/static/materialdesignicons-webfont-CYDMK1kx.woff2 +0 -0
  61. {admin_panel → brilliance_admin}/static/materialdesignicons-webfont-CgCzGbLl.woff +0 -0
  62. {admin_panel → brilliance_admin}/static/materialdesignicons-webfont-D3kAzl71.ttf +0 -0
  63. {admin_panel → brilliance_admin}/static/materialdesignicons-webfont-DttUABo4.eot +0 -0
  64. {admin_panel → brilliance_admin}/static/tinymce/dark-first/content.min.css +0 -0
  65. {admin_panel → brilliance_admin}/static/tinymce/dark-first/skin.min.css +0 -0
  66. {admin_panel → brilliance_admin}/static/tinymce/dark-slim/content.min.css +0 -0
  67. {admin_panel → brilliance_admin}/static/tinymce/dark-slim/skin.min.css +0 -0
  68. {admin_panel → brilliance_admin}/static/tinymce/img/example.png +0 -0
  69. {admin_panel → brilliance_admin}/static/tinymce/img/tinymce.woff2 +0 -0
  70. {admin_panel → brilliance_admin}/static/tinymce/lightgray/content.min.css +0 -0
  71. {admin_panel → brilliance_admin}/static/tinymce/lightgray/fonts/tinymce.woff +0 -0
  72. {admin_panel → brilliance_admin}/static/tinymce/lightgray/skin.min.css +0 -0
  73. {admin_panel → brilliance_admin}/static/tinymce/plugins/accordion/css/accordion.css +0 -0
  74. {admin_panel → brilliance_admin}/static/tinymce/plugins/accordion/plugin.js +0 -0
  75. {admin_panel → brilliance_admin}/static/tinymce/plugins/codesample/css/prism.css +0 -0
  76. {admin_panel → brilliance_admin}/static/tinymce/plugins/customLink/css/link.css +0 -0
  77. {admin_panel → brilliance_admin}/static/tinymce/plugins/customLink/plugin.js +0 -0
  78. {admin_panel → brilliance_admin}/static/tinymce/tinymce.min.js +0 -0
  79. {admin_panel → brilliance_admin}/static/vanilla-picker-B6E6ObS_.js +0 -0
  80. {brilliance_admin-0.42.0.dist-info → brilliance_admin-0.43.1.dist-info}/WHEEL +0 -0
  81. {brilliance_admin-0.42.0.dist-info → brilliance_admin-0.43.1.dist-info}/licenses/LICENSE +0 -0
@@ -13,7 +13,7 @@
13
13
  <link rel="icon" href="{{ favicon_image }}" />
14
14
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
15
15
  <title>{{ title }}</title>
16
- <script type="module" crossorigin src="/admin/static/index-BeniOHDv.js"></script>
16
+ <script type="module" crossorigin src="/admin/static/index-D9axz5zK.js"></script>
17
17
  <link rel="stylesheet" crossorigin href="/admin/static/index-vlBToOhT.css">
18
18
  </head>
19
19
 
@@ -0,0 +1,115 @@
1
+ import abc
2
+ from importlib import resources
3
+ from typing import Any, Dict
4
+
5
+ import pydantic
6
+ from asgiref.local import Local
7
+ from pydantic_core import core_schema
8
+
9
+ from brilliance_admin.utils import DataclassBase, YamlI18n, get_logger
10
+
11
+ logger = get_logger()
12
+
13
+ _active = Local()
14
+
15
+
16
+ @pydantic.dataclasses.dataclass
17
+ class TranslateText(DataclassBase):
18
+ slug: str
19
+ translation_kwargs: dict | None = None
20
+
21
+ def __init__(self, slug: str):
22
+ self.slug = slug
23
+
24
+ @pydantic.model_serializer(mode='plain')
25
+ def serialize_model(self, info: pydantic.SerializationInfo) -> str:
26
+ ctx = info.context or {}
27
+ language_context = ctx.get('language_context')
28
+
29
+ if not language_context:
30
+ raise AttributeError('language_context is not in context manager for serialization')
31
+
32
+ if not issubclass(type(language_context), LanguageContext):
33
+ raise AttributeError(f'language_context "{type(language_context)}" is not subclass of LanguageContext')
34
+
35
+ return language_context.get_text(self)
36
+
37
+ def __str__(self):
38
+ lm = getattr(_active, '_language_context', None)
39
+ if not lm:
40
+
41
+ raise AttributeError(f'language_context is not in local scope for translation: {locals()}')
42
+
43
+ if not issubclass(type(lm), LanguageContext):
44
+ raise AttributeError(f'language_context "{lm}" is not subclass of LanguageContext')
45
+
46
+ return lm.get_text(self)
47
+
48
+ def __mod__(self, other):
49
+ if not isinstance(other, dict):
50
+ msg = f'TranslateText only dict is supported trough % operand (slug="{self.slug}" other={type(other)})'
51
+ raise AttributeError(msg)
52
+ self.translation_kwargs = other
53
+ return self
54
+
55
+
56
+ class LanguageManager(abc.ABC):
57
+ languages: Dict[str, str] | None
58
+ phrases: YamlI18n = None
59
+
60
+ def __init__(self, languages: str | None, locales_dir: str | None = None):
61
+ self.languages = languages
62
+ self.phrases = YamlI18n()
63
+
64
+ builtin_locales_dir = resources.files("brilliance_admin").joinpath("locales")
65
+ self.phrases.load_folder(builtin_locales_dir)
66
+ logger.info('Language manager builtin dir loaded: %s', builtin_locales_dir)
67
+
68
+ if locales_dir:
69
+ self.phrases.load_folder(locales_dir)
70
+ logger.info('Language manager locales_dir loaded: %s', locales_dir)
71
+
72
+ langs = ', '.join(self.phrases.data.keys())
73
+ logger.info('Language manager setup completed; languages: %s', langs)
74
+
75
+ def get_text(self, text, language) -> str:
76
+ if not isinstance(text, TranslateText):
77
+ return text
78
+
79
+ default_lang = list(self.languages.keys())[0]
80
+
81
+ translation = self.phrases.get_text(text.slug, language or default_lang, default_lang) or text.slug
82
+ if text.translation_kwargs:
83
+ translation %= text.translation_kwargs
84
+
85
+ return translation
86
+
87
+ @classmethod
88
+ def __get_pydantic_core_schema__(cls, source_type: Any, handler: Any) -> core_schema.CoreSchema:
89
+ def validate(v: Any) -> "LanguageManager":
90
+ if isinstance(v, cls):
91
+ return v
92
+ raise TypeError(f"Expected {cls.__name__} instance")
93
+
94
+ return core_schema.no_info_plain_validator_function(
95
+ validate,
96
+ serialization=core_schema.plain_serializer_function_ser_schema(
97
+ lambda v: repr(v),
98
+ info_arg=False,
99
+ return_schema=core_schema.str_schema(),
100
+ ),
101
+ )
102
+
103
+
104
+ class LanguageContext(abc.ABC):
105
+ language: str | None
106
+ language_manager: LanguageManager
107
+
108
+ def __init__(self, language, language_manager):
109
+ self.language = language
110
+ self.language_manager = language_manager
111
+
112
+ _active._language_context = self
113
+
114
+ def get_text(self, text) -> str:
115
+ return self.language_manager.get_text(text, self.language)
@@ -0,0 +1,153 @@
1
+ import logging
2
+ import re
3
+ from pathlib import Path
4
+ from typing import Any, Dict, Protocol
5
+
6
+ import yaml
7
+ from pydantic import TypeAdapter
8
+ from pydantic_core import core_schema
9
+
10
+
11
+ class SupportsStr(Protocol):
12
+ def __str__(self) -> str: ...
13
+
14
+ @classmethod
15
+ def __get_pydantic_core_schema__(cls, source_type, handler):
16
+ def validate(v):
17
+ if not hasattr(v, '__str__'):
18
+ raise TypeError(f'value must implement __str__, got {type(v)}')
19
+ return v
20
+
21
+ return core_schema.no_info_plain_validator_function(validate)
22
+
23
+
24
+ def get_logger():
25
+ try:
26
+ # pylint: disable=import-outside-toplevel
27
+ import structlog
28
+ return structlog.get_logger('brilliance_admin')
29
+ except ImportError:
30
+ return logging.getLogger('brilliance_admin')
31
+
32
+
33
+ class DeserializeAction:
34
+ CREATE = 0
35
+ UPDATE = 1
36
+ TABLE_ACTION = 2
37
+ FILTERS = 3
38
+
39
+
40
+ class DataclassBase:
41
+ def model_dump(self, *args, **kwargs) -> dict:
42
+ adapter = TypeAdapter(type(self))
43
+ return adapter.dump_python(self, *args, **kwargs)
44
+
45
+ def to_dict(self, keep_none=True) -> dict:
46
+ data = self.model_dump()
47
+ return {
48
+ k: v for k, v in data.items()
49
+ if v is not None and not keep_none
50
+ }
51
+
52
+
53
+ def humanize_field_name(name: str) -> str:
54
+ # Convert snake_case / kebab-case / mixed tokens to Title Case with acronyms preserved
55
+ s = name.replace("-", "_")
56
+ parts = [p for p in s.split("_") if p]
57
+
58
+ def cap(token: str) -> str:
59
+ # Keep common acronyms uppercase
60
+ if token.lower() in {"id", "ip", "url", "api", "http", "https", "h2h"}:
61
+ return token.upper()
62
+ # If token contains digits, capitalize first letter only (e.g. "h2h" -> "H2h")
63
+ if re.search(r"\d", token):
64
+ return token[:1].upper() + token[1:].lower()
65
+ return token[:1].upper() + token[1:].lower()
66
+
67
+ return " ".join(cap(p) for p in parts)
68
+
69
+
70
+ def iter_locale_files(directory) -> list[Path]:
71
+ if isinstance(directory, str):
72
+ directory = Path(directory)
73
+
74
+ if not directory.exists():
75
+ raise FileNotFoundError(directory)
76
+ if not directory.is_dir():
77
+ raise NotADirectoryError(directory)
78
+
79
+ for path in directory.rglob('*'):
80
+ if not path.is_file():
81
+ continue
82
+
83
+ yield path
84
+
85
+
86
+ def merge_dict_data(base: dict, extra: dict) -> dict:
87
+ if not isinstance(base, dict):
88
+ raise TypeError('base must be dict')
89
+ if not isinstance(extra, dict):
90
+ raise TypeError('extra must be dict')
91
+
92
+ result = base.copy()
93
+ for key, value in extra.items():
94
+ if key in result and isinstance(result[key], dict) and isinstance(value, dict):
95
+ result[key] = merge_dict_data(result[key], value)
96
+ else:
97
+ result[key] = value
98
+ return result
99
+
100
+
101
+ class YamlI18n:
102
+ data: Dict[str, Any] = {}
103
+
104
+ def load_folder(self, path):
105
+ for file_path in iter_locale_files(path):
106
+ with file_path.open(encoding='utf-8') as f:
107
+ data = yaml.safe_load(f)
108
+
109
+ if not isinstance(data, dict):
110
+ msg = f'YAML root must be dict: {file_path}, got {type(data)}'
111
+ raise TypeError(msg)
112
+
113
+ language = file_path.stem
114
+ if language not in self.data:
115
+ self.data[language] = {}
116
+
117
+ if not isinstance(self.data[language], dict):
118
+ raise TypeError(f'language root must be dict: {language}')
119
+
120
+ self.data[language] = merge_dict_data(self.data[language], data)
121
+
122
+ def get_text(self, slug, language, default_language):
123
+ if not isinstance(slug, str):
124
+ raise TypeError(f'slug must be str, got {type(slug)}')
125
+
126
+ if not isinstance(language, str):
127
+ raise TypeError(f'language must be str, got {type(language)}')
128
+
129
+ if not isinstance(default_language, str):
130
+ raise TypeError(f'default_language must be str, got {type(default_language)}')
131
+
132
+ if not self.data:
133
+ raise ValueError('i18n data is empty')
134
+
135
+ for lang in (language, default_language):
136
+ if lang not in self.data:
137
+ continue
138
+
139
+ node = self.data[lang]
140
+
141
+ for part in slug.split('.'):
142
+ if not isinstance(node, dict):
143
+ node = None
144
+ break
145
+ if part not in node:
146
+ node = None
147
+ break
148
+ node = node[part]
149
+
150
+ if isinstance(node, str):
151
+ return node
152
+
153
+ raise KeyError(f'translation key not found: {slug}')
@@ -0,0 +1,214 @@
1
+ Metadata-Version: 2.4
2
+ Name: brilliance-admin
3
+ Version: 0.43.1
4
+ Summary: Simple and lightweight admin panel framework powered by FastAPI and Vue3 Vuetify together.. Some call it heavenly in its brilliance.
5
+ License-Expression: AGPL-3.0
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 admin panel framework powered by `FastAPI` and `Vue3` `Vuetify` together. \
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 (todo)
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
+ **Key ideas:**
52
+ - **API oriented**\
53
+ Works entirely on FastAPI and provides a prebuilt SPA [frontend](https://github.com/brilliance-admin/frontend) via static files (Vue3 + Vuetify). No separate startup is required.
54
+ > 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.
55
+ - **Rich visualization**\
56
+ Providing rich and convenient ways to display and manage data (tables, charts, etc) from any data source.
57
+ - **ORM**\
58
+ Automatic schema generation and methods for CRUD operations.
59
+ - **Minimal boilerplate**\
60
+ Focused on simplified, but rich configuration.
61
+
62
+ **How it works:**
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:
67
+
68
+ * Tables with full CRUD support, including filtering, sorting, and pagination.
69
+ * Ability to define custom table actions with forms, response messages, and file downloads.
70
+ * Graphs via ChartJS
71
+ * Localization support
72
+ * Adapted for different screen sizes and mobile devices
73
+ * Authorization via any account data source
74
+
75
+ **Integrations:**
76
+ * **SQLAlchemy** - schema autogeneration for tables + CRUD operations + authorization
77
+
78
+ **Planned:**
79
+ * Role-based access control system
80
+ * Nested data support for creation and detail views
81
+ * Django ORM inegration
82
+
83
+ ## How to use it
84
+
85
+ Installation:
86
+ ``` shell
87
+ pip install brilliance-admin
88
+ ```
89
+
90
+ You need to generate `AdminSchema` instance:
91
+ ``` python
92
+ from brilliance_admin import schema
93
+
94
+
95
+ class CategoryExample(schema.CategoryTable):
96
+ "Implementation of get_list and retrieve; update and create are optional"
97
+
98
+
99
+ admin_schema = schema.AdminSchema(
100
+ title='Admin Panel',
101
+ auth=YourAdminAuthentication(),
102
+ groups=[
103
+ schema.Group(
104
+ slug='example',
105
+ title='Example',
106
+ icon='mdi-star',
107
+ categories=[
108
+ CategoryExample(),
109
+ ]
110
+ ),
111
+ ],
112
+ )
113
+
114
+ admin_app = admin_schema.generate_app()
115
+
116
+ # Your FastAPI app
117
+ app = FastAPI()
118
+ app.mount('/admin', admin_app)
119
+ ```
120
+
121
+ ## SQLAlchemy integration
122
+
123
+ Supports automatic schema generation for CRUD tables:
124
+
125
+ ``` python
126
+ category = sqlalchemy.SQLAlchemyAdmin(db_async_session=async_sessionmaker, model=Terminal)
127
+ ```
128
+
129
+ > [!NOTE]
130
+ > If `table_schema` is not specified, it will be generated automatically with all discovered fields and relationships
131
+
132
+ Now, the `category` instance can be passed to `categories`.
133
+
134
+ ### DRF class style schema
135
+
136
+ ``` python
137
+ from brilliance_admin import sqlalchemy
138
+ from brilliance_admin.translations import TranslateText as _
139
+
140
+ from your_project.models import Terminal
141
+
142
+
143
+ class TerminalFiltersSchema(sqlalchemy.SQLAlchemyFieldsSchema):
144
+ model = Terminal
145
+ fields = ['id', 'created_at']
146
+ created_at = schema.DateTimeField(range=True)
147
+
148
+
149
+ class TerminalSchema(sqlalchemy.SQLAlchemyFieldsSchema):
150
+ model = Terminal
151
+ list_display = ['id', 'merchant_id']
152
+
153
+
154
+ class TerminalAdmin(sqlalchemy.SQLAlchemyAdmin):
155
+ db_async_session = async_sessionmaker
156
+ model = Terminal
157
+ title = _('terminals')
158
+ icon = 'mdi-console-network-outline'
159
+
160
+ ordering_fields = ['id']
161
+ search_fields = ['id', 'title']
162
+
163
+ table_schema = TerminalSchema()
164
+ table_filters = TerminalFiltersSchema()
165
+
166
+
167
+ category = TerminalAdmin()
168
+ ```
169
+
170
+ ### Can be used both via inheritance and instancing
171
+
172
+ Optionally, functional-style generation can be used to reduce boilerplate code
173
+
174
+ Availiable for `SQLAlchemyAdmin` and `SQLAlchemyFieldsSchema`
175
+
176
+ ``` python
177
+ category = sqlalchemy.SQLAlchemyAdmin(
178
+ db_async_session=async_sessionmaker,
179
+ model=Terminal,
180
+
181
+ table_schema = sqlalchemy.SQLAlchemyFieldsSchema(
182
+ model=Terminal,
183
+ list_display=['id', 'merchant_id'],
184
+ ),
185
+ table_filters = sqlalchemy.SQLAlchemyFieldsSchema(
186
+ model=Terminal,
187
+ fields=['id', 'created_at'],
188
+ created_at=schema.DateTimeField(range=True),
189
+ ),
190
+ )
191
+ ```
192
+
193
+ ### SQLAlchemy JWT Authentication
194
+
195
+ ``` python
196
+ auth = sqlalchemy.SQLAlchemyJWTAdminAuthentication(
197
+ secret='auth_secret',
198
+ db_async_session=async_session,
199
+ user_model=User,
200
+ )
201
+ ```
202
+
203
+ ## Comparison of Similar Projects
204
+
205
+ | Criterion | Brilliance Admin | Django Admin/Unfold | FastAPI Admin | Starlette Admin |
206
+ |---------|------------------|---------------|---------------|-----------------|
207
+ | Base framework | FastAPI | Django | FastAPI | Starlette / FastAPI |
208
+ | Rendering model | Prebuilt Vue 3 + Vuetify SPA + Jinja2 | Server-side Django templates | Server-side Jinja2 templates + Tabler UI | Server-side Jinja2 templates + Tabler UI |
209
+ | Frontend architecture | Separate frontend (SPA) | Classic server-rendered UI | Server-rendered UI with JS interactivity | Server-rendered UI with JS interactivity |
210
+ | Data source | Any source + SQLAlchemy | Django ORM | Tortoise ORM | Any source + SQLAlchemy, MongoDB |
211
+ | Multiple databases per model | Yes | Database routers | No (global engine) | Yes (session per ModelView) |
212
+ | Schema generation | User-defined format | From Django models | From ORM models | User-defined format |
213
+ | Async support | Yes | No | Yes | Yes |
214
+ | API-first approach | Yes | No | Partially | Partially |
@@ -0,0 +1,74 @@
1
+ brilliance_admin/__init__.py,sha256=qxGzLVhFNm2FKL2lVt7bEFb6pTPqyXEQvUsDfyfv6SM,183
2
+ brilliance_admin/auth.py,sha256=d57XRfLJIbOosLP1-0SCFkePPT8M5WhLcwxu4yW92RA,673
3
+ brilliance_admin/docs.py,sha256=fKeJKuiCCi1jHRmNcmkuDD6_2di7bwc6-w8V1VCTu0s,1231
4
+ brilliance_admin/exceptions.py,sha256=7_L3qVTwdLrzmDJjGv2yqCOVECP35wh0NyTvgjP7ETc,913
5
+ brilliance_admin/translations.py,sha256=9IHS7ld0dghjIvHgko4ueRMoJ53VcblcFCEkgxQEG4A,3847
6
+ brilliance_admin/utils.py,sha256=C5Rc7DUC9HnGIu4R9rdNGGtvZ3vrkWBNoDr-ijCPD3w,4593
7
+ brilliance_admin/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ brilliance_admin/api/routers.py,sha256=GXz-GFXRH5VbDH1r5O9LLnnmaMhbQq4RctzVEubHnsk,810
9
+ brilliance_admin/api/utils.py,sha256=ezvHK49OlpCdT9fLB41Y1nswZDLdzC3zTcQtGtuTYUk,999
10
+ brilliance_admin/api/views/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
+ brilliance_admin/api/views/auth.py,sha256=Qmd9KuFqCeNhQukYl0W62kcUYPMtgMekjkaTvz1AArM,1133
12
+ brilliance_admin/api/views/autocomplete.py,sha256=oJbt-xD4sKnOyQIxT0mFqyRvPxFk9KUyJabKzLY888s,1508
13
+ brilliance_admin/api/views/graphs.py,sha256=nn91zoFRZ0FvSkFJoTy7ZQEr8B2cUj43nd8OxPsSmTQ,1351
14
+ brilliance_admin/api/views/index.py,sha256=DkMshrs5zmqpbbF9G2RhqeLGFKMrkrAmQ6nnba7aLl8,1400
15
+ brilliance_admin/api/views/schema.py,sha256=MS9v9Qy3cRO9gGs4uW2PNRBS6Uw48sLRGRe49jnJ2yM,1019
16
+ brilliance_admin/api/views/settings.py,sha256=2A9suZQONEtW9LkFban29Fe5ipQaaGT0CzpxnbuotJQ,1166
17
+ brilliance_admin/api/views/table.py,sha256=29vLEwueHWBS8Dq6nBvujwy4CTs9v8ZcM1ErF9lYgIc,5932
18
+ brilliance_admin/integrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
+ brilliance_admin/integrations/sqlalchemy/__init__.py,sha256=tIegMU2eIW_zA-PdblCXCjenHAY4sGBUWKuz97ns4gE,275
20
+ brilliance_admin/integrations/sqlalchemy/auth.py,sha256=99O7zmhcKWB3cWGTbMd3OSyIJ81Vcfz7byDK6oLinm8,4910
21
+ brilliance_admin/integrations/sqlalchemy/autocomplete.py,sha256=baRroMwGg35uR5zt-aiQc2-ugX4hpwNf79R6bZH9UX0,1416
22
+ brilliance_admin/integrations/sqlalchemy/fields.py,sha256=AayeFmwKA5JooiCok7wPLJNSDQCiy5p-iQjrr9HMMSw,9412
23
+ brilliance_admin/integrations/sqlalchemy/fields_schema.py,sha256=fZu7I77POvcUHBVc7i0yKGZcAzDumVGMpsbX7TMByo4,10914
24
+ brilliance_admin/integrations/sqlalchemy/table/__init__.py,sha256=g_in2pLTi8UQnT5uNFA8mLW5mxlT84irQ7yVaP_OSS4,605
25
+ brilliance_admin/integrations/sqlalchemy/table/base.py,sha256=nZzHk56NyhfTll9vX9j5zzA557mLdQ-u-GccTFXIFSs,4873
26
+ brilliance_admin/integrations/sqlalchemy/table/create.py,sha256=z40PuKJYZ6goJdhsANVuEyTW1gh0PipCBdBSe5Ii5Tw,2693
27
+ brilliance_admin/integrations/sqlalchemy/table/delete.py,sha256=Awv50IG7DQsYrHVlk4dtYBMwexmlO92xhMBgrgEDBgs,731
28
+ brilliance_admin/integrations/sqlalchemy/table/list.py,sha256=--KVTLbuCr1fD79q0LXqlu6CuiYA3QhQj6rrdP7jR14,6692
29
+ brilliance_admin/integrations/sqlalchemy/table/retrieve.py,sha256=b-Kyft1ecG2nAj8krTumyeY2LaONBGgBwyfLayL-ilw,2140
30
+ brilliance_admin/integrations/sqlalchemy/table/update.py,sha256=AGEEbEsgKre784Dg4_kTKB6NfMwdAwNiMjm0ZKeg-LE,3543
31
+ brilliance_admin/locales/en.yml,sha256=lCM58SoP4HExXFCTHpl8pYw82tH_8TvlrcCXZDXYb5c,1129
32
+ brilliance_admin/locales/ru.yml,sha256=hXZwS71Z_aAyK9DB14pwmbtLI5xn6PvsnuLIZlEexbM,1661
33
+ brilliance_admin/schema/__init__.py,sha256=X-izShvv84jkFU47WfpUwtvRh3NOv570iUB3NRNEIDU,248
34
+ brilliance_admin/schema/admin_schema.py,sha256=fKVtaLkFkDF8ZkjIftackZ4rfCDcqLvci8PO6ZLycgc,6393
35
+ brilliance_admin/schema/category.py,sha256=Y2DuDmSve_-Lnx3zOQlbVcslywJhcwV49z1bOhSKIGs,4105
36
+ brilliance_admin/schema/group.py,sha256=Gdz3ME_EfQP_CFPou6OOJZWGWXKIYgB3zNX_oWsClwE,2240
37
+ brilliance_admin/schema/graphs/__init__.py,sha256=qvmZv9QWdiutPtN5VYQLYbsjY2SOg8p_XRaz2rUlIxY,44
38
+ brilliance_admin/schema/graphs/category_graphs.py,sha256=2nj_oiAoGXwGhc-gNVNFMNKDCkCdUWVn6u9CRCsb2DA,1513
39
+ brilliance_admin/schema/table/__init__.py,sha256=vuRw8HBuak2LaTZi2dNn5YOrJPalQps-O3Ht-d0AZV4,378
40
+ brilliance_admin/schema/table/admin_action.py,sha256=0ymRL9DKkBK-AF6wKy7K9R4hkmblh55eHuZA_rjO1Lk,2018
41
+ brilliance_admin/schema/table/category_table.py,sha256=BNdxspZ9Di6_bX0QQEGxgZLlddC7GIAWjBYha6bLtZo,6449
42
+ brilliance_admin/schema/table/fields_schema.py,sha256=PvNHIYqDeLvM2qy32vCmLQ6VORl7bJQhSVcGUP1LIEw,8276
43
+ brilliance_admin/schema/table/table_models.py,sha256=xidraifRYbXGkiVLn6dJ96dkOhW8-22ynE-fbiOjfAU,1018
44
+ brilliance_admin/schema/table/fields/__init__.py,sha256=RW-sIFTAaSQo4mMR6iWtnefogWPjmg6KAsDwe9mKW1k,291
45
+ brilliance_admin/schema/table/fields/base.py,sha256=gocPyNZvLFpv_3VXA8ptCcZKK8uVFlTiwzgiuOnN1FI,7791
46
+ brilliance_admin/schema/table/fields/function_field.py,sha256=4fm9kS8zpBG5oqp9sA81NQDHiqvU0BQmpf-wjkTuuwM,1780
47
+ brilliance_admin/static/index-D9axz5zK.js,sha256=KB_EHC9hoBK_LinkeMaE897Ws4Tl8xlJ8c_smbis_VE,3202111
48
+ brilliance_admin/static/index-vlBToOhT.css,sha256=hoVCpcStTHdAVRm37k1umrNdXjOwIIveu9lxk4ocpEc,983028
49
+ brilliance_admin/static/materialdesignicons-webfont-CYDMK1kx.woff2,sha256=5S1g9kJnzaoIQitQurXUW9NeZisDua91F5zq4ArF_Is,385360
50
+ brilliance_admin/static/materialdesignicons-webfont-CgCzGbLl.woff,sha256=SNPuxqtw3HoZCPm6LyCOClhxi57hbj9qvbXbT0Yfolg,561776
51
+ brilliance_admin/static/materialdesignicons-webfont-D3kAzl71.ttf,sha256=vXJaejiTnltZkE4benJlkZ7OwlYWbs5p1RXCEAUWWQc,1243500
52
+ brilliance_admin/static/materialdesignicons-webfont-DttUABo4.eot,sha256=hhrqBUjMya-Y3sUgvjQLji1L65e6NIk6ya9RGkSeP6I,1243720
53
+ brilliance_admin/static/vanilla-picker-B6E6ObS_.js,sha256=aVVU5PLkFXbK22gMndNsa5QNWtZxQBEqtd2Fp8HYfwE,18804
54
+ brilliance_admin/static/tinymce/tinymce.min.js,sha256=4KlST3LusLGTHzCqhWmoENGfb4aeSvmj9IlL-v6ATeg,1171290
55
+ brilliance_admin/static/tinymce/dark-first/content.min.css,sha256=tCWU8dU3Y4eejwO9fjZ22eIwrq8q5iFSlyR8FlQ8-dU,4788
56
+ brilliance_admin/static/tinymce/dark-first/skin.min.css,sha256=XaCMxHAKdwqA0HyM2l_vvA1KRwhOC7bbX3Bz2z0f1jw,50376
57
+ brilliance_admin/static/tinymce/dark-slim/content.min.css,sha256=fyLDj0yRBtk8ypK6LL3aI2PHyH6x7ISDncpbHOuvwPI,4730
58
+ brilliance_admin/static/tinymce/dark-slim/skin.min.css,sha256=tzEwzhO0ToVn_l1AkBRK6vwz9dSqjEpQbTNao12Kqy0,49965
59
+ brilliance_admin/static/tinymce/img/example.png,sha256=R18xquUdQyQvA6dFZUIXkLucVO2mMJpgV1oHI4cFQJ0,14708
60
+ brilliance_admin/static/tinymce/img/tinymce.woff2,sha256=_XmHMFsFYH9PlgREy6LOzYYFgvs2fGIMcr2vXt2Q-ik,15764
61
+ brilliance_admin/static/tinymce/lightgray/content.min.css,sha256=UgkDCoTokZ99doSjtoycaZAZVjO00I1XikWjBpWf9NI,3193
62
+ brilliance_admin/static/tinymce/lightgray/skin.min.css,sha256=ypP9oqgJwhKl2-B_ATE6uC77pqPjVYP6vz7EzBuiiSU,38232
63
+ brilliance_admin/static/tinymce/lightgray/fonts/tinymce.woff,sha256=o69L1waoOPHkqgihbpfjT_qTB8PJsZJeJTR7aL8IH8M,7664
64
+ brilliance_admin/static/tinymce/plugins/accordion/plugin.js,sha256=jf3gA0MkE2Gshhe8pTJP5JUVI56G3YlBZQSoevPWeuc,1251
65
+ brilliance_admin/static/tinymce/plugins/accordion/css/accordion.css,sha256=u5UQkMNA9fboQgOOuJCgoJ3Dm-9vDYDJDAmpPbB-yWM,282
66
+ brilliance_admin/static/tinymce/plugins/codesample/css/prism.css,sha256=exAdMtHbvwW7-DEs567MX65Fq1aJQTfREP5pw8gW-AY,1736
67
+ brilliance_admin/static/tinymce/plugins/customLink/plugin.js,sha256=illBNpnHDkBsLG6wo_jDPF6z7CGnO1MQWUoDwZKy6vQ,5589
68
+ brilliance_admin/static/tinymce/plugins/customLink/css/link.css,sha256=gh5nvY8Z92hJfCEBPnIm4jIPCcKKbJnab-30oIfX7Hc,56
69
+ brilliance_admin/templates/index.html,sha256=ZLJ_TKUvBDIo_hYfbW43ov0S_bFrzBF-283XP6BKtDI,1294
70
+ brilliance_admin-0.43.1.dist-info/licenses/LICENSE,sha256=PjeDRXGbVLtKul5Xpfco_6CyB6bYGWVVPrO0oubquuM,727
71
+ brilliance_admin-0.43.1.dist-info/METADATA,sha256=SgnEDyBA7AYrXHFP-T0K906C-rurqdqKHDkgr6Tl72s,7459
72
+ brilliance_admin-0.43.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
73
+ brilliance_admin-0.43.1.dist-info/top_level.txt,sha256=almFFSWrVYieI3i54hYL0fMUaeuIYiazS2Kx4wtK-ns,17
74
+ brilliance_admin-0.43.1.dist-info/RECORD,,
@@ -0,0 +1 @@
1
+ brilliance_admin
@@ -1,18 +0,0 @@
1
- from fastapi import APIRouter
2
-
3
- from .views.schema import router as schema_router
4
- from .views.table import router as schema_table
5
- from .views.auth import router as schema_auth
6
- from .views.autocomplete import router as schema_autocomplete
7
- from .views.graphs import router as schema_graphs
8
- from .views.settings import router as schema_settings
9
- from .views.index import router as schema_index
10
-
11
- admin_panel_router = APIRouter()
12
- admin_panel_router.include_router(schema_router)
13
- admin_panel_router.include_router(schema_table)
14
- admin_panel_router.include_router(schema_auth)
15
- admin_panel_router.include_router(schema_autocomplete)
16
- admin_panel_router.include_router(schema_graphs)
17
- admin_panel_router.include_router(schema_settings)
18
- admin_panel_router.include_router(schema_index)
Binary file