polyadmin 0.1.0b1__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 (209) hide show
  1. polyadmin-0.1.0b1/.github/workflows/notify-docs.yml +21 -0
  2. polyadmin-0.1.0b1/.github/workflows/publish.yml +32 -0
  3. polyadmin-0.1.0b1/.gitignore +17 -0
  4. polyadmin-0.1.0b1/LICENSE +21 -0
  5. polyadmin-0.1.0b1/PKG-INFO +239 -0
  6. polyadmin-0.1.0b1/README.md +209 -0
  7. polyadmin-0.1.0b1/babel.cfg +4 -0
  8. polyadmin-0.1.0b1/browsertests/README.md +31 -0
  9. polyadmin-0.1.0b1/browsertests/conftest.py +126 -0
  10. polyadmin-0.1.0b1/browsertests/test_deletes.py +61 -0
  11. polyadmin-0.1.0b1/browsertests/test_flows.py +419 -0
  12. polyadmin-0.1.0b1/browsertests/test_i18n.py +199 -0
  13. polyadmin-0.1.0b1/browsertests/test_parity.py +90 -0
  14. polyadmin-0.1.0b1/docs/architecture.md +104 -0
  15. polyadmin-0.1.0b1/docs/audit.md +60 -0
  16. polyadmin-0.1.0b1/docs/authentication.md +224 -0
  17. polyadmin-0.1.0b1/docs/components.md +219 -0
  18. polyadmin-0.1.0b1/docs/concepts/architecture.md +28 -0
  19. polyadmin-0.1.0b1/docs/concepts/model-admin.md +21 -0
  20. polyadmin-0.1.0b1/docs/dashboard.md +148 -0
  21. polyadmin-0.1.0b1/docs/deletes.md +188 -0
  22. polyadmin-0.1.0b1/docs/exports.md +74 -0
  23. polyadmin-0.1.0b1/docs/i18n.md +327 -0
  24. polyadmin-0.1.0b1/docs/inlines.md +124 -0
  25. polyadmin-0.1.0b1/docs/lists.md +238 -0
  26. polyadmin-0.1.0b1/docs/model-admin.md +491 -0
  27. polyadmin-0.1.0b1/docs/permissions.md +124 -0
  28. polyadmin-0.1.0b1/docs/python/index.md +18 -0
  29. polyadmin-0.1.0b1/docs/routing.md +181 -0
  30. polyadmin-0.1.0b1/docs/templates.md +174 -0
  31. polyadmin-0.1.0b1/examples/README.md +8 -0
  32. polyadmin-0.1.0b1/examples/fastapi/README.md +55 -0
  33. polyadmin-0.1.0b1/examples/fastapi/main.py +140 -0
  34. polyadmin-0.1.0b1/examples/fastapi/models.py +219 -0
  35. polyadmin-0.1.0b1/examples/fastapi/organization_admin.py +131 -0
  36. polyadmin-0.1.0b1/examples/fastapi/pages.py +42 -0
  37. polyadmin-0.1.0b1/examples/fastapi/pyproject.toml +27 -0
  38. polyadmin-0.1.0b1/examples/fastapi/role_admin.py +58 -0
  39. polyadmin-0.1.0b1/examples/fastapi/session.py +214 -0
  40. polyadmin-0.1.0b1/examples/fastapi/templates/pages/broadcast.html +52 -0
  41. polyadmin-0.1.0b1/examples/fastapi/tests/test_app.py +314 -0
  42. polyadmin-0.1.0b1/examples/fastapi/user_admin.py +200 -0
  43. polyadmin-0.1.0b1/examples/fastapi/uv.lock +1154 -0
  44. polyadmin-0.1.0b1/mkdocs.yml +72 -0
  45. polyadmin-0.1.0b1/polyadmin/__init__.py +47 -0
  46. polyadmin-0.1.0b1/polyadmin/core/__init__.py +0 -0
  47. polyadmin-0.1.0b1/polyadmin/core/_async.py +21 -0
  48. polyadmin-0.1.0b1/polyadmin/core/action.py +135 -0
  49. polyadmin-0.1.0b1/polyadmin/core/admin.py +129 -0
  50. polyadmin-0.1.0b1/polyadmin/core/audit.py +65 -0
  51. polyadmin-0.1.0b1/polyadmin/core/auth.py +52 -0
  52. polyadmin-0.1.0b1/polyadmin/core/authorization.py +48 -0
  53. polyadmin-0.1.0b1/polyadmin/core/csrf.py +70 -0
  54. polyadmin-0.1.0b1/polyadmin/core/dashboard.py +41 -0
  55. polyadmin-0.1.0b1/polyadmin/core/delete.py +146 -0
  56. polyadmin-0.1.0b1/polyadmin/core/exporter.py +136 -0
  57. polyadmin-0.1.0b1/polyadmin/core/field.py +201 -0
  58. polyadmin-0.1.0b1/polyadmin/core/filter.py +305 -0
  59. polyadmin-0.1.0b1/polyadmin/core/inline.py +97 -0
  60. polyadmin-0.1.0b1/polyadmin/core/login.py +110 -0
  61. polyadmin-0.1.0b1/polyadmin/core/model_admin.py +349 -0
  62. polyadmin-0.1.0b1/polyadmin/core/page.py +62 -0
  63. polyadmin-0.1.0b1/polyadmin/core/pagination.py +70 -0
  64. polyadmin-0.1.0b1/polyadmin/core/query.py +243 -0
  65. polyadmin-0.1.0b1/polyadmin/core/relation.py +40 -0
  66. polyadmin-0.1.0b1/polyadmin/core/slug.py +57 -0
  67. polyadmin-0.1.0b1/polyadmin/core/template_context.py +679 -0
  68. polyadmin-0.1.0b1/polyadmin/core/widget.py +280 -0
  69. polyadmin-0.1.0b1/polyadmin/fastapi/__init__.py +3 -0
  70. polyadmin-0.1.0b1/polyadmin/fastapi/audit.py +52 -0
  71. polyadmin-0.1.0b1/polyadmin/fastapi/auth.py +112 -0
  72. polyadmin-0.1.0b1/polyadmin/fastapi/csrf.py +93 -0
  73. polyadmin-0.1.0b1/polyadmin/fastapi/deletes.py +76 -0
  74. polyadmin-0.1.0b1/polyadmin/fastapi/errors.py +93 -0
  75. polyadmin-0.1.0b1/polyadmin/fastapi/handlers.py +798 -0
  76. polyadmin-0.1.0b1/polyadmin/fastapi/inlines.py +177 -0
  77. polyadmin-0.1.0b1/polyadmin/fastapi/locale.py +128 -0
  78. polyadmin-0.1.0b1/polyadmin/fastapi/login.py +110 -0
  79. polyadmin-0.1.0b1/polyadmin/fastapi/pages.py +97 -0
  80. polyadmin-0.1.0b1/polyadmin/fastapi/relations.py +264 -0
  81. polyadmin-0.1.0b1/polyadmin/fastapi/responses.py +55 -0
  82. polyadmin-0.1.0b1/polyadmin/fastapi/router.py +174 -0
  83. polyadmin-0.1.0b1/polyadmin/fastapi/static.py +21 -0
  84. polyadmin-0.1.0b1/polyadmin/i18n/__init__.py +50 -0
  85. polyadmin-0.1.0b1/polyadmin/i18n/context.py +54 -0
  86. polyadmin-0.1.0b1/polyadmin/i18n/negotiation.py +56 -0
  87. polyadmin-0.1.0b1/polyadmin/i18n/setup.py +81 -0
  88. polyadmin-0.1.0b1/polyadmin/i18n/translator.py +124 -0
  89. polyadmin-0.1.0b1/polyadmin/locale/fr/LC_MESSAGES/polyadmin.mo +0 -0
  90. polyadmin-0.1.0b1/polyadmin/locale/fr/LC_MESSAGES/polyadmin.po +486 -0
  91. polyadmin-0.1.0b1/polyadmin/locale/polyadmin.pot +485 -0
  92. polyadmin-0.1.0b1/polyadmin/locale/ru/LC_MESSAGES/polyadmin.mo +0 -0
  93. polyadmin-0.1.0b1/polyadmin/locale/ru/LC_MESSAGES/polyadmin.po +496 -0
  94. polyadmin-0.1.0b1/polyadmin/templates/admin/base.html +91 -0
  95. polyadmin-0.1.0b1/polyadmin/templates/admin/components/action_confirm_modal.html +86 -0
  96. polyadmin-0.1.0b1/polyadmin/templates/admin/components/csrf-field.html +5 -0
  97. polyadmin-0.1.0b1/polyadmin/templates/admin/components/error_fragment.html +6 -0
  98. polyadmin-0.1.0b1/polyadmin/templates/admin/components/field.html +60 -0
  99. polyadmin-0.1.0b1/polyadmin/templates/admin/components/form_wrapper.html +131 -0
  100. polyadmin-0.1.0b1/polyadmin/templates/admin/components/icons.html +67 -0
  101. polyadmin-0.1.0b1/polyadmin/templates/admin/components/inline.html +251 -0
  102. polyadmin-0.1.0b1/polyadmin/templates/admin/components/inline_fragment.html +2 -0
  103. polyadmin-0.1.0b1/polyadmin/templates/admin/components/list_content.html +54 -0
  104. polyadmin-0.1.0b1/polyadmin/templates/admin/components/lookup_results.html +19 -0
  105. polyadmin-0.1.0b1/polyadmin/templates/admin/components/search.html +19 -0
  106. polyadmin-0.1.0b1/polyadmin/templates/admin/components/toasts.html +151 -0
  107. polyadmin-0.1.0b1/polyadmin/templates/admin/components/ui/breadcrumb.html +30 -0
  108. polyadmin-0.1.0b1/polyadmin/templates/admin/components/ui/bulk-actions.html +69 -0
  109. polyadmin-0.1.0b1/polyadmin/templates/admin/components/ui/calendar.html +175 -0
  110. polyadmin-0.1.0b1/polyadmin/templates/admin/components/ui/combobox.html +82 -0
  111. polyadmin-0.1.0b1/polyadmin/templates/admin/components/ui/delete-preview.html +37 -0
  112. polyadmin-0.1.0b1/polyadmin/templates/admin/components/ui/dropdown-menu.html +71 -0
  113. polyadmin-0.1.0b1/polyadmin/templates/admin/components/ui/field.html +110 -0
  114. polyadmin-0.1.0b1/polyadmin/templates/admin/components/ui/filter-panel.html +155 -0
  115. polyadmin-0.1.0b1/polyadmin/templates/admin/components/ui/locale-switcher.html +30 -0
  116. polyadmin-0.1.0b1/polyadmin/templates/admin/components/ui/multi-select.html +253 -0
  117. polyadmin-0.1.0b1/polyadmin/templates/admin/components/ui/pagination.html +81 -0
  118. polyadmin-0.1.0b1/polyadmin/templates/admin/components/ui/radio-group.html +28 -0
  119. polyadmin-0.1.0b1/polyadmin/templates/admin/components/ui/select.html +165 -0
  120. polyadmin-0.1.0b1/polyadmin/templates/admin/components/ui/sidebar.html +175 -0
  121. polyadmin-0.1.0b1/polyadmin/templates/admin/components/ui/slider.html +22 -0
  122. polyadmin-0.1.0b1/polyadmin/templates/admin/components/ui/switch.html +36 -0
  123. polyadmin-0.1.0b1/polyadmin/templates/admin/components/ui/table.html +221 -0
  124. polyadmin-0.1.0b1/polyadmin/templates/admin/components/ui/theme-toggle.html +33 -0
  125. polyadmin-0.1.0b1/polyadmin/templates/admin/dashboard.html +35 -0
  126. polyadmin-0.1.0b1/polyadmin/templates/admin/error.html +33 -0
  127. polyadmin-0.1.0b1/polyadmin/templates/admin/login.html +94 -0
  128. polyadmin-0.1.0b1/polyadmin/templates/admin/resource/delete.html +29 -0
  129. polyadmin-0.1.0b1/polyadmin/templates/admin/resource/delete_selected.html +49 -0
  130. polyadmin-0.1.0b1/polyadmin/templates/admin/resource/detail.html +78 -0
  131. polyadmin-0.1.0b1/polyadmin/templates/admin/resource/form.html +5 -0
  132. polyadmin-0.1.0b1/polyadmin/templates/admin/resource/list.html +5 -0
  133. polyadmin-0.1.0b1/polyadmin/templates/admin/theme.html +372 -0
  134. polyadmin-0.1.0b1/polyadmin/templates/admin/widgets/activity.html +8 -0
  135. polyadmin-0.1.0b1/polyadmin/templates/admin/widgets/chart.html +15 -0
  136. polyadmin-0.1.0b1/polyadmin/templates/admin/widgets/donut.html +59 -0
  137. polyadmin-0.1.0b1/polyadmin/templates/admin/widgets/metric.html +1 -0
  138. polyadmin-0.1.0b1/polyadmin/templates/admin/widgets/progress.html +7 -0
  139. polyadmin-0.1.0b1/polyadmin/templates/admin/widgets/stat.html +22 -0
  140. polyadmin-0.1.0b1/polyadmin/templates/admin/widgets/table.html +29 -0
  141. polyadmin-0.1.0b1/polyadmin/templates/admin/widgets/tabs.html +34 -0
  142. polyadmin-0.1.0b1/polyadmin/templates/admin/widgets/timeline.html +21 -0
  143. polyadmin-0.1.0b1/polyadmin/templating.py +528 -0
  144. polyadmin-0.1.0b1/polyadmin/ui.py +817 -0
  145. polyadmin-0.1.0b1/pyproject.toml +64 -0
  146. polyadmin-0.1.0b1/tests/__init__.py +0 -0
  147. polyadmin-0.1.0b1/tests/conftest.py +49 -0
  148. polyadmin-0.1.0b1/tests/core/__init__.py +0 -0
  149. polyadmin-0.1.0b1/tests/core/mo.py +34 -0
  150. polyadmin-0.1.0b1/tests/core/test_action_decorator.py +173 -0
  151. polyadmin-0.1.0b1/tests/core/test_action_placement.py +66 -0
  152. polyadmin-0.1.0b1/tests/core/test_admin.py +88 -0
  153. polyadmin-0.1.0b1/tests/core/test_async.py +18 -0
  154. polyadmin-0.1.0b1/tests/core/test_auth.py +17 -0
  155. polyadmin-0.1.0b1/tests/core/test_authorization.py +26 -0
  156. polyadmin-0.1.0b1/tests/core/test_csrf.py +65 -0
  157. polyadmin-0.1.0b1/tests/core/test_dashboard.py +42 -0
  158. polyadmin-0.1.0b1/tests/core/test_delete.py +201 -0
  159. polyadmin-0.1.0b1/tests/core/test_delete_selected_i18n.py +24 -0
  160. polyadmin-0.1.0b1/tests/core/test_exporter.py +131 -0
  161. polyadmin-0.1.0b1/tests/core/test_field.py +82 -0
  162. polyadmin-0.1.0b1/tests/core/test_filter.py +269 -0
  163. polyadmin-0.1.0b1/tests/core/test_i18n.py +194 -0
  164. polyadmin-0.1.0b1/tests/core/test_inline.py +185 -0
  165. polyadmin-0.1.0b1/tests/core/test_login.py +55 -0
  166. polyadmin-0.1.0b1/tests/core/test_model_admin.py +217 -0
  167. polyadmin-0.1.0b1/tests/core/test_pagination.py +34 -0
  168. polyadmin-0.1.0b1/tests/core/test_parity.py +199 -0
  169. polyadmin-0.1.0b1/tests/core/test_query.py +193 -0
  170. polyadmin-0.1.0b1/tests/core/test_relation.py +59 -0
  171. polyadmin-0.1.0b1/tests/core/test_template_context.py +173 -0
  172. polyadmin-0.1.0b1/tests/core/test_widget.py +108 -0
  173. polyadmin-0.1.0b1/tests/fastapi/__init__.py +0 -0
  174. polyadmin-0.1.0b1/tests/fastapi/test_action_placement.py +61 -0
  175. polyadmin-0.1.0b1/tests/fastapi/test_actions.py +414 -0
  176. polyadmin-0.1.0b1/tests/fastapi/test_async_model_admin.py +114 -0
  177. polyadmin-0.1.0b1/tests/fastapi/test_async_relations.py +402 -0
  178. polyadmin-0.1.0b1/tests/fastapi/test_audit.py +145 -0
  179. polyadmin-0.1.0b1/tests/fastapi/test_auth.py +234 -0
  180. polyadmin-0.1.0b1/tests/fastapi/test_code_strings.py +135 -0
  181. polyadmin-0.1.0b1/tests/fastapi/test_components.py +535 -0
  182. polyadmin-0.1.0b1/tests/fastapi/test_csrf.py +128 -0
  183. polyadmin-0.1.0b1/tests/fastapi/test_dashboard.py +212 -0
  184. polyadmin-0.1.0b1/tests/fastapi/test_delete_preview.py +164 -0
  185. polyadmin-0.1.0b1/tests/fastapi/test_delete_selected.py +88 -0
  186. polyadmin-0.1.0b1/tests/fastapi/test_delete_selected_preview.py +100 -0
  187. polyadmin-0.1.0b1/tests/fastapi/test_errors.py +74 -0
  188. polyadmin-0.1.0b1/tests/fastapi/test_export.py +94 -0
  189. polyadmin-0.1.0b1/tests/fastapi/test_filters.py +160 -0
  190. polyadmin-0.1.0b1/tests/fastapi/test_htmx.py +126 -0
  191. polyadmin-0.1.0b1/tests/fastapi/test_inlines.py +407 -0
  192. polyadmin-0.1.0b1/tests/fastapi/test_list_querier.py +124 -0
  193. polyadmin-0.1.0b1/tests/fastapi/test_listopts.py +143 -0
  194. polyadmin-0.1.0b1/tests/fastapi/test_locale.py +204 -0
  195. polyadmin-0.1.0b1/tests/fastapi/test_login.py +353 -0
  196. polyadmin-0.1.0b1/tests/fastapi/test_pages.py +94 -0
  197. polyadmin-0.1.0b1/tests/fastapi/test_parity_list.py +300 -0
  198. polyadmin-0.1.0b1/tests/fastapi/test_pseudo_locale.py +227 -0
  199. polyadmin-0.1.0b1/tests/fastapi/test_relations.py +459 -0
  200. polyadmin-0.1.0b1/tests/fastapi/test_router.py +192 -0
  201. polyadmin-0.1.0b1/tests/fastapi/test_scroll_area.py +128 -0
  202. polyadmin-0.1.0b1/tests/fastapi/test_switcher.py +105 -0
  203. polyadmin-0.1.0b1/tests/fastapi/test_theme.py +122 -0
  204. polyadmin-0.1.0b1/tests/test_catalogs.py +146 -0
  205. polyadmin-0.1.0b1/tests/test_format.py +109 -0
  206. polyadmin-0.1.0b1/tests/test_layout.py +90 -0
  207. polyadmin-0.1.0b1/tests/test_templating.py +180 -0
  208. polyadmin-0.1.0b1/tests/test_ui.py +213 -0
  209. polyadmin-0.1.0b1/uv.lock +939 -0
