adminsite 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (365) hide show
  1. adminsite-0.1.0/.gitattributes +4 -0
  2. adminsite-0.1.0/.github/ISSUE_TEMPLATE/bug_report.yml +45 -0
  3. adminsite-0.1.0/.github/ISSUE_TEMPLATE/config.yml +11 -0
  4. adminsite-0.1.0/.github/ISSUE_TEMPLATE/feature_request.yml +23 -0
  5. adminsite-0.1.0/.github/pull_request_template.md +7 -0
  6. adminsite-0.1.0/.github/release_notes.py +69 -0
  7. adminsite-0.1.0/.github/workflows/ci.yml +113 -0
  8. adminsite-0.1.0/.github/workflows/demo.yml +55 -0
  9. adminsite-0.1.0/.github/workflows/docs.yml +39 -0
  10. adminsite-0.1.0/.github/workflows/release.yml +67 -0
  11. adminsite-0.1.0/.gitignore +34 -0
  12. adminsite-0.1.0/.python-version +1 -0
  13. adminsite-0.1.0/AGENTS.md +159 -0
  14. adminsite-0.1.0/CHANGELOG.md +1147 -0
  15. adminsite-0.1.0/CLAUDE.md +1 -0
  16. adminsite-0.1.0/CODE_OF_CONDUCT.md +83 -0
  17. adminsite-0.1.0/CONTRIBUTING.md +84 -0
  18. adminsite-0.1.0/LICENSE +21 -0
  19. adminsite-0.1.0/PKG-INFO +172 -0
  20. adminsite-0.1.0/README.md +130 -0
  21. adminsite-0.1.0/SECURITY.md +33 -0
  22. adminsite-0.1.0/demo/Caddyfile +11 -0
  23. adminsite-0.1.0/demo/Dockerfile +37 -0
  24. adminsite-0.1.0/demo/Dockerfile.dockerignore +11 -0
  25. adminsite-0.1.0/demo/README.md +45 -0
  26. adminsite-0.1.0/demo/app.py +245 -0
  27. adminsite-0.1.0/demo/compose.yaml +54 -0
  28. adminsite-0.1.0/demo/deploy.sh +34 -0
  29. adminsite-0.1.0/docs/accessibility.md +44 -0
  30. adminsite-0.1.0/docs/actions.md +411 -0
  31. adminsite-0.1.0/docs/api.md +124 -0
  32. adminsite-0.1.0/docs/assets/architecture.png +0 -0
  33. adminsite-0.1.0/docs/assets/orders.png +0 -0
  34. adminsite-0.1.0/docs/assets/social-preview.png +0 -0
  35. adminsite-0.1.0/docs/audit.md +325 -0
  36. adminsite-0.1.0/docs/auth.md +156 -0
  37. adminsite-0.1.0/docs/changelog.md +1 -0
  38. adminsite-0.1.0/docs/customizing.md +99 -0
  39. adminsite-0.1.0/docs/dashboard.md +128 -0
  40. adminsite-0.1.0/docs/databases.md +164 -0
  41. adminsite-0.1.0/docs/fields.md +706 -0
  42. adminsite-0.1.0/docs/filters.md +164 -0
  43. adminsite-0.1.0/docs/getting-started.md +182 -0
  44. adminsite-0.1.0/docs/hooks.md +160 -0
  45. adminsite-0.1.0/docs/import.md +68 -0
  46. adminsite-0.1.0/docs/index.md +63 -0
  47. adminsite-0.1.0/docs/pages.md +130 -0
  48. adminsite-0.1.0/docs/permissions.md +215 -0
  49. adminsite-0.1.0/docs/reference.md +319 -0
  50. adminsite-0.1.0/docs/translations.md +207 -0
  51. adminsite-0.1.0/docs/views.md +619 -0
  52. adminsite-0.1.0/examples/fields.py +891 -0
  53. adminsite-0.1.0/examples/shop.py +423 -0
  54. adminsite-0.1.0/frontend/architecture.html +390 -0
  55. adminsite-0.1.0/frontend/input.css +386 -0
  56. adminsite-0.1.0/frontend/package.json +19 -0
  57. adminsite-0.1.0/frontend/social-preview.html +121 -0
  58. adminsite-0.1.0/frontend/vendor.mjs +21 -0
  59. adminsite-0.1.0/mkdocs.yml +167 -0
  60. adminsite-0.1.0/overrides/main.html +31 -0
  61. adminsite-0.1.0/pyproject.toml +148 -0
  62. adminsite-0.1.0/src/adminsite/__init__.py +69 -0
  63. adminsite-0.1.0/src/adminsite/_http/__init__.py +1 -0
  64. adminsite-0.1.0/src/adminsite/_http/actions.py +163 -0
  65. adminsite-0.1.0/src/adminsite/_http/activity.py +295 -0
  66. adminsite-0.1.0/src/adminsite/_http/api.py +461 -0
  67. adminsite-0.1.0/src/adminsite/_http/csrf.py +71 -0
  68. adminsite-0.1.0/src/adminsite/_http/dashboard.py +21 -0
  69. adminsite-0.1.0/src/adminsite/_http/detail.py +278 -0
  70. adminsite-0.1.0/src/adminsite/_http/document_forms.py +229 -0
  71. adminsite-0.1.0/src/adminsite/_http/editing.py +339 -0
  72. adminsite-0.1.0/src/adminsite/_http/export.py +166 -0
  73. adminsite-0.1.0/src/adminsite/_http/files.py +30 -0
  74. adminsite-0.1.0/src/adminsite/_http/form_rows.py +123 -0
  75. adminsite-0.1.0/src/adminsite/_http/forms.py +278 -0
  76. adminsite-0.1.0/src/adminsite/_http/history.py +234 -0
  77. adminsite-0.1.0/src/adminsite/_http/importing.py +231 -0
  78. adminsite-0.1.0/src/adminsite/_http/inline_tables.py +289 -0
  79. adminsite-0.1.0/src/adminsite/_http/languages.py +33 -0
  80. adminsite-0.1.0/src/adminsite/_http/list_editing.py +184 -0
  81. adminsite-0.1.0/src/adminsite/_http/listing.py +599 -0
  82. adminsite-0.1.0/src/adminsite/_http/lookups.py +113 -0
  83. adminsite-0.1.0/src/adminsite/_http/pages.py +33 -0
  84. adminsite-0.1.0/src/adminsite/_http/palette.py +121 -0
  85. adminsite-0.1.0/src/adminsite/_http/requests.py +107 -0
  86. adminsite-0.1.0/src/adminsite/_http/saved_views.py +115 -0
  87. adminsite-0.1.0/src/adminsite/_http/signing_in.py +114 -0
  88. adminsite-0.1.0/src/adminsite/_http/templating.py +169 -0
  89. adminsite-0.1.0/src/adminsite/_http/timezones.py +86 -0
  90. adminsite-0.1.0/src/adminsite/_http/urls.py +226 -0
  91. adminsite-0.1.0/src/adminsite/_importing.py +446 -0
  92. adminsite-0.1.0/src/adminsite/_orm/__init__.py +1 -0
  93. adminsite-0.1.0/src/adminsite/_orm/cursor.py +65 -0
  94. adminsite-0.1.0/src/adminsite/_orm/loader.py +66 -0
  95. adminsite-0.1.0/src/adminsite/_orm/repository.py +767 -0
  96. adminsite-0.1.0/src/adminsite/_orm/values.py +39 -0
  97. adminsite-0.1.0/src/adminsite/_storage.py +86 -0
  98. adminsite-0.1.0/src/adminsite/_text.py +104 -0
  99. adminsite-0.1.0/src/adminsite/actions/__init__.py +9 -0
  100. adminsite-0.1.0/src/adminsite/actions/_parameters.py +595 -0
  101. adminsite-0.1.0/src/adminsite/actions/base.py +262 -0
  102. adminsite-0.1.0/src/adminsite/actions/selection.py +257 -0
  103. adminsite-0.1.0/src/adminsite/admin.py +831 -0
  104. adminsite-0.1.0/src/adminsite/audit/__init__.py +22 -0
  105. adminsite-0.1.0/src/adminsite/audit/_actor.py +52 -0
  106. adminsite-0.1.0/src/adminsite/audit/_inputs.py +72 -0
  107. adminsite-0.1.0/src/adminsite/audit/entry.py +218 -0
  108. adminsite-0.1.0/src/adminsite/audit/log.py +181 -0
  109. adminsite-0.1.0/src/adminsite/audit/store.py +116 -0
  110. adminsite-0.1.0/src/adminsite/auth/__init__.py +11 -0
  111. adminsite-0.1.0/src/adminsite/auth/passwords.py +70 -0
  112. adminsite-0.1.0/src/adminsite/auth/provider.py +255 -0
  113. adminsite-0.1.0/src/adminsite/columns.py +167 -0
  114. adminsite-0.1.0/src/adminsite/dashboard.py +496 -0
  115. adminsite-0.1.0/src/adminsite/database.py +491 -0
  116. adminsite-0.1.0/src/adminsite/exceptions.py +170 -0
  117. adminsite-0.1.0/src/adminsite/fields/__init__.py +46 -0
  118. adminsite-0.1.0/src/adminsite/fields/_documents/__init__.py +20 -0
  119. adminsite-0.1.0/src/adminsite/fields/_documents/document.py +496 -0
  120. adminsite-0.1.0/src/adminsite/fields/_documents/error_messages.py +115 -0
  121. adminsite-0.1.0/src/adminsite/fields/_documents/inputs.py +294 -0
  122. adminsite-0.1.0/src/adminsite/fields/_documents/schema_reader.py +302 -0
  123. adminsite-0.1.0/src/adminsite/fields/_documents/shapes.py +118 -0
  124. adminsite-0.1.0/src/adminsite/fields/_documents/shown.py +123 -0
  125. adminsite-0.1.0/src/adminsite/fields/base.py +326 -0
  126. adminsite-0.1.0/src/adminsite/fields/choices.py +294 -0
  127. adminsite-0.1.0/src/adminsite/fields/computed.py +145 -0
  128. adminsite-0.1.0/src/adminsite/fields/files.py +200 -0
  129. adminsite-0.1.0/src/adminsite/fields/json_field.py +260 -0
  130. adminsite-0.1.0/src/adminsite/fields/lists.py +101 -0
  131. adminsite-0.1.0/src/adminsite/fields/passwords.py +57 -0
  132. adminsite-0.1.0/src/adminsite/fields/registry.py +118 -0
  133. adminsite-0.1.0/src/adminsite/fields/relations.py +119 -0
  134. adminsite-0.1.0/src/adminsite/fields/scalars.py +170 -0
  135. adminsite-0.1.0/src/adminsite/fields/temporal.py +169 -0
  136. adminsite-0.1.0/src/adminsite/fields/tones.py +36 -0
  137. adminsite-0.1.0/src/adminsite/files.py +183 -0
  138. adminsite-0.1.0/src/adminsite/filters/__init__.py +28 -0
  139. adminsite-0.1.0/src/adminsite/filters/base.py +147 -0
  140. adminsite-0.1.0/src/adminsite/filters/sql.py +542 -0
  141. adminsite-0.1.0/src/adminsite/i18n.py +446 -0
  142. adminsite-0.1.0/src/adminsite/inspector.py +280 -0
  143. adminsite-0.1.0/src/adminsite/locales/ar.json +476 -0
  144. adminsite-0.1.0/src/adminsite/locales/de.json +424 -0
  145. adminsite-0.1.0/src/adminsite/locales/es.json +424 -0
  146. adminsite-0.1.0/src/adminsite/locales/fa.json +424 -0
  147. adminsite-0.1.0/src/adminsite/locales/fr.json +424 -0
  148. adminsite-0.1.0/src/adminsite/locales/ja.json +385 -0
  149. adminsite-0.1.0/src/adminsite/locales/pt-BR.json +424 -0
  150. adminsite-0.1.0/src/adminsite/locales/ru.json +437 -0
  151. adminsite-0.1.0/src/adminsite/locales/tr.json +424 -0
  152. adminsite-0.1.0/src/adminsite/locales/zh-Hans.json +385 -0
  153. adminsite-0.1.0/src/adminsite/locales/zh-Hant.json +385 -0
  154. adminsite-0.1.0/src/adminsite/markup.py +43 -0
  155. adminsite-0.1.0/src/adminsite/messages.py +101 -0
  156. adminsite-0.1.0/src/adminsite/pages.py +126 -0
  157. adminsite-0.1.0/src/adminsite/permissions.py +54 -0
  158. adminsite-0.1.0/src/adminsite/plugins.py +48 -0
  159. adminsite-0.1.0/src/adminsite/py.typed +0 -0
  160. adminsite-0.1.0/src/adminsite/query.py +150 -0
  161. adminsite-0.1.0/src/adminsite/saved_views.py +193 -0
  162. adminsite-0.1.0/src/adminsite/schema.py +169 -0
  163. adminsite-0.1.0/src/adminsite/static/adminsite.css +2 -0
  164. adminsite-0.1.0/src/adminsite/static/alpine.min.js +21 -0
  165. adminsite-0.1.0/src/adminsite/static/htmx.min.js +1 -0
  166. adminsite-0.1.0/src/adminsite/templates/adminsite/_action_forms.html +51 -0
  167. adminsite-0.1.0/src/adminsite/templates/adminsite/_action_inputs.html +17 -0
  168. adminsite-0.1.0/src/adminsite/templates/adminsite/_browser_timezone.html +12 -0
  169. adminsite-0.1.0/src/adminsite/templates/adminsite/_bulk_actions.html +57 -0
  170. adminsite-0.1.0/src/adminsite/templates/adminsite/_bulk_summary.html +21 -0
  171. adminsite-0.1.0/src/adminsite/templates/adminsite/_document_form.html +225 -0
  172. adminsite-0.1.0/src/adminsite/templates/adminsite/_document_json.html +18 -0
  173. adminsite-0.1.0/src/adminsite/templates/adminsite/_document_switch.html +15 -0
  174. adminsite-0.1.0/src/adminsite/templates/adminsite/_document_view.html +111 -0
  175. adminsite-0.1.0/src/adminsite/templates/adminsite/_empty.html +2 -0
  176. adminsite-0.1.0/src/adminsite/templates/adminsite/_field.html +55 -0
  177. adminsite-0.1.0/src/adminsite/templates/adminsite/_filter_chips.html +26 -0
  178. adminsite-0.1.0/src/adminsite/templates/adminsite/_filter_counts.html +9 -0
  179. adminsite-0.1.0/src/adminsite/templates/adminsite/_filters.html +136 -0
  180. adminsite-0.1.0/src/adminsite/templates/adminsite/_filters_count.html +4 -0
  181. adminsite-0.1.0/src/adminsite/templates/adminsite/_filters_footer.html +18 -0
  182. adminsite-0.1.0/src/adminsite/templates/adminsite/_form_layout.html +73 -0
  183. adminsite-0.1.0/src/adminsite/templates/adminsite/_history.html +62 -0
  184. adminsite-0.1.0/src/adminsite/templates/adminsite/_icons.html +54 -0
  185. adminsite-0.1.0/src/adminsite/templates/adminsite/_inlines.html +107 -0
  186. adminsite-0.1.0/src/adminsite/templates/adminsite/_json.html +73 -0
  187. adminsite-0.1.0/src/adminsite/templates/adminsite/_list_forms.html +52 -0
  188. adminsite-0.1.0/src/adminsite/templates/adminsite/_list_menus.html +27 -0
  189. adminsite-0.1.0/src/adminsite/templates/adminsite/_lookup.html +6 -0
  190. adminsite-0.1.0/src/adminsite/templates/adminsite/_message.html +27 -0
  191. adminsite-0.1.0/src/adminsite/templates/adminsite/_option_count.html +9 -0
  192. adminsite-0.1.0/src/adminsite/templates/adminsite/_page_numbers.html +21 -0
  193. adminsite-0.1.0/src/adminsite/templates/adminsite/_palette.html +18 -0
  194. adminsite-0.1.0/src/adminsite/templates/adminsite/_records_count.html +12 -0
  195. adminsite-0.1.0/src/adminsite/templates/adminsite/_records_counted.html +10 -0
  196. adminsite-0.1.0/src/adminsite/templates/adminsite/_records_total.html +9 -0
  197. adminsite-0.1.0/src/adminsite/templates/adminsite/_row.html +87 -0
  198. adminsite-0.1.0/src/adminsite/templates/adminsite/_row_actions.html +25 -0
  199. adminsite-0.1.0/src/adminsite/templates/adminsite/_saved_tabs.html +33 -0
  200. adminsite-0.1.0/src/adminsite/templates/adminsite/_table.html +220 -0
  201. adminsite-0.1.0/src/adminsite/templates/adminsite/_toolbar.html +38 -0
  202. adminsite-0.1.0/src/adminsite/templates/adminsite/_value_editing.html +137 -0
  203. adminsite-0.1.0/src/adminsite/templates/adminsite/_value_editor.html +18 -0
  204. adminsite-0.1.0/src/adminsite/templates/adminsite/_value_saved.html +4 -0
  205. adminsite-0.1.0/src/adminsite/templates/adminsite/_values.html +19 -0
  206. adminsite-0.1.0/src/adminsite/templates/adminsite/activity.html +78 -0
  207. adminsite-0.1.0/src/adminsite/templates/adminsite/base.html +1214 -0
  208. adminsite-0.1.0/src/adminsite/templates/adminsite/dashboard/chart.html +61 -0
  209. adminsite-0.1.0/src/adminsite/templates/adminsite/dashboard/counts.html +17 -0
  210. adminsite-0.1.0/src/adminsite/templates/adminsite/dashboard/recent.html +15 -0
  211. adminsite-0.1.0/src/adminsite/templates/adminsite/dashboard/stat.html +23 -0
  212. adminsite-0.1.0/src/adminsite/templates/adminsite/detail.html +268 -0
  213. adminsite-0.1.0/src/adminsite/templates/adminsite/error.html +16 -0
  214. adminsite-0.1.0/src/adminsite/templates/adminsite/form.html +158 -0
  215. adminsite-0.1.0/src/adminsite/templates/adminsite/import.html +61 -0
  216. adminsite-0.1.0/src/adminsite/templates/adminsite/import_preview.html +95 -0
  217. adminsite-0.1.0/src/adminsite/templates/adminsite/index.html +33 -0
  218. adminsite-0.1.0/src/adminsite/templates/adminsite/list.html +110 -0
  219. adminsite-0.1.0/src/adminsite/templates/adminsite/login.html +63 -0
  220. adminsite-0.1.0/src/adminsite/templates/adminsite/widgets/_option.html +14 -0
  221. adminsite-0.1.0/src/adminsite/templates/adminsite/widgets/_picker.html +108 -0
  222. adminsite-0.1.0/src/adminsite/templates/adminsite/widgets/checkbox.html +6 -0
  223. adminsite-0.1.0/src/adminsite/templates/adminsite/widgets/file.html +20 -0
  224. adminsite-0.1.0/src/adminsite/templates/adminsite/widgets/image.html +1 -0
  225. adminsite-0.1.0/src/adminsite/templates/adminsite/widgets/input.html +7 -0
  226. adminsite-0.1.0/src/adminsite/templates/adminsite/widgets/json.html +48 -0
  227. adminsite-0.1.0/src/adminsite/templates/adminsite/widgets/list.html +4 -0
  228. adminsite-0.1.0/src/adminsite/templates/adminsite/widgets/password.html +6 -0
  229. adminsite-0.1.0/src/adminsite/templates/adminsite/widgets/readonly.html +5 -0
  230. adminsite-0.1.0/src/adminsite/templates/adminsite/widgets/relation.html +32 -0
  231. adminsite-0.1.0/src/adminsite/templates/adminsite/widgets/select.html +18 -0
  232. adminsite-0.1.0/src/adminsite/templates/adminsite/widgets/textarea.html +3 -0
  233. adminsite-0.1.0/src/adminsite/timezones.py +146 -0
  234. adminsite-0.1.0/src/adminsite/views/__init__.py +19 -0
  235. adminsite-0.1.0/src/adminsite/views/_actions.py +452 -0
  236. adminsite-0.1.0/src/adminsite/views/_audit.py +151 -0
  237. adminsite-0.1.0/src/adminsite/views/_checks.py +398 -0
  238. adminsite-0.1.0/src/adminsite/views/_fields.py +291 -0
  239. adminsite-0.1.0/src/adminsite/views/_forms.py +271 -0
  240. adminsite-0.1.0/src/adminsite/views/_inline_views.py +150 -0
  241. adminsite-0.1.0/src/adminsite/views/_linked_names.py +106 -0
  242. adminsite-0.1.0/src/adminsite/views/_links.py +124 -0
  243. adminsite-0.1.0/src/adminsite/views/_pages.py +423 -0
  244. adminsite-0.1.0/src/adminsite/views/_picker.py +203 -0
  245. adminsite-0.1.0/src/adminsite/views/_reader.py +407 -0
  246. adminsite-0.1.0/src/adminsite/views/_saver.py +414 -0
  247. adminsite-0.1.0/src/adminsite/views/_settings.py +277 -0
  248. adminsite-0.1.0/src/adminsite/views/contexts.py +225 -0
  249. adminsite-0.1.0/src/adminsite/views/inlines.py +67 -0
  250. adminsite-0.1.0/src/adminsite/views/layout.py +284 -0
  251. adminsite-0.1.0/src/adminsite/views/model_view.py +761 -0
  252. adminsite-0.1.0/src/adminsite/views/registry.py +152 -0
  253. adminsite-0.1.0/tests/__init__.py +0 -0
  254. adminsite-0.1.0/tests/conftest.py +309 -0
  255. adminsite-0.1.0/tests/doc_examples/__init__.py +0 -0
  256. adminsite-0.1.0/tests/doc_examples/__main__.py +308 -0
  257. adminsite-0.1.0/tests/doc_examples/context.py +709 -0
  258. adminsite-0.1.0/tests/factories.py +86 -0
  259. adminsite-0.1.0/tests/messages.py +178 -0
  260. adminsite-0.1.0/tests/models.py +203 -0
  261. adminsite-0.1.0/tests/reference/__init__.py +6 -0
  262. adminsite-0.1.0/tests/reference/field_mistakes.py +38 -0
  263. adminsite-0.1.0/tests/reference/gallery.py +95 -0
  264. adminsite-0.1.0/tests/reference/library.py +31 -0
  265. adminsite-0.1.0/tests/reference/mistakes.py +44 -0
  266. adminsite-0.1.0/tests/reference/models.py +179 -0
  267. adminsite-0.1.0/tests/reference/orders.py +183 -0
  268. adminsite-0.1.0/tests/reference/products.py +254 -0
  269. adminsite-0.1.0/tests/reference/startup_mistakes.py +151 -0
  270. adminsite-0.1.0/tests/support.py +103 -0
  271. adminsite-0.1.0/tests/test_access.py +189 -0
  272. adminsite-0.1.0/tests/test_accessibility.py +298 -0
  273. adminsite-0.1.0/tests/test_account_menu.py +100 -0
  274. adminsite-0.1.0/tests/test_action_icons.py +219 -0
  275. adminsite-0.1.0/tests/test_action_inputs.py +210 -0
  276. adminsite-0.1.0/tests/test_action_links.py +328 -0
  277. adminsite-0.1.0/tests/test_actions.py +528 -0
  278. adminsite-0.1.0/tests/test_activity.py +274 -0
  279. adminsite-0.1.0/tests/test_api.py +512 -0
  280. adminsite-0.1.0/tests/test_app.py +170 -0
  281. adminsite-0.1.0/tests/test_audit.py +716 -0
  282. adminsite-0.1.0/tests/test_audit_actions.py +436 -0
  283. adminsite-0.1.0/tests/test_audit_sign_in.py +242 -0
  284. adminsite-0.1.0/tests/test_audit_together.py +234 -0
  285. adminsite-0.1.0/tests/test_auth.py +467 -0
  286. adminsite-0.1.0/tests/test_banner.py +63 -0
  287. adminsite-0.1.0/tests/test_bulk_delete.py +291 -0
  288. adminsite-0.1.0/tests/test_choice_search.py +114 -0
  289. adminsite-0.1.0/tests/test_columns.py +296 -0
  290. adminsite-0.1.0/tests/test_composite_selection.py +181 -0
  291. adminsite-0.1.0/tests/test_computed.py +160 -0
  292. adminsite-0.1.0/tests/test_computed_load.py +145 -0
  293. adminsite-0.1.0/tests/test_custom_types.py +203 -0
  294. adminsite-0.1.0/tests/test_dashboard.py +319 -0
  295. adminsite-0.1.0/tests/test_database.py +241 -0
  296. adminsite-0.1.0/tests/test_deferred.py +118 -0
  297. adminsite-0.1.0/tests/test_demo.py +223 -0
  298. adminsite-0.1.0/tests/test_detail_fields.py +117 -0
  299. adminsite-0.1.0/tests/test_enum_values.py +144 -0
  300. adminsite-0.1.0/tests/test_exports.py +61 -0
  301. adminsite-0.1.0/tests/test_field_access.py +818 -0
  302. adminsite-0.1.0/tests/test_field_options.py +169 -0
  303. adminsite-0.1.0/tests/test_fields.py +262 -0
  304. adminsite-0.1.0/tests/test_files.py +310 -0
  305. adminsite-0.1.0/tests/test_filter_picker.py +237 -0
  306. adminsite-0.1.0/tests/test_filters.py +766 -0
  307. adminsite-0.1.0/tests/test_fixtures.py +71 -0
  308. adminsite-0.1.0/tests/test_form_layout.py +264 -0
  309. adminsite-0.1.0/tests/test_form_only.py +438 -0
  310. adminsite-0.1.0/tests/test_hooks.py +233 -0
  311. adminsite-0.1.0/tests/test_i18n.py +826 -0
  312. adminsite-0.1.0/tests/test_import.py +570 -0
  313. adminsite-0.1.0/tests/test_inline_editable.py +719 -0
  314. adminsite-0.1.0/tests/test_inlines.py +952 -0
  315. adminsite-0.1.0/tests/test_inspector.py +274 -0
  316. adminsite-0.1.0/tests/test_json_field.py +281 -0
  317. adminsite-0.1.0/tests/test_json_schema.py +813 -0
  318. adminsite-0.1.0/tests/test_keys.py +704 -0
  319. adminsite-0.1.0/tests/test_keyset.py +306 -0
  320. adminsite-0.1.0/tests/test_linked_names.py +146 -0
  321. adminsite-0.1.0/tests/test_list_address.py +70 -0
  322. adminsite-0.1.0/tests/test_list_field.py +259 -0
  323. adminsite-0.1.0/tests/test_list_page.py +496 -0
  324. adminsite-0.1.0/tests/test_list_refresh.py +100 -0
  325. adminsite-0.1.0/tests/test_list_totals.py +265 -0
  326. adminsite-0.1.0/tests/test_many_links.py +351 -0
  327. adminsite-0.1.0/tests/test_many_sites.py +186 -0
  328. adminsite-0.1.0/tests/test_markup.py +105 -0
  329. adminsite-0.1.0/tests/test_messages.py +157 -0
  330. adminsite-0.1.0/tests/test_model_view.py +350 -0
  331. adminsite-0.1.0/tests/test_page_size.py +113 -0
  332. adminsite-0.1.0/tests/test_pages.py +287 -0
  333. adminsite-0.1.0/tests/test_palette.py +124 -0
  334. adminsite-0.1.0/tests/test_permissions.py +342 -0
  335. adminsite-0.1.0/tests/test_picker.py +338 -0
  336. adminsite-0.1.0/tests/test_picker_access.py +207 -0
  337. adminsite-0.1.0/tests/test_record_names.py +145 -0
  338. adminsite-0.1.0/tests/test_record_pages.py +395 -0
  339. adminsite-0.1.0/tests/test_reference_docs.py +204 -0
  340. adminsite-0.1.0/tests/test_reference_orders.py +350 -0
  341. adminsite-0.1.0/tests/test_reference_views.py +241 -0
  342. adminsite-0.1.0/tests/test_relation_views.py +546 -0
  343. adminsite-0.1.0/tests/test_repository.py +421 -0
  344. adminsite-0.1.0/tests/test_row_actions.py +365 -0
  345. adminsite-0.1.0/tests/test_row_selection.py +57 -0
  346. adminsite-0.1.0/tests/test_save_context.py +663 -0
  347. adminsite-0.1.0/tests/test_saved_views.py +319 -0
  348. adminsite-0.1.0/tests/test_scope_links.py +165 -0
  349. adminsite-0.1.0/tests/test_scrolling.py +66 -0
  350. adminsite-0.1.0/tests/test_search_condition.py +103 -0
  351. adminsite-0.1.0/tests/test_security_review.py +597 -0
  352. adminsite-0.1.0/tests/test_sidebar.py +84 -0
  353. adminsite-0.1.0/tests/test_sqlmodel.py +126 -0
  354. adminsite-0.1.0/tests/test_startup_checks.py +603 -0
  355. adminsite-0.1.0/tests/test_switches.py +200 -0
  356. adminsite-0.1.0/tests/test_timezones.py +517 -0
  357. adminsite-0.1.0/tests/test_tones.py +178 -0
  358. adminsite-0.1.0/tests/test_type_mistakes.py +132 -0
  359. adminsite-0.1.0/tests/test_typed_actions.py +1024 -0
  360. adminsite-0.1.0/tests/test_typed_fields.py +294 -0
  361. adminsite-0.1.0/tests/test_view_fields.py +319 -0
  362. adminsite-0.1.0/tests/test_view_layers.py +104 -0
  363. adminsite-0.1.0/tests/test_web_layer.py +193 -0
  364. adminsite-0.1.0/tests/test_writing.py +497 -0
  365. adminsite-0.1.0/uv.lock +2170 -0
