openadmin-dev 0.7.18__tar.gz → 0.7.20__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 (157) hide show
  1. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/.gitignore +2 -1
  2. openadmin_dev-0.7.20/PKG-INFO +108 -0
  3. openadmin_dev-0.7.20/README.md +91 -0
  4. openadmin_dev-0.7.20/docs/assets/dashboard-screenshot.png +0 -0
  5. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/docs/content/index.md +1 -1
  6. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/docs/content/introduction/getting-started.md +2 -2
  7. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/examples/admin/authors.py +1 -5
  8. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/examples/admin/books.py +15 -12
  9. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/examples/admin/control_panel.py +17 -28
  10. openadmin_dev-0.7.20/examples/admin/form_test.py +73 -0
  11. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/examples/admin/health.py +4 -1
  12. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/examples/admin/overview.py +1 -1
  13. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/examples/admin/publishers.py +1 -4
  14. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/examples/admin/tags.py +7 -6
  15. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/examples/admin/weekdays.py +1 -4
  16. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/examples/main.py +4 -1
  17. openadmin_dev-0.7.20/openadmin/__client__/assets/CardTitle-g-eR1YB7.js +1 -0
  18. openadmin_dev-0.7.20/openadmin/__client__/assets/Dashboard-CH62YEWP.js +1 -0
  19. openadmin_dev-0.7.20/openadmin/__client__/assets/DialogPortal-sTp649jA.js +1 -0
  20. openadmin_dev-0.7.20/openadmin/__client__/assets/FormView-DPqNEejF.js +193 -0
  21. openadmin_dev-0.7.20/openadmin/__client__/assets/LoginView-3isjvo_Q.js +1 -0
  22. openadmin_dev-0.7.20/openadmin/__client__/assets/PageView-Dq8SJOqN.js +67 -0
  23. openadmin_dev-0.7.20/openadmin/__client__/assets/SelectValue-WzbxPrgA.js +1 -0
  24. openadmin_dev-0.7.20/openadmin/__client__/assets/error-C_Ap5sRv.js +66 -0
  25. openadmin_dev-0.7.20/openadmin/__client__/assets/field-BSJU6_no.js +1 -0
  26. openadmin_dev-0.7.20/openadmin/__client__/assets/index-B5AJQb5X.css +2 -0
  27. openadmin_dev-0.7.20/openadmin/__client__/assets/index-BOBko9Fy.js +2 -0
  28. openadmin_dev-0.7.20/openadmin/__client__/assets/openadmin-form-Dp1fg3ME.js +3 -0
  29. openadmin_dev-0.7.20/openadmin/__client__/assets/openadmin-spec-DMXwLYN0.js +1 -0
  30. openadmin_dev-0.7.20/openadmin/__client__/assets/theme-CEh0Zsbj.js +1 -0
  31. openadmin_dev-0.7.20/openadmin/__client__/assets/useForm-DH6J4tEc.js +1 -0
  32. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/__client__/index.html +4 -6
  33. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/fastapi/admin_page.py +3 -0
  34. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/spec/__init__.py +2 -1
  35. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/spec/action.py +0 -4
  36. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/spec/form.py +3 -4
  37. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/spec/page.py +2 -0
  38. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/pyproject.toml +1 -1
  39. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/uv.lock +1 -1
  40. openadmin_dev-0.7.18/PKG-INFO +0 -332
  41. openadmin_dev-0.7.18/README.md +0 -315
  42. openadmin_dev-0.7.18/openadmin/__client__/assets/CardTitle-_-VQGAs4.js +0 -1
  43. openadmin_dev-0.7.18/openadmin/__client__/assets/Dashboard-Cbu4jQ6o.js +0 -1
  44. openadmin_dev-0.7.18/openadmin/__client__/assets/LoginView-DftiEGqx.js +0 -1
  45. openadmin_dev-0.7.18/openadmin/__client__/assets/PageView-3k326RhX.js +0 -67
  46. openadmin_dev-0.7.18/openadmin/__client__/assets/button-BahsXozd.js +0 -1
  47. openadmin_dev-0.7.18/openadmin/__client__/assets/error-BjWDzPTy.js +0 -66
  48. openadmin_dev-0.7.18/openadmin/__client__/assets/index-B6cWCMDE.js +0 -2
  49. openadmin_dev-0.7.18/openadmin/__client__/assets/index-DHDnMjXv.css +0 -1
  50. openadmin_dev-0.7.18/openadmin/__client__/assets/lib-DCDnSbDN.js +0 -1
  51. openadmin_dev-0.7.18/openadmin/__client__/assets/openadmin-page-BBinRZu9.js +0 -3
  52. openadmin_dev-0.7.18/openadmin/__client__/assets/openadmin-spec-B5RYk8O-.js +0 -1
  53. openadmin_dev-0.7.18/openadmin/__client__/assets/theme-BbaAAQ0K.js +0 -1
  54. openadmin_dev-0.7.18/openadmin/__client__/assets/vue-router-CAoMHVFh.js +0 -1
  55. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/.claude/launch.json +0 -0
  56. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/.github/ISSUE_TEMPLATE/bug.md +0 -0
  57. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/.github/ISSUE_TEMPLATE/chore.md +0 -0
  58. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/.github/ISSUE_TEMPLATE/feature.md +0 -0
  59. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/.github/actions/branch-name/action.yml +0 -0
  60. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/.github/actions/dependency-cves/action.yml +0 -0
  61. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/.github/actions/format/action.yml +0 -0
  62. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/.github/actions/license/action.yml +0 -0
  63. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/.github/actions/lint/action.yml +0 -0
  64. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/.github/actions/security/action.yml +0 -0
  65. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/.github/actions/spell-check/action.yml +0 -0
  66. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/.github/actions/test/action.yml +0 -0
  67. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/.github/actions/typecheck/action.yml +0 -0
  68. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/.github/actions/unused-code/action.yml +0 -0
  69. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/.github/workflows/ci.yml +0 -0
  70. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/.github/workflows/docs.yml +0 -0
  71. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/.github/workflows/publish.yml +0 -0
  72. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/.python-version +0 -0
  73. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/LICENSE +0 -0
  74. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/LICENSES/AGPL-3.0-or-later.txt +0 -0
  75. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/Makefile +0 -0
  76. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/REUSE.toml +0 -0
  77. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/docs/.gitignore +0 -0
  78. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/docs/assets/logo.png +0 -0
  79. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/docs/bun.lock +0 -0
  80. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/docs/config/.vitepress/config.mts +0 -0
  81. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/docs/config/.vitepress/theme/index.ts +0 -0
  82. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/docs/config/.vitepress/theme/style.css +0 -0
  83. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/docs/content/auth/index.md +0 -0
  84. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/docs/content/components/action.md +0 -0
  85. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/docs/content/components/area-chart.md +0 -0
  86. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/docs/content/components/bar-chart.md +0 -0
  87. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/docs/content/components/form.md +0 -0
  88. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/docs/content/components/index.md +0 -0
  89. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/docs/content/components/line-chart.md +0 -0
  90. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/docs/content/components/markdown.md +0 -0
  91. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/docs/content/components/pie-chart.md +0 -0
  92. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/docs/content/components/stat.md +0 -0
  93. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/docs/content/components/table.md +0 -0
  94. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/docs/content/cookbook/implementing-auth.md +0 -0
  95. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/docs/content/cookbook/implementing-form-with-reference.md +0 -0
  96. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/docs/content/cookbook/implementing-table.md +0 -0
  97. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/docs/content/introduction/what-is-openadmin.md +0 -0
  98. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/docs/package.json +0 -0
  99. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/examples/__init__.py +0 -0
  100. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/examples/admin/__init__.py +0 -0
  101. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/examples/admin/analytics.py +0 -0
  102. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/examples/admin/auth.py +0 -0
  103. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/examples/admin/docs.py +0 -0
  104. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/examples/admin/genres.py +0 -0
  105. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/examples/admin/reports.py +0 -0
  106. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/examples/lib/__init__.py +0 -0
  107. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/examples/lib/database.py +0 -0
  108. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/examples/lib/lifespan.py +0 -0
  109. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/examples/lib/models.py +0 -0
  110. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/examples/lib/seed.py +0 -0
  111. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/__client__/assets/HomeView-BY92UWl4.js +0 -0
  112. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/__client__/assets/geist-cyrillic-400-normal-DXusLSnH.woff2 +0 -0
  113. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/__client__/assets/geist-cyrillic-400-normal-DkrqoNl2.woff +0 -0
  114. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/__client__/assets/geist-cyrillic-ext-400-normal-ChfpGzr5.woff +0 -0
  115. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/__client__/assets/geist-latin-400-normal-B40WzpMT.woff2 +0 -0
  116. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/__client__/assets/geist-latin-400-normal-akEymXtG.woff +0 -0
  117. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/__client__/assets/geist-latin-ext-400-normal-Bz1pQMyt.woff +0 -0
  118. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/__client__/assets/geist-latin-ext-400-normal-CND6cjiG.woff2 +0 -0
  119. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/__client__/assets/geist-mono-cyrillic-400-normal-C51Di1Mf.woff2 +0 -0
  120. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/__client__/assets/geist-mono-cyrillic-400-normal-CPJFfJgk.woff +0 -0
  121. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/__client__/assets/geist-mono-latin-400-normal-B2yvC1Cq.woff +0 -0
  122. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/__client__/assets/geist-mono-latin-400-normal-DTRLJnHl.woff2 +0 -0
  123. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/__client__/assets/geist-mono-latin-ext-400-normal-CfzLURNc.woff +0 -0
  124. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/__client__/assets/geist-mono-latin-ext-400-normal-DI-rJ0UV.woff2 +0 -0
  125. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/__client__/assets/geist-mono-symbols2-400-normal-DjbiTEEA.woff +0 -0
  126. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/__client__/assets/geist-mono-vietnamese-400-normal-CZEPLOgu.woff +0 -0
  127. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/__client__/assets/geist-vietnamese-400-normal-C8xY9-dI.woff +0 -0
  128. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/__client__/assets/logo-D9vyMJZl.png +0 -0
  129. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/__client__/assets/rolldown-runtime-DK3Fl9T5.js +0 -0
  130. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/__client__/assets/runtime-core.esm-bundler-B4b-2GOK.js +0 -0
  131. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/__client__/favicon.svg +0 -0
  132. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/__client__/icons.svg +0 -0
  133. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/fastapi/__init__.py +0 -0
  134. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/fastapi/admin_auth.py +0 -0
  135. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/fastapi/admin_panel.py +0 -0
  136. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/fastapi/counter.py +0 -0
  137. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/fastapi/deps.py +0 -0
  138. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/fastapi/exc_handler.py +0 -0
  139. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/fastapi/refs.py +0 -0
  140. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/fastapi/req.py +0 -0
  141. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/fastapi/utils.py +0 -0
  142. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/spec/area_chart.py +0 -0
  143. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/spec/bar_chart.py +0 -0
  144. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/spec/colors.py +0 -0
  145. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/spec/component.py +0 -0
  146. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/spec/error.py +0 -0
  147. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/spec/http_methods.py +0 -0
  148. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/spec/icons.py +0 -0
  149. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/spec/json_schema.py +0 -0
  150. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/spec/line_chart.py +0 -0
  151. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/spec/markdown.py +0 -0
  152. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/spec/pie_chart.py +0 -0
  153. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/spec/section.py +0 -0
  154. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/spec/spec.py +0 -0
  155. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/spec/stat.py +0 -0
  156. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/openadmin/spec/table.py +0 -0
  157. {openadmin_dev-0.7.18 → openadmin_dev-0.7.20}/tests/test_hello.py +0 -0