@@ -0,0 +1,21 @@
1
+ name: Notify documentation site
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ paths:
7
+ - docs/**
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ jobs:
13
+ notify:
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - name: Trigger documentation rebuild
17
+ uses: peter-evans/repository-dispatch@v3
18
+ with:
19
+ token: ${{ secrets.DOCS_REPO_TOKEN }}
20
+ repository: MagicRodri/polyadmin-docs
21
+ event-type: source-docs-updated
@@ -0,0 +1,32 @@
1
+ name: Publish Python package
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+ workflow_dispatch:
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ jobs:
13
+ publish:
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - name: Check out repository
17
+ uses: actions/checkout@v4
18
+
19
+ - name: Set up Python
20
+ uses: actions/setup-python@v5
21
+ with:
22
+ python-version: "3.x"
23
+
24
+ - name: Build package
25
+ run: |
26
+ python -m pip install --upgrade build
27
+ python -m build
28
+
29
+ - name: Publish to PyPI
30
+ uses: pypa/gh-action-pypi-publish@release/v1
31
+ with:
32
+ password: ${{ secrets.PYPI_API_TOKEN }}
@@ -0,0 +1,17 @@
1
+ # Python
2
+ .venv/
3
+ __pycache__/
4
+ *.pyc
5
+ .pytest_cache/
6
+
7
+ # Build artifacts
8
+ build/
9
+ dist/
10
+ *.egg-info/
11
+
12
+ # Editors / OS
13
+ .DS_Store
14
+ .idea/
15
+
16
+ # Isolated worktrees for in-progress work
17
+ .worktrees/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 MagicRodri
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 NONINFRINGEMENT. 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,239 @@
1
+ Metadata-Version: 2.5
2
+ Name: polyadmin
3
+ Version: 0.1.0b1
4
+ Summary: PolyAdmin: cross-language server-rendered admin framework (Python core)
5
+ Project-URL: Documentation, https://magicrodri.github.io/polyadmin-docs/
6
+ Project-URL: Source, https://github.com/MagicRodri/polyadmin
7
+ Project-URL: Issues, https://github.com/MagicRodri/polyadmin/issues
8
+ License: MIT
9
+ License-File: LICENSE
10
+ Keywords: admin,fastapi,htmx,server-rendered
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Framework :: FastAPI
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3 :: Only
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Application
22
+ Requires-Python: >=3.10
23
+ Requires-Dist: jinja2>=3.1
24
+ Provides-Extra: export-xlsx
25
+ Requires-Dist: openpyxl>=3.1; extra == 'export-xlsx'
26
+ Provides-Extra: fastapi
27
+ Requires-Dist: fastapi>=0.110; extra == 'fastapi'
28
+ Requires-Dist: python-multipart>=0.0.9; extra == 'fastapi'
29
+ Description-Content-Type: text/markdown
30
+
31
+ # PolyAdmin
32
+
33
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-blue?logo=python&logoColor=white)](https://www.python.org/)
34
+ [![CI](https://github.com/MagicRodri/polyadmin/actions/workflows/publish.yml/badge.svg)](https://github.com/MagicRodri/polyadmin/actions/workflows/publish.yml)
35
+ [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
36
+ [![GitHub Repo stars](https://img.shields.io/github/stars/MagicRodri/polyadmin?style=flat-square)](https://github.com/MagicRodri/polyadmin)
37
+
38
+ A Django-admin-style, server-rendered admin framework for
39
+ [FastAPI](https://fastapi.tiangolo.com). See [`docs/`](docs/) for
40
+ reference documentation, or visit the [PolyAdmin documentation site](https://magicrodri.github.io/polyadmin-docs/)
41
+ for the shared Python and Go guides.
42
+
43
+ ## What you get from declaring a `ModelAdmin`
44
+
45
+ Point one at your model (fields, `list_display`, search/filter config,
46
+ permissions) and you get:
47
+
48
+ - Full CRUD with search, filter, sort, and pagination, all swapped in
49
+ via HTMX partials — no full-page reloads
50
+ - A dashboard of pluggable widgets: metric, stat (value + trend),
51
+ progress bar, bar chart, donut/pie breakdown, table, activity feed,
52
+ timeline, and tabs (several widgets in one card)
53
+ - Sidebar grouping — resources sharing a `category` collapse into one
54
+ collapsible accordion section, e.g. everything CRUD-shaped under
55
+ one "Directory" group
56
+ - Custom admin pages (`admin.route`) for functionality that isn't
57
+ resource CRUD — reports, wizards, internal tools — rendered inside
58
+ the same layout, auth, and sidebar grouping as everything else
59
+ - Relation fields rendered as links, or as a searchable shadcn/ui
60
+ Command-style autocomplete backed by a server-side `/lookup` route
61
+ (never dumps the target model's full queryset into the page)
62
+ - Inline related records (`StackedInline`/`TabularInline`) — a
63
+ parent's create/detail/edit pages can show and manage a child's
64
+ records that point back at it, Django-admin style
65
+ - Record and bulk Actions, with a shadcn/ui Dialog confirmation step for
66
+ destructive ones
67
+ - Delete previews: the confirmation page says what else a delete takes
68
+ with it, and protected records block it
69
+ - List niceties: a date drill-down, restricted sorting, chosen link
70
+ columns, and filters that survive the trip to a record and back
71
+ - Authentication/authorization hooks gating every route *and* every
72
+ control the templates render
73
+ - CSV and XLSX export
74
+ - Internationalisation: per-request locale, language switcher, French
75
+ and Russian
76
+ - Toast notifications for every create/update/delete/action
77
+ - Per-resource (and per-widget) template overrides, so an application
78
+ can replace one view's markup without forking the framework
79
+
80
+ Styling is [shadcn/ui](https://ui.shadcn.com), hand-ported to
81
+ Alpine.js + Tailwind — its CSS-variable token system and component
82
+ markup, without React or Radix. That gives the admin **dark mode and
83
+ themability**: every color resolves through a CSS variable, so restyling
84
+ the whole thing is a change to one template. The layout is mobile-first
85
+ with a collapsible sidebar (a Sheet below `md`), a Django-admin-style
86
+ right-hand filter panel on wider screens, and breadcrumbs as the page
87
+ title. Tailwind/Alpine/HTMX are all CDN-loaded — no frontend build step.
88
+ See [`docs/components.md`](docs/components.md).
89
+
90
+ ## Quickstart
91
+
92
+ The first beta release is available on PyPI:
93
+
94
+ ```bash
95
+ pip install "polyadmin[fastapi]==0.1.0b1"
96
+ ```
97
+
98
+ For the latest unreleased code, install from Git:
99
+
100
+ ```bash
101
+ pip install "polyadmin[fastapi] @ git+https://github.com/MagicRodri/polyadmin.git"
102
+ ```
103
+
104
+ Declare a `ModelAdmin` against your own storage and mount it on a
105
+ FastAPI app:
106
+
107
+ ```python
108
+ from dataclasses import dataclass
109
+
110
+ from fastapi import FastAPI
111
+ from polyadmin import BooleanField, EmailField, ModelAdmin
112
+ from polyadmin.core.admin import Admin
113
+ from polyadmin.fastapi.router import create_router
114
+
115
+
116
+ @dataclass
117
+ class User:
118
+ id: int
119
+ email: str
120
+ is_active: bool = True
121
+
122
+
123
+ _users: list[User] = []
124
+
125
+
126
+ class UserAdmin(ModelAdmin):
127
+ model = User
128
+ list_display = ("id", "email", "is_active")
129
+ form_fields = ("email", "is_active")
130
+ search_fields = ("email",)
131
+ fields = (
132
+ EmailField("email", required=True),
133
+ BooleanField("is_active", default=True),
134
+ )
135
+
136
+ def get_queryset(self):
137
+ return _users
138
+
139
+ def get_object(self, pk):
140
+ return next((u for u in _users if u.id == int(pk)), None)
141
+
142
+ def create(self, data):
143
+ user = User(id=len(_users) + 1, **data)
144
+ _users.append(user)
145
+ return user
146
+
147
+ def update(self, obj, data):
148
+ obj.email = data["email"]
149
+ obj.is_active = data["is_active"]
150
+ return obj
151
+
152
+ def delete(self, obj):
153
+ _users.remove(obj)
154
+
155
+
156
+ admin = Admin(model_admins=[UserAdmin()])
157
+
158
+ app = FastAPI()
159
+ app.include_router(create_router(admin, base_path="/admin"), prefix="/admin")
160
+ ```
161
+
162
+ ```bash
163
+ uv run uvicorn main:app --reload
164
+ # open http://127.0.0.1:8000/admin
165
+ ```
166
+
167
+ That's a full CRUD admin for `User` — search, sort, create, edit,
168
+ delete, CSV/XLSX export, all with zero routes or templates of your
169
+ own. With no `authenticator`/`authorizer` set, every request is
170
+ allowed by default (fine for exploring locally, not for anything
171
+ real) — see [`docs/authentication.md`](docs/authentication.md)
172
+ and [`docs/permissions.md`](docs/permissions.md) before
173
+ deploying. For everything else a `ModelAdmin` supports (relations,
174
+ filters, actions, a dashboard, exports, delete previews), see
175
+ [`docs/model-admin.md`](docs/model-admin.md); for the UI components and
176
+ theming, [`docs/components.md`](docs/components.md); and for the rest,
177
+ [`docs/`](docs/).
178
+
179
+ ## Languages
180
+
181
+ French and Russian are **on by default** next to English: a visitor
182
+ whose browser asks for either gets the admin's own text in it, and the
183
+ language switcher appears in the header. Their catalogs are **drafts,
184
+ awaiting review by native speakers** — corrections are welcome. To keep
185
+ an admin English-only, restrict the supported set:
186
+
187
+ ```python
188
+ admin = Admin(model_admins=[...], locales=["en"])
189
+ ```
190
+
191
+ See [`docs/i18n.md`](docs/i18n.md) for the rest: translating your own
192
+ strings, the switcher, and how a request's language is chosen.
193
+
194
+ ## Upgrading: CSRF protection
195
+
196
+ Every mutating route now requires a CSRF token, on by default. The
197
+ framework's own pages carry it automatically; a **custom `AdminPage` that
198
+ renders its own `<form>`** must add the hidden field, or its posts will be
199
+ rejected with `403`:
200
+
201
+ ```html
202
+ {% from "admin/components/csrf-field.html" import csrf_field %}
203
+ {{ csrf_field(csrf_token) }}
204
+ ```
205
+
206
+ Forms that only submit via `hx-post` need no change. Every admin response
207
+ also sends `X-Frame-Options: DENY`. See
208
+ [`docs/authentication.md`](docs/authentication.md#csrf-protection) for the
209
+ cookie/header/field names, the proxy caveat (`X-Forwarded-Proto`), and how
210
+ to opt out.
211
+
212
+ ## Status
213
+
214
+ All 9 implementation phases are done:
215
+
216
+ - **Core** (`polyadmin/core/`): `Admin`, `ModelAdmin`, `Field` (incl. relation
217
+ field types), `Relation`, `Filter`, the list query pipeline (`query.py`),
218
+ `Paginator`, `Authenticator`/`Principal`, `Authorizer`, `Dashboard`/`Widget`,
219
+ `Exporter` (CSV + XLSX).
220
+ - **Rendering** (`polyadmin/templating.py`, `polyadmin/templates/`): Jinja2
221
+ templates with framework/app/resource override resolution and
222
+ `TemplateContext` builders. Tailwind/Alpine/HTMX are CDN-loaded
223
+ — no frontend build step required.
224
+ - **FastAPI adapter** (`polyadmin/fastapi/`): `create_router` mounts full CRUD +
225
+ list/detail/create/edit/delete + relation lookup + CSV/XLSX export routes,
226
+ with HTMX partial-swap for list search/filter/sort/pagination and for
227
+ forms, flash messages surviving a redirect, delete previews (see what a
228
+ delete takes with it; protected records block it), and auth/permission
229
+ enforcement on every route (also gates which controls the templates show).
230
+
231
+ See [`examples/fastapi`](examples/fastapi) for a full runnable
232
+ reference app exercising all of this.
233
+
234
+ ## Development
235
+
236
+ ```bash
237
+ uv sync
238
+ uv run pytest # 205 tests
239
+ ```
@@ -0,0 +1,209 @@
1
+ # PolyAdmin
2
+
3
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-blue?logo=python&logoColor=white)](https://www.python.org/)
4
+ [![CI](https://github.com/MagicRodri/polyadmin/actions/workflows/publish.yml/badge.svg)](https://github.com/MagicRodri/polyadmin/actions/workflows/publish.yml)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
6
+ [![GitHub Repo stars](https://img.shields.io/github/stars/MagicRodri/polyadmin?style=flat-square)](https://github.com/MagicRodri/polyadmin)
7
+
8
+ A Django-admin-style, server-rendered admin framework for
9
+ [FastAPI](https://fastapi.tiangolo.com). See [`docs/`](docs/) for
10
+ reference documentation, or visit the [PolyAdmin documentation site](https://magicrodri.github.io/polyadmin-docs/)
11
+ for the shared Python and Go guides.
12
+
13
+ ## What you get from declaring a `ModelAdmin`
14
+
15
+ Point one at your model (fields, `list_display`, search/filter config,
16
+ permissions) and you get:
17
+
18
+ - Full CRUD with search, filter, sort, and pagination, all swapped in
19
+ via HTMX partials — no full-page reloads
20
+ - A dashboard of pluggable widgets: metric, stat (value + trend),
21
+ progress bar, bar chart, donut/pie breakdown, table, activity feed,
22
+ timeline, and tabs (several widgets in one card)
23
+ - Sidebar grouping — resources sharing a `category` collapse into one
24
+ collapsible accordion section, e.g. everything CRUD-shaped under
25
+ one "Directory" group
26
+ - Custom admin pages (`admin.route`) for functionality that isn't
27
+ resource CRUD — reports, wizards, internal tools — rendered inside
28
+ the same layout, auth, and sidebar grouping as everything else
29
+ - Relation fields rendered as links, or as a searchable shadcn/ui
30
+ Command-style autocomplete backed by a server-side `/lookup` route
31
+ (never dumps the target model's full queryset into the page)
32
+ - Inline related records (`StackedInline`/`TabularInline`) — a
33
+ parent's create/detail/edit pages can show and manage a child's
34
+ records that point back at it, Django-admin style
35
+ - Record and bulk Actions, with a shadcn/ui Dialog confirmation step for
36
+ destructive ones
37
+ - Delete previews: the confirmation page says what else a delete takes
38
+ with it, and protected records block it
39
+ - List niceties: a date drill-down, restricted sorting, chosen link
40
+ columns, and filters that survive the trip to a record and back
41
+ - Authentication/authorization hooks gating every route *and* every
42
+ control the templates render
43
+ - CSV and XLSX export
44
+ - Internationalisation: per-request locale, language switcher, French
45
+ and Russian
46
+ - Toast notifications for every create/update/delete/action
47
+ - Per-resource (and per-widget) template overrides, so an application
48
+ can replace one view's markup without forking the framework
49
+
50
+ Styling is [shadcn/ui](https://ui.shadcn.com), hand-ported to
51
+ Alpine.js + Tailwind — its CSS-variable token system and component
52
+ markup, without React or Radix. That gives the admin **dark mode and
53
+ themability**: every color resolves through a CSS variable, so restyling
54
+ the whole thing is a change to one template. The layout is mobile-first
55
+ with a collapsible sidebar (a Sheet below `md`), a Django-admin-style
56
+ right-hand filter panel on wider screens, and breadcrumbs as the page
57
+ title. Tailwind/Alpine/HTMX are all CDN-loaded — no frontend build step.
58
+ See [`docs/components.md`](docs/components.md).
59
+
60
+ ## Quickstart
61
+
62
+ The first beta release is available on PyPI:
63
+
64
+ ```bash
65
+ pip install "polyadmin[fastapi]==0.1.0b1"
66
+ ```
67
+
68
+ For the latest unreleased code, install from Git:
69
+
70
+ ```bash
71
+ pip install "polyadmin[fastapi] @ git+https://github.com/MagicRodri/polyadmin.git"
72
+ ```
73
+
74
+ Declare a `ModelAdmin` against your own storage and mount it on a
75
+ FastAPI app:
76
+
77
+ ```python
78
+ from dataclasses import dataclass
79
+
80
+ from fastapi import FastAPI
81
+ from polyadmin import BooleanField, EmailField, ModelAdmin
82
+ from polyadmin.core.admin import Admin
83
+ from polyadmin.fastapi.router import create_router
84
+
85
+
86
+ @dataclass
87
+ class User:
88
+ id: int
89
+ email: str
90
+ is_active: bool = True
91
+
92
+
93
+ _users: list[User] = []
94
+
95
+
96
+ class UserAdmin(ModelAdmin):
97
+ model = User
98
+ list_display = ("id", "email", "is_active")
99
+ form_fields = ("email", "is_active")
100
+ search_fields = ("email",)
101
+ fields = (
102
+ EmailField("email", required=True),
103
+ BooleanField("is_active", default=True),
104
+ )
105
+
106
+ def get_queryset(self):
107
+ return _users
108
+
109
+ def get_object(self, pk):
110
+ return next((u for u in _users if u.id == int(pk)), None)
111
+
112
+ def create(self, data):
113
+ user = User(id=len(_users) + 1, **data)
114
+ _users.append(user)
115
+ return user
116
+
117
+ def update(self, obj, data):
118
+ obj.email = data["email"]
119
+ obj.is_active = data["is_active"]
120
+ return obj
121
+
122
+ def delete(self, obj):
123
+ _users.remove(obj)
124
+
125
+
126
+ admin = Admin(model_admins=[UserAdmin()])
127
+
128
+ app = FastAPI()
129
+ app.include_router(create_router(admin, base_path="/admin"), prefix="/admin")
130
+ ```
131
+
132
+ ```bash
133
+ uv run uvicorn main:app --reload
134
+ # open http://127.0.0.1:8000/admin
135
+ ```
136
+
137
+ That's a full CRUD admin for `User` — search, sort, create, edit,
138
+ delete, CSV/XLSX export, all with zero routes or templates of your
139
+ own. With no `authenticator`/`authorizer` set, every request is
140
+ allowed by default (fine for exploring locally, not for anything
141
+ real) — see [`docs/authentication.md`](docs/authentication.md)
142
+ and [`docs/permissions.md`](docs/permissions.md) before
143
+ deploying. For everything else a `ModelAdmin` supports (relations,
144
+ filters, actions, a dashboard, exports, delete previews), see
145
+ [`docs/model-admin.md`](docs/model-admin.md); for the UI components and
146
+ theming, [`docs/components.md`](docs/components.md); and for the rest,
147
+ [`docs/`](docs/).
148
+
149
+ ## Languages
150
+
151
+ French and Russian are **on by default** next to English: a visitor
152
+ whose browser asks for either gets the admin's own text in it, and the
153
+ language switcher appears in the header. Their catalogs are **drafts,
154
+ awaiting review by native speakers** — corrections are welcome. To keep
155
+ an admin English-only, restrict the supported set:
156
+
157
+ ```python
158
+ admin = Admin(model_admins=[...], locales=["en"])
159
+ ```
160
+
161
+ See [`docs/i18n.md`](docs/i18n.md) for the rest: translating your own
162
+ strings, the switcher, and how a request's language is chosen.
163
+
164
+ ## Upgrading: CSRF protection
165
+
166
+ Every mutating route now requires a CSRF token, on by default. The
167
+ framework's own pages carry it automatically; a **custom `AdminPage` that
168
+ renders its own `<form>`** must add the hidden field, or its posts will be
169
+ rejected with `403`:
170
+
171
+ ```html
172
+ {% from "admin/components/csrf-field.html" import csrf_field %}
173
+ {{ csrf_field(csrf_token) }}
174
+ ```
175
+
176
+ Forms that only submit via `hx-post` need no change. Every admin response
177
+ also sends `X-Frame-Options: DENY`. See
178
+ [`docs/authentication.md`](docs/authentication.md#csrf-protection) for the
179
+ cookie/header/field names, the proxy caveat (`X-Forwarded-Proto`), and how
180
+ to opt out.
181
+
182
+ ## Status
183
+
184
+ All 9 implementation phases are done:
185
+
186
+ - **Core** (`polyadmin/core/`): `Admin`, `ModelAdmin`, `Field` (incl. relation
187
+ field types), `Relation`, `Filter`, the list query pipeline (`query.py`),
188
+ `Paginator`, `Authenticator`/`Principal`, `Authorizer`, `Dashboard`/`Widget`,
189
+ `Exporter` (CSV + XLSX).
190
+ - **Rendering** (`polyadmin/templating.py`, `polyadmin/templates/`): Jinja2
191
+ templates with framework/app/resource override resolution and
192
+ `TemplateContext` builders. Tailwind/Alpine/HTMX are CDN-loaded
193
+ — no frontend build step required.
194
+ - **FastAPI adapter** (`polyadmin/fastapi/`): `create_router` mounts full CRUD +
195
+ list/detail/create/edit/delete + relation lookup + CSV/XLSX export routes,
196
+ with HTMX partial-swap for list search/filter/sort/pagination and for
197
+ forms, flash messages surviving a redirect, delete previews (see what a
198
+ delete takes with it; protected records block it), and auth/permission
199
+ enforcement on every route (also gates which controls the templates show).
200
+
201
+ See [`examples/fastapi`](examples/fastapi) for a full runnable
202
+ reference app exercising all of this.
203
+
204
+ ## Development
205
+
206
+ ```bash
207
+ uv sync
208
+ uv run pytest # 205 tests
209
+ ```
@@ -0,0 +1,4 @@
1
+ [python: polyadmin/**.py]
2
+
3
+ [jinja2: polyadmin/templates/**.html]
4
+ extensions = jinja2.ext.i18n
@@ -0,0 +1,31 @@
1
+ # browsertests
2
+
3
+ Playwright coverage for the flows a unit test cannot see: the login
4
+ gate, the five mutating flows, the htmx-driven list, the error pages,
5
+ and the layout facts only a browser can measure.
6
+
7
+ ## Running
8
+
9
+ ```bash
10
+ pip install playwright pytest-playwright
11
+ playwright install chromium
12
+ pytest browsertests
13
+ ```
14
+
15
+ The suite starts the reference app itself on port 3100 (override with
16
+ `POLYADMIN_TEST_PORT`) and stops it afterwards, so nothing needs to be
17
+ running first.
18
+
19
+ ## The rule these tests follow
20
+
21
+ Every assertion checks state the server actually holds, or a style the
22
+ browser actually resolved. Two failures are the reason:
23
+
24
+ - throwaway scripts that asserted the absence of an error string, and
25
+ passed against a form that had never been submitted;
26
+ - a login page that shipped with a panel the same colour as the page
27
+ behind it, past twenty passing assertions and two unit suites.
28
+
29
+ So after a mutation, reload and count the rows. For layout, read
30
+ `getComputedStyle` or a bounding box and compare — never assert on a
31
+ class name.