@@ -0,0 +1,4 @@
1
+ * text=auto eol=lf
2
+ *.png binary
3
+ *.ico binary
4
+ *.woff2 binary
@@ -0,0 +1,45 @@
1
+ name: Bug report
2
+ description: Something in adminsite does not work the way its docs say it does.
3
+ labels: ["bug"]
4
+ body:
5
+ - type: markdown
6
+ attributes:
7
+ value: |
8
+ A small example that shows the problem is the fastest way to a fix. The
9
+ [field gallery](https://github.com/nimaxin/adminsite/blob/main/examples/fields.py) and the
10
+ [shop](https://github.com/nimaxin/adminsite/blob/main/examples/shop.py) are good places to
11
+ start one from.
12
+ - type: input
13
+ id: version
14
+ attributes:
15
+ label: adminsite version
16
+ placeholder: 0.1.0
17
+ validations:
18
+ required: true
19
+ - type: textarea
20
+ id: happens
21
+ attributes:
22
+ label: What happens
23
+ description: What you did and what you saw. Paste any error with its traceback.
24
+ validations:
25
+ required: true
26
+ - type: textarea
27
+ id: expected
28
+ attributes:
29
+ label: What you expected
30
+ validations:
31
+ required: true
32
+ - type: textarea
33
+ id: reproduce
34
+ attributes:
35
+ label: How to reproduce it
36
+ description: The smallest models and view that show it.
37
+ render: python
38
+ - type: input
39
+ id: setup
40
+ attributes:
41
+ label: Where it runs
42
+ description: >-
43
+ Python, SQLAlchemy, the database and its driver, the framework, and the browser if the
44
+ problem is on a page.
45
+ placeholder: Python 3.13, SQLAlchemy 2.0.44, Postgres 17 with asyncpg, FastAPI, Chrome 153
@@ -0,0 +1,11 @@
1
+ blank_issues_enabled: true
2
+ contact_links:
3
+ - name: Ask a question
4
+ url: https://github.com/nimaxin/adminsite/discussions/categories/q-a
5
+ about: How to do something with adminsite, or whether it can. Ask in Discussions.
6
+ - name: Documentation
7
+ url: https://nimaxin.github.io/adminsite/
8
+ about: How to install adminsite, and every option it has.
9
+ - name: Live demo
10
+ url: https://adminsite.duckdns.org
11
+ about: Try adminsite in the browser. Sign in as admin with the password admin.
@@ -0,0 +1,23 @@
1
+ name: Feature request
2
+ description: Something adminsite does not do yet.
3
+ labels: ["enhancement"]
4
+ body:
5
+ - type: textarea
6
+ id: missing
7
+ attributes:
8
+ label: What is missing
9
+ description: What you want to do and cannot, and how you work around it today.
10
+ validations:
11
+ required: true
12
+ - type: textarea
13
+ id: why
14
+ attributes:
15
+ label: Why it matters
16
+ description: Who needs it and how often, such as the kind of project and the people who use its pages.
17
+ validations:
18
+ required: true
19
+ - type: textarea
20
+ id: shape
21
+ attributes:
22
+ label: What it could look like
23
+ description: The code you would like to write, or what the page would do.
@@ -0,0 +1,7 @@
1
+ What this changes, and why. For anything bigger than a fix, link the issue where it was agreed.
2
+
3
+ - [ ] `ruff check`, `ruff format --check`, `mypy src tests`, `python -m tests.doc_examples` and
4
+ `pytest` pass
5
+ - [ ] A line in `CHANGELOG.md` under Unreleased, if a user would notice the change
6
+ - [ ] The docs page the change belongs to says what it does now
7
+ - [ ] Any new visible text is translated in `locales/fa.json`
@@ -0,0 +1,69 @@
1
+ """Write a release's notes from its section of CHANGELOG.md.
2
+
3
+ python .github/release_notes.py 0.1.0 > notes.md
4
+
5
+ The changelog wraps its lines, and GitHub shows every line break in release
6
+ notes, so each paragraph and each point is joined back onto one line; table
7
+ rows stay one to a line.
8
+ """
9
+
10
+ import sys
11
+ from pathlib import Path
12
+
13
+ CHANGELOG = Path(__file__).parent.parent / "CHANGELOG.md"
14
+
15
+
16
+ def section(version: str) -> list[str]:
17
+ """The lines under the version's heading, up to the next heading."""
18
+ found: list[str] = []
19
+ inside = False
20
+ for line in CHANGELOG.read_text(encoding="utf-8").splitlines():
21
+ if line.startswith("## "):
22
+ if inside:
23
+ break
24
+ inside = line == f"## {version}"
25
+ continue
26
+ if inside:
27
+ found.append(line)
28
+ return found
29
+
30
+
31
+ def unwrapped(lines: list[str]) -> list[str]:
32
+ """Each paragraph and each point on one line."""
33
+ joined: list[str] = []
34
+ fenced = False
35
+ for line in lines:
36
+ if line.startswith("```"):
37
+ fenced = not fenced
38
+ joined.append(line)
39
+ continue
40
+ starts_part = (
41
+ fenced
42
+ or not line.strip()
43
+ or line.lstrip().startswith(("- ", "#", "|"))
44
+ or not joined
45
+ or not joined[-1].strip()
46
+ or joined[-1].startswith("```")
47
+ )
48
+ if starts_part:
49
+ joined.append(line)
50
+ else:
51
+ joined[-1] = f"{joined[-1]} {line.strip()}"
52
+ return joined
53
+
54
+
55
+ def main() -> int:
56
+ """Print the notes for the version, or say why there are none."""
57
+ version = sys.argv[1].removeprefix("v")
58
+ lines = unwrapped(section(version))
59
+ if not any(line.strip() for line in lines):
60
+ message = f"CHANGELOG.md has no section for {version}. Add one and tag again."
61
+ print(message, file=sys.stderr)
62
+ return 1
63
+ notes = ["```", f"pip install adminsite=={version}", "```", *lines]
64
+ sys.stdout.write("\n".join(notes).strip() + "\n")
65
+ return 0
66
+
67
+
68
+ if __name__ == "__main__":
69
+ raise SystemExit(main())
@@ -0,0 +1,113 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ concurrency:
9
+ group: ${{ github.workflow }}-${{ github.ref }}
10
+ cancel-in-progress: true
11
+
12
+ jobs:
13
+ check:
14
+ name: Lint and types
15
+ runs-on: ubuntu-latest
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+ - uses: astral-sh/setup-uv@v6
19
+ with:
20
+ enable-cache: true
21
+ - run: uv sync --all-groups
22
+ - run: uv run ruff check .
23
+ - run: uv run ruff format --check .
24
+ - run: uv run mypy src tests
25
+ - run: uv run python -m tests.doc_examples
26
+
27
+ test:
28
+ name: Tests on ${{ matrix.python }}
29
+ runs-on: ubuntu-latest
30
+ strategy:
31
+ fail-fast: false
32
+ matrix:
33
+ python: ["3.11", "3.12", "3.13", "3.14"]
34
+ steps:
35
+ - uses: actions/checkout@v4
36
+ - uses: astral-sh/setup-uv@v6
37
+ with:
38
+ enable-cache: true
39
+ python-version: ${{ matrix.python }}
40
+ - run: uv sync --all-groups
41
+ - run: uv run pytest -q
42
+
43
+ # The lock file pins the latest SQLAlchemy, so this job checks the oldest
44
+ # series adminsite supports: its types and its behaviour.
45
+ sqlalchemy-2-0:
46
+ name: Types and tests on SQLAlchemy 2.0
47
+ runs-on: ubuntu-latest
48
+ steps:
49
+ - uses: actions/checkout@v4
50
+ - uses: astral-sh/setup-uv@v6
51
+ with:
52
+ enable-cache: true
53
+ - run: uv sync --all-groups
54
+ - run: uv pip install "sqlalchemy[asyncio]>=2.0.30,<2.1"
55
+ - run: uv run --no-sync python -c "import sqlalchemy; print(sqlalchemy.__version__)"
56
+ - run: uv run --no-sync mypy src tests
57
+ - run: uv run --no-sync python -m tests.doc_examples
58
+ - run: uv run --no-sync pytest -q
59
+
60
+ postgres:
61
+ name: Tests on Postgres
62
+ runs-on: ubuntu-latest
63
+ services:
64
+ postgres:
65
+ image: postgres:17-alpine
66
+ env:
67
+ POSTGRES_USER: adminsite
68
+ POSTGRES_PASSWORD: adminsite
69
+ POSTGRES_DB: adminsite
70
+ ports:
71
+ - 5432:5432
72
+ options: >-
73
+ --health-cmd "pg_isready -U adminsite"
74
+ --health-interval 5s
75
+ --health-timeout 5s
76
+ --health-retries 10
77
+ env:
78
+ ADMINSITE_POSTGRES_URL: postgresql://adminsite:adminsite@localhost:5432/adminsite
79
+ steps:
80
+ - uses: actions/checkout@v4
81
+ - uses: astral-sh/setup-uv@v6
82
+ with:
83
+ enable-cache: true
84
+ - run: uv sync --all-groups
85
+ - run: uv run pytest -q
86
+
87
+ mysql:
88
+ name: Tests on MySQL
89
+ runs-on: ubuntu-latest
90
+ services:
91
+ mysql:
92
+ image: mysql:8.4
93
+ env:
94
+ MYSQL_ROOT_PASSWORD: adminsite
95
+ MYSQL_DATABASE: adminsite
96
+ MYSQL_USER: adminsite
97
+ MYSQL_PASSWORD: adminsite
98
+ ports:
99
+ - 3306:3306
100
+ options: >-
101
+ --health-cmd "mysqladmin ping -uadminsite -padminsite"
102
+ --health-interval 5s
103
+ --health-timeout 5s
104
+ --health-retries 20
105
+ env:
106
+ ADMINSITE_MYSQL_URL: mysql://adminsite:adminsite@127.0.0.1:3306/adminsite
107
+ steps:
108
+ - uses: actions/checkout@v4
109
+ - uses: astral-sh/setup-uv@v6
110
+ with:
111
+ enable-cache: true
112
+ - run: uv sync --all-groups
113
+ - run: uv run pytest -q -k mysql
@@ -0,0 +1,55 @@
1
+ name: Demo
2
+
3
+ # Deploys the online demo once CI has passed on a push to main.
4
+ on:
5
+ workflow_run:
6
+ workflows: [CI]
7
+ types: [completed]
8
+ branches: [main]
9
+ workflow_dispatch:
10
+
11
+ permissions:
12
+ contents: read
13
+
14
+ concurrency:
15
+ group: demo
16
+ cancel-in-progress: false
17
+
18
+ jobs:
19
+ deploy:
20
+ name: Deploy the demo
21
+ # Only a green CI run of a push to this repository: a pull request from a
22
+ # fork's own main branch must never reach the server.
23
+ if: >-
24
+ github.event_name == 'workflow_dispatch' ||
25
+ (github.event.workflow_run.conclusion == 'success' &&
26
+ github.event.workflow_run.event == 'push' &&
27
+ github.event.workflow_run.head_repository.full_name == github.repository)
28
+ runs-on: ubuntu-latest
29
+ environment:
30
+ name: demo
31
+ url: https://${{ vars.DEMO_HOST }}
32
+ steps:
33
+ - name: Trust the server and hold the deploy key
34
+ env:
35
+ DEPLOY_KEY: ${{ secrets.DEMO_SSH_KEY }}
36
+ KNOWN_HOSTS: ${{ secrets.DEMO_KNOWN_HOSTS }}
37
+ run: |
38
+ install -m 700 -d ~/.ssh
39
+ printf '%s\n' "$DEPLOY_KEY" > ~/.ssh/demo
40
+ chmod 600 ~/.ssh/demo
41
+ printf '%s\n' "$KNOWN_HOSTS" > ~/.ssh/known_hosts
42
+
43
+ # The server runs its deploy script with the commit, and nothing else.
44
+ - name: Deploy the commit CI checked
45
+ env:
46
+ HOST: ${{ vars.DEMO_HOST }}
47
+ COMMIT: ${{ github.event.workflow_run.head_sha || github.sha }}
48
+ run: ssh -i ~/.ssh/demo -o IdentitiesOnly=yes -o BatchMode=yes "deploy@$HOST" "$COMMIT"
49
+
50
+ - name: Check the demo answers
51
+ env:
52
+ HOST: ${{ vars.DEMO_HOST }}
53
+ run: >-
54
+ curl --fail --silent --show-error --retry 10 --retry-delay 3
55
+ --retry-all-errors "https://$HOST/healthz"
@@ -0,0 +1,39 @@
1
+ name: Docs
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ workflow_dispatch:
7
+
8
+ permissions:
9
+ contents: read
10
+ pages: write
11
+ id-token: write
12
+
13
+ concurrency:
14
+ group: pages
15
+ cancel-in-progress: true
16
+
17
+ jobs:
18
+ build:
19
+ runs-on: ubuntu-latest
20
+ steps:
21
+ - uses: actions/checkout@v4
22
+ - uses: astral-sh/setup-uv@v6
23
+ with:
24
+ enable-cache: true
25
+ - run: uv sync --group docs
26
+ - run: uv run --group docs mkdocs build --strict
27
+ - uses: actions/upload-pages-artifact@v3
28
+ with:
29
+ path: site
30
+
31
+ deploy:
32
+ needs: build
33
+ runs-on: ubuntu-latest
34
+ environment:
35
+ name: github-pages
36
+ url: ${{ steps.deployment.outputs.page_url }}
37
+ steps:
38
+ - id: deployment
39
+ uses: actions/deploy-pages@v4
@@ -0,0 +1,67 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags: ["v*"]
6
+
7
+ jobs:
8
+ build:
9
+ name: Build the distributions
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+ - uses: astral-sh/setup-uv@v6
14
+ with:
15
+ enable-cache: true
16
+ - run: uv sync --all-groups
17
+ - run: uv run ruff check .
18
+ - run: uv run mypy src tests
19
+ - run: uv run pytest -q
20
+ - run: uv build
21
+ - uses: actions/upload-artifact@v4
22
+ with:
23
+ name: distributions
24
+ path: dist/
25
+
26
+ publish:
27
+ name: Publish to PyPI
28
+ needs: build
29
+ runs-on: ubuntu-latest
30
+ environment: pypi
31
+ # Trusted publishing: PyPI trusts this workflow, so there is no token
32
+ # to keep anywhere.
33
+ permissions:
34
+ id-token: write
35
+ steps:
36
+ - uses: actions/download-artifact@v4
37
+ with:
38
+ name: distributions
39
+ path: dist/
40
+ - uses: pypa/gh-action-pypi-publish@release/v1
41
+
42
+ github:
43
+ name: Create the GitHub release
44
+ needs: publish
45
+ runs-on: ubuntu-latest
46
+ # The release sits beside the tag, so the repository page names the same
47
+ # version PyPI has, and people who watch releases hear of it.
48
+ permissions:
49
+ contents: write
50
+ steps:
51
+ - uses: actions/checkout@v4
52
+ - uses: actions/setup-python@v5
53
+ with:
54
+ python-version: "3.13"
55
+ - name: Write the notes from the changelog
56
+ run: python .github/release_notes.py "$GITHUB_REF_NAME" > notes.md
57
+ - name: Create the release
58
+ env:
59
+ GH_TOKEN: ${{ github.token }}
60
+ run: |
61
+ version="${GITHUB_REF_NAME#v}"
62
+ kind=""
63
+ case "$version" in
64
+ *[0-9]a[0-9]* | *[0-9]b[0-9]* | *rc[0-9]*) kind="--prerelease" ;;
65
+ esac
66
+ gh release create "$GITHUB_REF_NAME" --verify-tag $kind \
67
+ --title "adminsite $version" --notes-file notes.md
@@ -0,0 +1,34 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ build/
6
+ dist/
7
+
8
+ # Environments
9
+ .venv/
10
+
11
+ # Tools
12
+ .pytest_cache/
13
+ .mypy_cache/
14
+ .ruff_cache/
15
+ .coverage
16
+ htmlcov/
17
+
18
+ # Frontend build
19
+ node_modules/
20
+ frontend/package-lock.json
21
+
22
+ # Build output
23
+ dist/
24
+
25
+ # Example app databases
26
+ shop.db
27
+ adminsite_audit.db
28
+ adminsite_views.db
29
+ shop_uploads/
30
+ fields_uploads/
31
+ demo-data/
32
+
33
+ # Built docs
34
+ site/
@@ -0,0 +1 @@
1
+ 3.13
@@ -0,0 +1,159 @@
1
+ # Working on adminsite
2
+
3
+ adminsite is an admin panel for SQLAlchemy 2.0 models, served as an ASGI app that people mount
4
+ into Starlette, FastAPI or Litestar. Python 3.11+, managed with uv.
5
+
6
+ This file is for agents changing adminsite itself. Agents using adminsite in another project should
7
+ read https://nimaxin.github.io/adminsite/llms.txt instead.
8
+
9
+ ## Layout
10
+
11
+ - `src/adminsite/` is the package. `admin.py` builds the app and its routes, `views/` holds
12
+ `ModelView` and the parts it is built from (below), `_http/` holds one module per page,
13
+ `_orm/` builds the statements a view runs, `database.py` hands out sessions, `inspector.py`
14
+ reads the models, `timezones.py` moves times between the database and each reader's clock, and
15
+ `fields/` and `filters/` are what they say. A name starting with an underscore is machinery no
16
+ project imports.
17
+ - `src/adminsite/templates/adminsite/` holds the Jinja templates; `widgets/` has one file per form
18
+ control.
19
+ - `src/adminsite/static/adminsite.css` is built from `frontend/` and committed. HTMX and Alpine are
20
+ vendored next to it.
21
+ - `src/adminsite/locales/` holds the translation catalogs, one JSON file per language.
22
+ - `docs/` is the MkDocs site, and `overrides/main.html` gives its pages their link previews;
23
+ `examples/shop.py` is the demo shop, and `examples/fields.py` a gallery with every kind of
24
+ field, each labelled by its class. Add a new field type to it.
25
+ - `demo/` serves both at https://adminsite.duckdns.org, signed in as admin / admin and reset
26
+ every hour. `.github/workflows/demo.yml` deploys it after CI passes on main; `demo/README.md`
27
+ describes the server.
28
+ - `tests/` runs every database test on SQLite async and sync, and on Postgres and MySQL when their
29
+ URLs are set.
30
+
31
+ ## Inside views/
32
+
33
+ `ModelView` keeps what users write: the settings, the `get_` methods, `allows`, `scope_query`,
34
+ `get_actions` and the hooks. The work is done by parts it builds in `__init__`, from the bottom row
35
+ up, each handed the parts it uses. Templates and `_http/` reach a part through the view, such as
36
+ `view._pages.form_fields(request, record)`. A part's module is named for the attribute the view
37
+ holds it as, so `view._reader` lives in `_reader.py`, and the modules a project imports carry no
38
+ underscore. A module imports only modules of the rows below its own; `tests/test_view_layers.py`
39
+ fails otherwise.
40
+
41
+ | Row | Module | What it does |
42
+ | --- | --- | --- |
43
+ | 1 | `_checks.py` | The startup checks, and the messages they stop the admin with |
44
+ | 1 | `_linked_names.py` | How a linked record is named |
45
+ | 1 | `layout.py` | `PanelWidget`, `FieldsetWidget`, `RowWidget`, `TabsWidget`, and arranging them for one page |
46
+ | 1 | `inlines.py` | `Inline`, the setting |
47
+ | 1 | `contexts.py` | `SaveContext`, `DeleteContext` and `SaveValues`, which the hooks get |
48
+ | 2 | `_settings.py` | The settings read into paths and checked, once, at startup |
49
+ | 3 | `_fields.py` | The field for each path, and what a record holds and shows there |
50
+ | 3 | `_inline_views.py` | The child view of each inline |
51
+ | 4 | `_pages.py` | The fields each page shows this user, and which of them are editable |
52
+ | 5 | `_reader.py` | Pages of records, one record, filter choices and computed values |
53
+ | 5 | `_forms.py` | A submitted form, its inline rows and an action's inputs, as values |
54
+ | 5 | `_audit.py` | The entries a change writes to the audit log |
55
+ | 6 | `_links.py` | The records a submitted link names, read through the linked view |
56
+ | 7 | `_saver.py` | A save or a delete in one transaction, with its hooks and files |
57
+ | 7 | `_actions.py` | The actions offered to a request, run and written down |
58
+ | 8 | `model_view.py` | `ModelView` itself |
59
+ | 9 | `registry.py`, `_picker.py` | The admin's views; the records a link may offer |
60
+
61
+ Where a change goes:
62
+
63
+ - **A setting that names columns:** an attribute on `ModelView`, with a docstring under it, read
64
+ into paths in `SettingsReader.__init__`, which checks every name. When the answer can depend on
65
+ the request, add a `get_` method beside it; `PageFields` calls it and checks an answer other
66
+ than the setting itself through `SettingsReader`.
67
+ - **A startup check:** a function in `_checks.py`, called where the setting is read.
68
+ - **A hook:** a method on `ModelView` that does nothing by default, called by the part that does
69
+ the work, such as `Saver` for a save.
70
+ - **A rule about which fields a page shows:** `PageFields`, which the pages, the export, the
71
+ import and the API all ask.
72
+ - **Where a page puts its fields:** `form_layout`, read in `SettingsReader` and arranged for each
73
+ page by `PageFields.arranged`; the form and the record page draw what it returns.
74
+ - **A new module:** a row in `LAYERS` in `tests/test_view_layers.py`, and in the table above.
75
+
76
+ ## Commands
77
+
78
+ ```bash
79
+ uv sync --all-groups
80
+ uv run ruff check . && uv run ruff format --check .
81
+ uv run mypy src tests
82
+ uv run python -m tests.doc_examples
83
+ uv run pytest -q
84
+ uv run --group docs mkdocs build --strict
85
+ ```
86
+
87
+ Postgres and MySQL:
88
+
89
+ ```bash
90
+ docker run -d --name adminsite-postgres -p 55432:5432 -e POSTGRES_USER=adminsite -e POSTGRES_PASSWORD=adminsite -e POSTGRES_DB=adminsite postgres:17-alpine
91
+ docker run -d --name adminsite-mysql -p 53306:3306 -e MYSQL_ROOT_PASSWORD=adminsite -e MYSQL_DATABASE=adminsite -e MYSQL_USER=adminsite -e MYSQL_PASSWORD=adminsite mysql:8.4
92
+ ADMINSITE_POSTGRES_URL=postgresql://adminsite:adminsite@localhost:55432/adminsite \
93
+ ADMINSITE_MYSQL_URL=mysql://adminsite:adminsite@127.0.0.1:53306/adminsite \
94
+ uv run pytest -q
95
+ ```
96
+
97
+ After changing classes in a template, rebuild the stylesheet, or the new classes will not exist:
98
+
99
+ ```bash
100
+ cd frontend && npx @tailwindcss/cli -i ./input.css -o ../src/adminsite/static/adminsite.css --minify
101
+ ```
102
+
103
+ Run the demo with `uv run uvicorn examples.shop:app --reload`, then open
104
+ http://127.0.0.1:8000/admin and sign in as nima / letmein. To see every field after changing one,
105
+ run `uv run uvicorn examples.fields:app --reload` instead; it needs no sign in.
106
+
107
+ ## Rules that are easy to break
108
+
109
+ - **Every visible text is translated.** Templates use `{{ _('Save') }}`, Python uses
110
+ `from adminsite.i18n import gettext as _` and `_("{thing} saved.", thing=...)` with named
111
+ placeholders, never f-strings. Add each new text to `locales/fa.json`;
112
+ `uv run python -m tests.messages fa` lists what is missing, and a test fails until it is done.
113
+ - **Layouts mirror for right to left languages.** Use `ms-`, `me-`, `ps-`, `pe-`, `start-`, `end-`,
114
+ `text-start`, `text-end`, `border-s`, `border-e`, never left or right.
115
+ - **Every control has an accessible name**: a `<label for>`, `aria-label` or `aria-labelledby`.
116
+ `tests/test_accessibility.py` checks the main pages.
117
+ - **Reads go through the view**, so `scope_query` and `allows` always apply. Never query a model
118
+ directly from an endpoint. Nothing under `_http/` imports SQLAlchemy or the ORM layer
119
+ (`adminsite._orm`, `adminsite.database`, `adminsite.inspector`, the SQL filters) when it runs,
120
+ only `SessionAdapter` under `TYPE_CHECKING` for annotations; `tests/test_web_layer.py` fails
121
+ otherwise. Catch `adminsite.exceptions.IntegrityError`, which the session raises for a change
122
+ the database refuses, never SQLAlchemy's.
123
+ - **No N+1 queries.** Load what a page shows with the page; the tests count statements.
124
+ - **Code must work on SQLite, Postgres and MySQL.** Convert keys read from URLs with
125
+ `to_column_type`. MySQL refuses a `LIMIT` straight inside `IN (...)`.
126
+ - **The session is only saved when a key is set.** Assign a new value; never change a list or
127
+ dict inside it in place.
128
+ - **Examples type-check.** `python -m tests.doc_examples` runs mypy strict over every python block
129
+ in `docs/`, the README and the docstrings, and refuses a view setting `ModelView` does not have.
130
+ A name an example takes as given, such as a model, goes in `tests/doc_examples/context.py`.
131
+ - **Text files use LF endings.** `.gitattributes` enforces it; keep it that way when writing files
132
+ from scripts.
133
+
134
+ ## Style
135
+
136
+ - Write like a person: plain, specific, short. No marketing tone, no filler, no "seamless" or
137
+ "robust". Avoid em dashes; use a full stop, a comma or a colon.
138
+ - Names read as words: `fetch_page`, `can_import`, `saved_for`. No abbreviations.
139
+ - Docstrings say what a thing is or does in one line. Comments explain why, and only where the code
140
+ cannot.
141
+ - What `docs/reference.md` shows is documented in full, in Google style, because the Reference
142
+ page and editors read it from there: `Args:` for each parameter, the constructor's in the class
143
+ docstring; `Returns:` for what it answers; `Raises:` for an error the caller is meant to handle;
144
+ and a docstring under each attribute people set or read, such as a setting of `ModelView`.
145
+ Options a class inherits are described once, on the class they come from.
146
+ `tests/test_reference_docs.py` fails on a gap. Everything else keeps its one line, plus a
147
+ sentence for a parameter whose name and type do not say enough.
148
+ - Messages tell people what happened and what to do: "Keep the file under 5 MB.", not
149
+ "Error: file size exceeded".
150
+ - ruff at 88 columns and mypy strict are the rules; do not silence them without a reason in a
151
+ comment.
152
+
153
+ ## Workflow
154
+
155
+ - Plan first when asked to plan, and wait for approval before building.
156
+ - Commit after each finished step, with a message such as `feat: ...`, `fix: ...` or `docs: ...`.
157
+ - Add a line to `CHANGELOG.md` under Unreleased for anything a user would notice, and update the
158
+ docs page the change belongs to.
159
+ - Never push, tag or publish unless asked. A tag `v*` publishes to PyPI.