@@ -12,4 +12,5 @@
12
12
  *.sqlite
13
13
 
14
14
  # Built frontend (bundled into the package at build time, see [tool.hatch.build] in pyproject.toml)
15
- openadmin/__client__
15
+ openadmin/__client__
16
+ .notes
@@ -0,0 +1,108 @@
1
+ Metadata-Version: 2.5
2
+ Name: openadmin-dev
3
+ Version: 0.7.20
4
+ Summary: Add your description here
5
+ Project-URL: Homepage, https://github.com/openadmin-team/openadmin-py
6
+ Project-URL: Issues, https://github.com/openadmin-team/openadmin-py/issues
7
+ Author-email: Mykyta Kasianenko <mykytakasianenko@gmail.com>
8
+ License-Expression: AGPL-3.0-or-later
9
+ License-File: LICENSE
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Programming Language :: Python :: 3
12
+ Requires-Python: >=3.14
13
+ Requires-Dist: fastapi[standard]>=0.136.3
14
+ Requires-Dist: itsdangerous>=2.2.0
15
+ Requires-Dist: typing-extensions>=4.12.0
16
+ Description-Content-Type: text/markdown
17
+
18
+ <div align="center">
19
+ <img src="docs/assets/logo.png" alt="OpenAdmin" width="120" />
20
+
21
+ <h1>OpenAdmin</h1>
22
+
23
+ <p>Admin dashboards as FastAPI routes. No templates, no frontend project, no config files.</p>
24
+
25
+ <a href="https://pypi.org/project/openadmin/"><img src="https://img.shields.io/pypi/v/openadmin?style=flat-square&label=pypi&color=blue" alt="PyPI" /></a>
26
+ <a href="https://python.org"><img src="https://img.shields.io/badge/python-3.14%2B-blue?style=flat-square" alt="Python" /></a>
27
+ <a href="https://fastapi.tiangolo.com"><img src="https://img.shields.io/badge/built%20on-FastAPI-009688?style=flat-square" alt="FastAPI" /></a>
28
+ <a href="LICENSE"><img src="https://img.shields.io/badge/license-AGPL--3.0--or--later-green?style=flat-square" alt="License" /></a>
29
+
30
+ <p><a href="https://openadmin-team.github.io/openadmin-py/">Documentation</a></p>
31
+ </div>
32
+
33
+ <img src="docs/assets/dashboard-screenshot.png" alt="OpenAdmin dashboard" width="100%" />
34
+
35
+ ---
36
+
37
+ OpenAdmin is a FastAPI-native library for building admin panels. A page is a router, a widget is an endpoint — stats, tables, forms, actions, and charts, defined with typed decorators in plain Python.
38
+
39
+ ```python
40
+ from openadmin import spec
41
+ from openadmin.fastapi import AdminPage, AdminPanel
42
+
43
+ page = AdminPage("Dashboard", icon="layout-dashboard")
44
+
45
+
46
+ @page.stat("Total Users")
47
+ async def total_users() -> spec.Stat:
48
+ return 1_024
49
+
50
+
51
+ @page.table("Recent Users")
52
+ async def recent_users() -> spec.Table:
53
+ return {
54
+ "data": [
55
+ {"id": 1, "name": "Alice", "role": "admin"},
56
+ {"id": 2, "name": "Bob", "role": "viewer"},
57
+ ]
58
+ }
59
+
60
+
61
+ admin = AdminPanel("My Admin")
62
+ admin.section("General", pages=[page])
63
+ ```
64
+
65
+ ```python
66
+ from fastapi import FastAPI
67
+
68
+ app = FastAPI()
69
+ app.mount("/admin", admin.app)
70
+ ```
71
+
72
+ Each decorated function is a normal FastAPI handler — dependency injection, `Query`/`Body` parameters, and OpenAPI docs all work as they would on any route.
73
+
74
+ ## Installation
75
+
76
+ ```bash
77
+ pip install openadmin
78
+ # or
79
+ uv add openadmin
80
+ ```
81
+
82
+ ## Documentation
83
+
84
+ Everything else — the full component reference, authentication, and worked cookbook recipes — lives at:
85
+
86
+ **[openadmin-team.github.io/openadmin-py](https://openadmin-team.github.io/openadmin-py/)**
87
+
88
+ - [Getting Started](https://openadmin-team.github.io/openadmin-py/introduction/getting-started) — build a minimal panel end to end
89
+ - [Components](https://openadmin-team.github.io/openadmin-py/components/) — stats, tables, forms, actions, and charts
90
+ - [Authentication](https://openadmin-team.github.io/openadmin-py/auth/) — gate the panel behind a login screen
91
+ - [Cookbook](https://openadmin-team.github.io/openadmin-py/cookbook/implementing-auth) — recipes for row actions, reference fields, and auth
92
+
93
+ ## Development
94
+
95
+ ```bash
96
+ # Run the example app
97
+ make dev/example
98
+
99
+ # Run all checks (format, lint, types, tests, security)
100
+ make check
101
+
102
+ # Auto-fix formatting and lint issues
103
+ make fix
104
+ ```
105
+
106
+ ## License
107
+
108
+ [AGPL-3.0-or-later](LICENSE) — © 2026 OpenAdmin
@@ -0,0 +1,91 @@
1
+ <div align="center">
2
+ <img src="docs/assets/logo.png" alt="OpenAdmin" width="120" />
3
+
4
+ <h1>OpenAdmin</h1>
5
+
6
+ <p>Admin dashboards as FastAPI routes. No templates, no frontend project, no config files.</p>
7
+
8
+ <a href="https://pypi.org/project/openadmin/"><img src="https://img.shields.io/pypi/v/openadmin?style=flat-square&label=pypi&color=blue" alt="PyPI" /></a>
9
+ <a href="https://python.org"><img src="https://img.shields.io/badge/python-3.14%2B-blue?style=flat-square" alt="Python" /></a>
10
+ <a href="https://fastapi.tiangolo.com"><img src="https://img.shields.io/badge/built%20on-FastAPI-009688?style=flat-square" alt="FastAPI" /></a>
11
+ <a href="LICENSE"><img src="https://img.shields.io/badge/license-AGPL--3.0--or--later-green?style=flat-square" alt="License" /></a>
12
+
13
+ <p><a href="https://openadmin-team.github.io/openadmin-py/">Documentation</a></p>
14
+ </div>
15
+
16
+ <img src="docs/assets/dashboard-screenshot.png" alt="OpenAdmin dashboard" width="100%" />
17
+
18
+ ---
19
+
20
+ OpenAdmin is a FastAPI-native library for building admin panels. A page is a router, a widget is an endpoint — stats, tables, forms, actions, and charts, defined with typed decorators in plain Python.
21
+
22
+ ```python
23
+ from openadmin import spec
24
+ from openadmin.fastapi import AdminPage, AdminPanel
25
+
26
+ page = AdminPage("Dashboard", icon="layout-dashboard")
27
+
28
+
29
+ @page.stat("Total Users")
30
+ async def total_users() -> spec.Stat:
31
+ return 1_024
32
+
33
+
34
+ @page.table("Recent Users")
35
+ async def recent_users() -> spec.Table:
36
+ return {
37
+ "data": [
38
+ {"id": 1, "name": "Alice", "role": "admin"},
39
+ {"id": 2, "name": "Bob", "role": "viewer"},
40
+ ]
41
+ }
42
+
43
+
44
+ admin = AdminPanel("My Admin")
45
+ admin.section("General", pages=[page])
46
+ ```
47
+
48
+ ```python
49
+ from fastapi import FastAPI
50
+
51
+ app = FastAPI()
52
+ app.mount("/admin", admin.app)
53
+ ```
54
+
55
+ Each decorated function is a normal FastAPI handler — dependency injection, `Query`/`Body` parameters, and OpenAPI docs all work as they would on any route.
56
+
57
+ ## Installation
58
+
59
+ ```bash
60
+ pip install openadmin
61
+ # or
62
+ uv add openadmin
63
+ ```
64
+
65
+ ## Documentation
66
+
67
+ Everything else — the full component reference, authentication, and worked cookbook recipes — lives at:
68
+
69
+ **[openadmin-team.github.io/openadmin-py](https://openadmin-team.github.io/openadmin-py/)**
70
+
71
+ - [Getting Started](https://openadmin-team.github.io/openadmin-py/introduction/getting-started) — build a minimal panel end to end
72
+ - [Components](https://openadmin-team.github.io/openadmin-py/components/) — stats, tables, forms, actions, and charts
73
+ - [Authentication](https://openadmin-team.github.io/openadmin-py/auth/) — gate the panel behind a login screen
74
+ - [Cookbook](https://openadmin-team.github.io/openadmin-py/cookbook/implementing-auth) — recipes for row actions, reference fields, and auth
75
+
76
+ ## Development
77
+
78
+ ```bash
79
+ # Run the example app
80
+ make dev/example
81
+
82
+ # Run all checks (format, lint, types, tests, security)
83
+ make check
84
+
85
+ # Auto-fix formatting and lint issues
86
+ make fix
87
+ ```
88
+
89
+ ## License
90
+
91
+ [AGPL-3.0-or-later](LICENSE) — © 2026 OpenAdmin
@@ -5,7 +5,7 @@ layout: home
5
5
  hero:
6
6
  name: "OpenAdmin"
7
7
  text: "Admin panel for python"
8
- tagline: Build admin dashboards as FastAPI routes — stats, tables, charts, and actions, all in pure Python.
8
+ tagline: Build admin dashboards as FastAPI routes — stats, tables, charts, and actions, all in pure Python using any orm and api.
9
9
  actions:
10
10
  - theme: brand
11
11
  text: What is OpenAdmin?
@@ -10,13 +10,13 @@ This walks through building a minimal admin panel from scratch: one page, one st
10
10
  ## Installation
11
11
 
12
12
  ```bash
13
- pip install openadmin-py
13
+ pip install openadmin
14
14
  ```
15
15
 
16
16
  or with `uv`:
17
17
 
18
18
  ```bash
19
- uv add openadmin-py
19
+ uv add openadmin
20
20
  ```
21
21
 
22
22
  ## Build a page
@@ -160,7 +160,6 @@ class AddAuthorBody(BaseModel):
160
160
  first_name: str
161
161
  last_name: str
162
162
  bio: str | None = None
163
- friend: int
164
163
 
165
164
 
166
165
  @page.form(
@@ -181,7 +180,4 @@ async def add_author(body: AddAuthorBody, session: AsyncSessionDep) -> spec.Form
181
180
  await session.commit()
182
181
  await session.refresh(author)
183
182
  name = f"{author.first_name} {author.last_name}"
184
- return {
185
- "message": f"Added author '{name}'",
186
- "table": {"id": author.id, "name": name},
187
- }
183
+ return {"toast": f"Added author '{name}'"}
@@ -5,12 +5,12 @@
5
5
  import asyncio
6
6
  from urllib.parse import quote
7
7
 
8
- from fastapi import Query
8
+ from fastapi import Query, UploadFile
9
9
  from pydantic import BaseModel
10
10
  from sqlalchemy import func, select
11
11
 
12
12
  from openadmin import spec
13
- from openadmin.fastapi import AdminPage
13
+ from openadmin.fastapi import AdminPage, reference_action
14
14
  from openadmin.fastapi.deps import PageDep, SearchDep
15
15
 
16
16
  from ..lib import models
@@ -44,6 +44,14 @@ async def get_books_without_publisher(session: AsyncSessionDep) -> int:
44
44
  BOOK_COVER_URL = "https://img.magnific.com/free-photo/beautiful-tropical-beach-sea-ocean-with-white-cloud-blue-sky-copyspace_74190-8663.jpg?semt=ais_hybrid&w=740&q=80"
45
45
 
46
46
 
47
+ @page.action(
48
+ "Upload cover",
49
+ is_hidden=True,
50
+ )
51
+ async def upload_cover(id: int, cover: UploadFile) -> spec.Action:
52
+ return f"File uploaded {cover.filename} {id=}"
53
+
54
+
47
55
  @page.table(
48
56
  "All Books",
49
57
  description="Browse all books with search and pagination",
@@ -112,6 +120,9 @@ async def get_all_books(
112
120
  "reference": {"label": "Link to the book"},
113
121
  "attachment": {"label": "Book in pdf"},
114
122
  },
123
+ "__actions__": [
124
+ {"action": reference_action(upload_cover), "form": {"id": book.id}}
125
+ ],
115
126
  }
116
127
  for book, author in result.all()
117
128
  ],
@@ -161,10 +172,7 @@ async def add_book(body: AddBookBody, session: AsyncSessionDep) -> spec.Form:
161
172
  session.add(book)
162
173
  await session.commit()
163
174
  await session.refresh(book)
164
- return {
165
- "message": f"Added book '{book.title}'",
166
- "table": {"id": book.id, "title": book.title},
167
- }
175
+ return {"toast": f"Added book '{book.title}'"}
168
176
 
169
177
 
170
178
  @page.action("Delete Book", method="delete", description="Remove a book by ID")
@@ -177,9 +185,4 @@ async def delete_book(
177
185
  await session.delete(book)
178
186
  await session.commit()
179
187
  found = book is not None
180
- return {
181
- "message": f"Deleted book #{book_id}"
182
- if found
183
- else f"Book #{book_id} not found",
184
- "table": {"deleted": book_id, "found": found},
185
- }
188
+ return f"Deleted book #{book_id}" if found else f"Book #{book_id} not found"
@@ -48,10 +48,8 @@ async def ping_service(
48
48
  target: str = Query("api", description="Service name to ping"),
49
49
  ) -> spec.Action:
50
50
  return {
51
- "icon": "activity",
52
- "color": "green",
53
- "toast": f"{target} responded in 12ms",
54
- "message": f"Pinged '{target}' — reachable (example response, nothing was contacted)",
51
+ "toast": f"Pinged '{target}' — reachable in 12ms "
52
+ "(example response, nothing was contacted)"
55
53
  }
56
54
 
57
55
 
@@ -93,11 +91,7 @@ async def update_feature_flag(
93
91
  flag_name: str = Query(..., description="Flag key, e.g. new-dashboard"),
94
92
  enabled: bool = Query(..., description="New flag state"),
95
93
  ) -> spec.Action:
96
- return {
97
- "toast": f"{flag_name} -> {enabled}",
98
- "message": "Preview of the change below (example data, flag was not modified)",
99
- "table": [{"flag": flag_name, "previous": not enabled, "new": enabled}],
100
- }
94
+ return {"toast": f"{flag_name} -> {enabled} (example only, flag was not modified)"}
101
95
 
102
96
 
103
97
  @page.action(
@@ -124,12 +118,10 @@ async def rotate_api_key(
124
118
  async def purge_temp_files(
125
119
  older_than_days: int = Query(7, ge=0, description="Age threshold in days"),
126
120
  ) -> spec.Action:
127
- return {
128
- "message": (
129
- f"Would delete files older than {older_than_days} days "
130
- "(example — nothing was purged)"
131
- )
132
- }
121
+ return (
122
+ f"Would delete files older than {older_than_days} days "
123
+ "(example nothing was purged)"
124
+ )
133
125
 
134
126
 
135
127
  # ---------------------------------------------------------------------------
@@ -447,17 +439,7 @@ class WebhookBody(BaseModel):
447
439
  color="teal",
448
440
  )
449
441
  async def create_webhook(body: WebhookBody) -> spec.Form:
450
- return {
451
- "icon": "webhook",
452
- "color": "teal",
453
- "toast": "Webhook created",
454
- "message": f"Would create a webhook for '{body.event}' -> {body.url}",
455
- "table": {
456
- "data": [{"id": 1, "url": body.url, "event": body.event}],
457
- "icon": "webhook",
458
- "color": "teal",
459
- },
460
- }
442
+ return {"toast": f"Would create a webhook for '{body.event}' -> {body.url}"}
461
443
 
462
444
 
463
445
  class RateLimitBody(BaseModel):
@@ -475,8 +457,6 @@ class RateLimitBody(BaseModel):
475
457
  )
476
458
  async def update_rate_limit(body: RateLimitBody) -> spec.Form:
477
459
  return {
478
- "icon": "gauge",
479
- "color": "cyan",
480
460
  "toast": f"Rate limit set to {body.requests_per_minute}/min "
481
461
  f"(burst {body.burst_size}) — example only, nothing was saved",
482
462
  }
@@ -509,6 +489,15 @@ async def rename_environment(
509
489
  )
510
490
  async def schedule_config_reset(
511
491
  config_id: int = Query(..., description="Config entry to reset"),
492
+ environment: str = Query("production", description="Environment to target"),
493
+ reason: str = Query(..., description="Why the reset is needed"),
494
+ scheduled_by: str = Query(..., description="Who is requesting the reset"),
495
+ maintenance_window: str = Query(
496
+ ..., description="Maintenance window label, e.g. Sat 02:00 UTC"
497
+ ),
498
+ notify_owner: bool = Query(True, description="Notify the config owner beforehand"),
499
+ dry_run: bool = Query(False, description="Preview the reset without scheduling it"),
500
+ retry_count: int = Query(0, description="Number of retries if the reset fails"),
512
501
  ) -> spec.Form:
513
502
  return None
514
503
 
@@ -0,0 +1,73 @@
1
+ # SPDX-FileCopyrightText: 2026 OpenAdmin
2
+ #
3
+ # SPDX-License-Identifier: AGPL-3.0-or-later
4
+
5
+ from datetime import date, datetime
6
+ from enum import Enum, StrEnum
7
+ from typing import Literal
8
+
9
+ from fastapi import UploadFile
10
+
11
+ from openadmin import spec
12
+ from openadmin.fastapi import AdminPage
13
+
14
+ page = AdminPage("Test forms", icon="test-tube")
15
+
16
+
17
+ class Methods(Enum):
18
+ GET = "GET"
19
+ POST = "POST"
20
+
21
+
22
+ class MethodsStr(StrEnum):
23
+ GET = "GET"
24
+ POST = "POST"
25
+
26
+
27
+ @page.form(
28
+ "Test all fields",
29
+ method="post",
30
+ icon="university",
31
+ color="green",
32
+ fields={
33
+ "string": {"icon": "type", "color": "blue"},
34
+ "int": {"icon": "hash", "color": "indigo"},
35
+ "float": {"icon": "binary", "color": "violet"},
36
+ "date": {"icon": "calendar-days", "color": "purple"},
37
+ "dates": {"icon": "calendar", "color": "fuchsia"},
38
+ "bool": {"icon": "toggle-left", "color": "pink"},
39
+ "list_of_strings": {"icon": "list", "color": "rose"},
40
+ "list_of_ints": {"icon": "list-ordered", "color": "red"},
41
+ "file": {"icon": "file-up", "color": "orange"},
42
+ "files": {"icon": "files", "color": "amber"},
43
+ "rich_text": {"icon": "pilcrow", "color": "yellow", "style": "rich-text"},
44
+ "literals": {"icon": "list-filter", "color": "lime"},
45
+ "enum": {"icon": "tag", "color": "green"},
46
+ "str_enum": {"icon": "tags", "color": "emerald"},
47
+ "list_literals": {"icon": "list-checks", "color": "teal"},
48
+ "list_enum": {"icon": "layers", "color": "cyan"},
49
+ "list_str_enum": {"icon": "list-tree", "color": "sky"},
50
+ },
51
+ )
52
+ def get_info(
53
+ string: str,
54
+ int: int,
55
+ float: float,
56
+ date: list[datetime],
57
+ dates: list[date],
58
+ bool: bool,
59
+ list_of_strings: list[str],
60
+ list_of_ints: list[int],
61
+ file: UploadFile,
62
+ files: list[UploadFile],
63
+ rich_text: str,
64
+ literals: Literal["get", "post"],
65
+ enum: Methods,
66
+ str_enum: MethodsStr,
67
+ list_literals: list[Literal["get", "post"]],
68
+ list_enum: list[Methods],
69
+ list_str_enum: list[MethodsStr],
70
+ ) -> spec.Form:
71
+ return {
72
+ "toast": f"All good ! {rich_text=} {list_literals=} {list_str_enum=} {list_enum=} {enum=} {literals=} {str_enum} {string=} {int=} {float=} {date=} {dates=} {bool=} {list_of_ints=} {list_of_strings=} {file.filename=} {len(files)=}"
73
+ }
@@ -5,7 +5,10 @@
5
5
  from openadmin.fastapi import AdminPage
6
6
 
7
7
  page = AdminPage(
8
- "Health", icon="heart-pulse", description="System health and infrastructure metrics"
8
+ "Health",
9
+ icon="heart-pulse",
10
+ description="System health and infrastructure metrics",
11
+ color="blue",
9
12
  )
10
13
 
11
14
 
@@ -86,7 +86,7 @@ Foreign keys are enforced at the application layer.
86
86
  )
87
87
  async def ping_database(session: AsyncSessionDep) -> spec.Action:
88
88
  await session.execute(text("SELECT 1"))
89
- return {"message": "Database is reachable"}
89
+ return {"toast": "Database is reachable"}
90
90
 
91
91
 
92
92
  async def _all_table_counts(session: AsyncSessionDep) -> list[tuple[str, int]]:
@@ -99,7 +99,4 @@ async def add_publisher(body: AddPublisherBody, session: AsyncSessionDep) -> spe
99
99
  session.add(publisher)
100
100
  await session.commit()
101
101
  await session.refresh(publisher)
102
- return {
103
- "message": f"Added publisher '{publisher.name}'",
104
- "table": {"id": publisher.id, "name": publisher.name},
105
- }
102
+ return {"toast": f"Added publisher '{publisher.name}'"}
@@ -12,7 +12,9 @@ from openadmin.fastapi.deps import PageDep
12
12
  from ..lib import models
13
13
  from ..lib.database import AsyncSessionDep
14
14
 
15
- page = AdminPage("Tags", icon="tag", description="Manage and analyze book tags")
15
+ page = AdminPage(
16
+ "Tags", icon="tag", description="Manage and analyze book tags", color="red"
17
+ )
16
18
 
17
19
 
18
20
  @page.stat("Total Tags")
@@ -97,9 +99,8 @@ async def remove_tag_from_book(
97
99
  await session.delete(link)
98
100
  await session.commit()
99
101
  removed = link is not None
100
- return {
101
- "message": f"Removed tag #{tag_id} from book #{book_id}"
102
+ return (
103
+ f"Removed tag #{tag_id} from book #{book_id}"
102
104
  if removed
103
- else f"No such tag assignment (book #{book_id}, tag #{tag_id})",
104
- "table": {"book_id": book_id, "tag_id": tag_id, "removed": removed},
105
- }
105
+ else f"No such tag assignment (book #{book_id}, tag #{tag_id})"
106
+ )
@@ -15,10 +15,7 @@ WEEKDAYS: list[dict[str, Any]] = [
15
15
  {"day": "Friday", "books": 10, "coffe": 9},
16
16
  ]
17
17
 
18
- page = fastapi.AdminPage(
19
- "Week Days",
20
- icon="calendar",
21
- )
18
+ page = fastapi.AdminPage("Week Days", icon="calendar", color="green")
22
19
 
23
20
 
24
21
  @page.pie_chart(
@@ -15,6 +15,7 @@ from .admin import (
15
15
  books,
16
16
  control_panel,
17
17
  docs,
18
+ form_test,
18
19
  genres,
19
20
  health,
20
21
  overview,
@@ -62,6 +63,8 @@ admin_panel.section(
62
63
  icon="server",
63
64
  pages=[health.page, reports.page, overview.page, control_panel.page],
64
65
  )
65
- admin_panel.section("Weekdays", icon="calendar-days", pages=[weekdays.page])
66
+ admin_panel.section(
67
+ "Weekdays", icon="calendar-days", pages=[weekdays.page, form_test.page]
68
+ )
66
69
 
67
70
  app.mount("/admin", admin_panel.app, name="admin_panel")
@@ -0,0 +1 @@
1
+ import{On as e,Zn as t,b as n,k as r,mt as i,xt as a}from"./runtime-core.esm-bundler-B4b-2GOK.js";import{Wc as o}from"./error-C_Ap5sRv.js";var s=r({__name:`Card`,props:{class:{type:[Boolean,null,String,Object,Array]}},setup(r){let s=r;return(r,c)=>(i(),n(`div`,{"data-slot":`card`,class:t(e(o)(`bg-card text-card-foreground flex flex-col gap-6 rounded-lg border py-6`,s.class))},[a(r.$slots,`default`)],2))}}),c=r({__name:`CardDescription`,props:{class:{type:[Boolean,null,String,Object,Array]}},setup(r){let s=r;return(r,c)=>(i(),n(`p`,{"data-slot":`card-description`,class:t(e(o)(`text-muted-foreground text-sm`,s.class))},[a(r.$slots,`default`)],2))}}),l=r({__name:`CardHeader`,props:{class:{type:[Boolean,null,String,Object,Array]}},setup(r){let s=r;return(r,c)=>(i(),n(`div`,{"data-slot":`card-header`,class:t(e(o)(`@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6`,s.class))},[a(r.$slots,`default`)],2))}}),u=r({__name:`CardTitle`,props:{class:{type:[Boolean,null,String,Object,Array]}},setup(r){let s=r;return(r,c)=>(i(),n(`h3`,{"data-slot":`card-title`,class:t(e(o)(`leading-none font-semibold`,s.class))},[a(r.$slots,`default`)],2))}});export{s as i,l as n,c as r,u as t};