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.
- adminsite-0.1.0/.gitattributes +4 -0
- adminsite-0.1.0/.github/ISSUE_TEMPLATE/bug_report.yml +45 -0
- adminsite-0.1.0/.github/ISSUE_TEMPLATE/config.yml +11 -0
- adminsite-0.1.0/.github/ISSUE_TEMPLATE/feature_request.yml +23 -0
- adminsite-0.1.0/.github/pull_request_template.md +7 -0
- adminsite-0.1.0/.github/release_notes.py +69 -0
- adminsite-0.1.0/.github/workflows/ci.yml +113 -0
- adminsite-0.1.0/.github/workflows/demo.yml +55 -0
- adminsite-0.1.0/.github/workflows/docs.yml +39 -0
- adminsite-0.1.0/.github/workflows/release.yml +67 -0
- adminsite-0.1.0/.gitignore +34 -0
- adminsite-0.1.0/.python-version +1 -0
- adminsite-0.1.0/AGENTS.md +159 -0
- adminsite-0.1.0/CHANGELOG.md +1147 -0
- adminsite-0.1.0/CLAUDE.md +1 -0
- adminsite-0.1.0/CODE_OF_CONDUCT.md +83 -0
- adminsite-0.1.0/CONTRIBUTING.md +84 -0
- adminsite-0.1.0/LICENSE +21 -0
- adminsite-0.1.0/PKG-INFO +172 -0
- adminsite-0.1.0/README.md +130 -0
- adminsite-0.1.0/SECURITY.md +33 -0
- adminsite-0.1.0/demo/Caddyfile +11 -0
- adminsite-0.1.0/demo/Dockerfile +37 -0
- adminsite-0.1.0/demo/Dockerfile.dockerignore +11 -0
- adminsite-0.1.0/demo/README.md +45 -0
- adminsite-0.1.0/demo/app.py +245 -0
- adminsite-0.1.0/demo/compose.yaml +54 -0
- adminsite-0.1.0/demo/deploy.sh +34 -0
- adminsite-0.1.0/docs/accessibility.md +44 -0
- adminsite-0.1.0/docs/actions.md +411 -0
- adminsite-0.1.0/docs/api.md +124 -0
- adminsite-0.1.0/docs/assets/architecture.png +0 -0
- adminsite-0.1.0/docs/assets/orders.png +0 -0
- adminsite-0.1.0/docs/assets/social-preview.png +0 -0
- adminsite-0.1.0/docs/audit.md +325 -0
- adminsite-0.1.0/docs/auth.md +156 -0
- adminsite-0.1.0/docs/changelog.md +1 -0
- adminsite-0.1.0/docs/customizing.md +99 -0
- adminsite-0.1.0/docs/dashboard.md +128 -0
- adminsite-0.1.0/docs/databases.md +164 -0
- adminsite-0.1.0/docs/fields.md +706 -0
- adminsite-0.1.0/docs/filters.md +164 -0
- adminsite-0.1.0/docs/getting-started.md +182 -0
- adminsite-0.1.0/docs/hooks.md +160 -0
- adminsite-0.1.0/docs/import.md +68 -0
- adminsite-0.1.0/docs/index.md +63 -0
- adminsite-0.1.0/docs/pages.md +130 -0
- adminsite-0.1.0/docs/permissions.md +215 -0
- adminsite-0.1.0/docs/reference.md +319 -0
- adminsite-0.1.0/docs/translations.md +207 -0
- adminsite-0.1.0/docs/views.md +619 -0
- adminsite-0.1.0/examples/fields.py +891 -0
- adminsite-0.1.0/examples/shop.py +423 -0
- adminsite-0.1.0/frontend/architecture.html +390 -0
- adminsite-0.1.0/frontend/input.css +386 -0
- adminsite-0.1.0/frontend/package.json +19 -0
- adminsite-0.1.0/frontend/social-preview.html +121 -0
- adminsite-0.1.0/frontend/vendor.mjs +21 -0
- adminsite-0.1.0/mkdocs.yml +167 -0
- adminsite-0.1.0/overrides/main.html +31 -0
- adminsite-0.1.0/pyproject.toml +148 -0
- adminsite-0.1.0/src/adminsite/__init__.py +69 -0
- adminsite-0.1.0/src/adminsite/_http/__init__.py +1 -0
- adminsite-0.1.0/src/adminsite/_http/actions.py +163 -0
- adminsite-0.1.0/src/adminsite/_http/activity.py +295 -0
- adminsite-0.1.0/src/adminsite/_http/api.py +461 -0
- adminsite-0.1.0/src/adminsite/_http/csrf.py +71 -0
- adminsite-0.1.0/src/adminsite/_http/dashboard.py +21 -0
- adminsite-0.1.0/src/adminsite/_http/detail.py +278 -0
- adminsite-0.1.0/src/adminsite/_http/document_forms.py +229 -0
- adminsite-0.1.0/src/adminsite/_http/editing.py +339 -0
- adminsite-0.1.0/src/adminsite/_http/export.py +166 -0
- adminsite-0.1.0/src/adminsite/_http/files.py +30 -0
- adminsite-0.1.0/src/adminsite/_http/form_rows.py +123 -0
- adminsite-0.1.0/src/adminsite/_http/forms.py +278 -0
- adminsite-0.1.0/src/adminsite/_http/history.py +234 -0
- adminsite-0.1.0/src/adminsite/_http/importing.py +231 -0
- adminsite-0.1.0/src/adminsite/_http/inline_tables.py +289 -0
- adminsite-0.1.0/src/adminsite/_http/languages.py +33 -0
- adminsite-0.1.0/src/adminsite/_http/list_editing.py +184 -0
- adminsite-0.1.0/src/adminsite/_http/listing.py +599 -0
- adminsite-0.1.0/src/adminsite/_http/lookups.py +113 -0
- adminsite-0.1.0/src/adminsite/_http/pages.py +33 -0
- adminsite-0.1.0/src/adminsite/_http/palette.py +121 -0
- adminsite-0.1.0/src/adminsite/_http/requests.py +107 -0
- adminsite-0.1.0/src/adminsite/_http/saved_views.py +115 -0
- adminsite-0.1.0/src/adminsite/_http/signing_in.py +114 -0
- adminsite-0.1.0/src/adminsite/_http/templating.py +169 -0
- adminsite-0.1.0/src/adminsite/_http/timezones.py +86 -0
- adminsite-0.1.0/src/adminsite/_http/urls.py +226 -0
- adminsite-0.1.0/src/adminsite/_importing.py +446 -0
- adminsite-0.1.0/src/adminsite/_orm/__init__.py +1 -0
- adminsite-0.1.0/src/adminsite/_orm/cursor.py +65 -0
- adminsite-0.1.0/src/adminsite/_orm/loader.py +66 -0
- adminsite-0.1.0/src/adminsite/_orm/repository.py +767 -0
- adminsite-0.1.0/src/adminsite/_orm/values.py +39 -0
- adminsite-0.1.0/src/adminsite/_storage.py +86 -0
- adminsite-0.1.0/src/adminsite/_text.py +104 -0
- adminsite-0.1.0/src/adminsite/actions/__init__.py +9 -0
- adminsite-0.1.0/src/adminsite/actions/_parameters.py +595 -0
- adminsite-0.1.0/src/adminsite/actions/base.py +262 -0
- adminsite-0.1.0/src/adminsite/actions/selection.py +257 -0
- adminsite-0.1.0/src/adminsite/admin.py +831 -0
- adminsite-0.1.0/src/adminsite/audit/__init__.py +22 -0
- adminsite-0.1.0/src/adminsite/audit/_actor.py +52 -0
- adminsite-0.1.0/src/adminsite/audit/_inputs.py +72 -0
- adminsite-0.1.0/src/adminsite/audit/entry.py +218 -0
- adminsite-0.1.0/src/adminsite/audit/log.py +181 -0
- adminsite-0.1.0/src/adminsite/audit/store.py +116 -0
- adminsite-0.1.0/src/adminsite/auth/__init__.py +11 -0
- adminsite-0.1.0/src/adminsite/auth/passwords.py +70 -0
- adminsite-0.1.0/src/adminsite/auth/provider.py +255 -0
- adminsite-0.1.0/src/adminsite/columns.py +167 -0
- adminsite-0.1.0/src/adminsite/dashboard.py +496 -0
- adminsite-0.1.0/src/adminsite/database.py +491 -0
- adminsite-0.1.0/src/adminsite/exceptions.py +170 -0
- adminsite-0.1.0/src/adminsite/fields/__init__.py +46 -0
- adminsite-0.1.0/src/adminsite/fields/_documents/__init__.py +20 -0
- adminsite-0.1.0/src/adminsite/fields/_documents/document.py +496 -0
- adminsite-0.1.0/src/adminsite/fields/_documents/error_messages.py +115 -0
- adminsite-0.1.0/src/adminsite/fields/_documents/inputs.py +294 -0
- adminsite-0.1.0/src/adminsite/fields/_documents/schema_reader.py +302 -0
- adminsite-0.1.0/src/adminsite/fields/_documents/shapes.py +118 -0
- adminsite-0.1.0/src/adminsite/fields/_documents/shown.py +123 -0
- adminsite-0.1.0/src/adminsite/fields/base.py +326 -0
- adminsite-0.1.0/src/adminsite/fields/choices.py +294 -0
- adminsite-0.1.0/src/adminsite/fields/computed.py +145 -0
- adminsite-0.1.0/src/adminsite/fields/files.py +200 -0
- adminsite-0.1.0/src/adminsite/fields/json_field.py +260 -0
- adminsite-0.1.0/src/adminsite/fields/lists.py +101 -0
- adminsite-0.1.0/src/adminsite/fields/passwords.py +57 -0
- adminsite-0.1.0/src/adminsite/fields/registry.py +118 -0
- adminsite-0.1.0/src/adminsite/fields/relations.py +119 -0
- adminsite-0.1.0/src/adminsite/fields/scalars.py +170 -0
- adminsite-0.1.0/src/adminsite/fields/temporal.py +169 -0
- adminsite-0.1.0/src/adminsite/fields/tones.py +36 -0
- adminsite-0.1.0/src/adminsite/files.py +183 -0
- adminsite-0.1.0/src/adminsite/filters/__init__.py +28 -0
- adminsite-0.1.0/src/adminsite/filters/base.py +147 -0
- adminsite-0.1.0/src/adminsite/filters/sql.py +542 -0
- adminsite-0.1.0/src/adminsite/i18n.py +446 -0
- adminsite-0.1.0/src/adminsite/inspector.py +280 -0
- adminsite-0.1.0/src/adminsite/locales/ar.json +476 -0
- adminsite-0.1.0/src/adminsite/locales/de.json +424 -0
- adminsite-0.1.0/src/adminsite/locales/es.json +424 -0
- adminsite-0.1.0/src/adminsite/locales/fa.json +424 -0
- adminsite-0.1.0/src/adminsite/locales/fr.json +424 -0
- adminsite-0.1.0/src/adminsite/locales/ja.json +385 -0
- adminsite-0.1.0/src/adminsite/locales/pt-BR.json +424 -0
- adminsite-0.1.0/src/adminsite/locales/ru.json +437 -0
- adminsite-0.1.0/src/adminsite/locales/tr.json +424 -0
- adminsite-0.1.0/src/adminsite/locales/zh-Hans.json +385 -0
- adminsite-0.1.0/src/adminsite/locales/zh-Hant.json +385 -0
- adminsite-0.1.0/src/adminsite/markup.py +43 -0
- adminsite-0.1.0/src/adminsite/messages.py +101 -0
- adminsite-0.1.0/src/adminsite/pages.py +126 -0
- adminsite-0.1.0/src/adminsite/permissions.py +54 -0
- adminsite-0.1.0/src/adminsite/plugins.py +48 -0
- adminsite-0.1.0/src/adminsite/py.typed +0 -0
- adminsite-0.1.0/src/adminsite/query.py +150 -0
- adminsite-0.1.0/src/adminsite/saved_views.py +193 -0
- adminsite-0.1.0/src/adminsite/schema.py +169 -0
- adminsite-0.1.0/src/adminsite/static/adminsite.css +2 -0
- adminsite-0.1.0/src/adminsite/static/alpine.min.js +21 -0
- adminsite-0.1.0/src/adminsite/static/htmx.min.js +1 -0
- adminsite-0.1.0/src/adminsite/templates/adminsite/_action_forms.html +51 -0
- adminsite-0.1.0/src/adminsite/templates/adminsite/_action_inputs.html +17 -0
- adminsite-0.1.0/src/adminsite/templates/adminsite/_browser_timezone.html +12 -0
- adminsite-0.1.0/src/adminsite/templates/adminsite/_bulk_actions.html +57 -0
- adminsite-0.1.0/src/adminsite/templates/adminsite/_bulk_summary.html +21 -0
- adminsite-0.1.0/src/adminsite/templates/adminsite/_document_form.html +225 -0
- adminsite-0.1.0/src/adminsite/templates/adminsite/_document_json.html +18 -0
- adminsite-0.1.0/src/adminsite/templates/adminsite/_document_switch.html +15 -0
- adminsite-0.1.0/src/adminsite/templates/adminsite/_document_view.html +111 -0
- adminsite-0.1.0/src/adminsite/templates/adminsite/_empty.html +2 -0
- adminsite-0.1.0/src/adminsite/templates/adminsite/_field.html +55 -0
- adminsite-0.1.0/src/adminsite/templates/adminsite/_filter_chips.html +26 -0
- adminsite-0.1.0/src/adminsite/templates/adminsite/_filter_counts.html +9 -0
- adminsite-0.1.0/src/adminsite/templates/adminsite/_filters.html +136 -0
- adminsite-0.1.0/src/adminsite/templates/adminsite/_filters_count.html +4 -0
- adminsite-0.1.0/src/adminsite/templates/adminsite/_filters_footer.html +18 -0
- adminsite-0.1.0/src/adminsite/templates/adminsite/_form_layout.html +73 -0
- adminsite-0.1.0/src/adminsite/templates/adminsite/_history.html +62 -0
- adminsite-0.1.0/src/adminsite/templates/adminsite/_icons.html +54 -0
- adminsite-0.1.0/src/adminsite/templates/adminsite/_inlines.html +107 -0
- adminsite-0.1.0/src/adminsite/templates/adminsite/_json.html +73 -0
- adminsite-0.1.0/src/adminsite/templates/adminsite/_list_forms.html +52 -0
- adminsite-0.1.0/src/adminsite/templates/adminsite/_list_menus.html +27 -0
- adminsite-0.1.0/src/adminsite/templates/adminsite/_lookup.html +6 -0
- adminsite-0.1.0/src/adminsite/templates/adminsite/_message.html +27 -0
- adminsite-0.1.0/src/adminsite/templates/adminsite/_option_count.html +9 -0
- adminsite-0.1.0/src/adminsite/templates/adminsite/_page_numbers.html +21 -0
- adminsite-0.1.0/src/adminsite/templates/adminsite/_palette.html +18 -0
- adminsite-0.1.0/src/adminsite/templates/adminsite/_records_count.html +12 -0
- adminsite-0.1.0/src/adminsite/templates/adminsite/_records_counted.html +10 -0
- adminsite-0.1.0/src/adminsite/templates/adminsite/_records_total.html +9 -0
- adminsite-0.1.0/src/adminsite/templates/adminsite/_row.html +87 -0
- adminsite-0.1.0/src/adminsite/templates/adminsite/_row_actions.html +25 -0
- adminsite-0.1.0/src/adminsite/templates/adminsite/_saved_tabs.html +33 -0
- adminsite-0.1.0/src/adminsite/templates/adminsite/_table.html +220 -0
- adminsite-0.1.0/src/adminsite/templates/adminsite/_toolbar.html +38 -0
- adminsite-0.1.0/src/adminsite/templates/adminsite/_value_editing.html +137 -0
- adminsite-0.1.0/src/adminsite/templates/adminsite/_value_editor.html +18 -0
- adminsite-0.1.0/src/adminsite/templates/adminsite/_value_saved.html +4 -0
- adminsite-0.1.0/src/adminsite/templates/adminsite/_values.html +19 -0
- adminsite-0.1.0/src/adminsite/templates/adminsite/activity.html +78 -0
- adminsite-0.1.0/src/adminsite/templates/adminsite/base.html +1214 -0
- adminsite-0.1.0/src/adminsite/templates/adminsite/dashboard/chart.html +61 -0
- adminsite-0.1.0/src/adminsite/templates/adminsite/dashboard/counts.html +17 -0
- adminsite-0.1.0/src/adminsite/templates/adminsite/dashboard/recent.html +15 -0
- adminsite-0.1.0/src/adminsite/templates/adminsite/dashboard/stat.html +23 -0
- adminsite-0.1.0/src/adminsite/templates/adminsite/detail.html +268 -0
- adminsite-0.1.0/src/adminsite/templates/adminsite/error.html +16 -0
- adminsite-0.1.0/src/adminsite/templates/adminsite/form.html +158 -0
- adminsite-0.1.0/src/adminsite/templates/adminsite/import.html +61 -0
- adminsite-0.1.0/src/adminsite/templates/adminsite/import_preview.html +95 -0
- adminsite-0.1.0/src/adminsite/templates/adminsite/index.html +33 -0
- adminsite-0.1.0/src/adminsite/templates/adminsite/list.html +110 -0
- adminsite-0.1.0/src/adminsite/templates/adminsite/login.html +63 -0
- adminsite-0.1.0/src/adminsite/templates/adminsite/widgets/_option.html +14 -0
- adminsite-0.1.0/src/adminsite/templates/adminsite/widgets/_picker.html +108 -0
- adminsite-0.1.0/src/adminsite/templates/adminsite/widgets/checkbox.html +6 -0
- adminsite-0.1.0/src/adminsite/templates/adminsite/widgets/file.html +20 -0
- adminsite-0.1.0/src/adminsite/templates/adminsite/widgets/image.html +1 -0
- adminsite-0.1.0/src/adminsite/templates/adminsite/widgets/input.html +7 -0
- adminsite-0.1.0/src/adminsite/templates/adminsite/widgets/json.html +48 -0
- adminsite-0.1.0/src/adminsite/templates/adminsite/widgets/list.html +4 -0
- adminsite-0.1.0/src/adminsite/templates/adminsite/widgets/password.html +6 -0
- adminsite-0.1.0/src/adminsite/templates/adminsite/widgets/readonly.html +5 -0
- adminsite-0.1.0/src/adminsite/templates/adminsite/widgets/relation.html +32 -0
- adminsite-0.1.0/src/adminsite/templates/adminsite/widgets/select.html +18 -0
- adminsite-0.1.0/src/adminsite/templates/adminsite/widgets/textarea.html +3 -0
- adminsite-0.1.0/src/adminsite/timezones.py +146 -0
- adminsite-0.1.0/src/adminsite/views/__init__.py +19 -0
- adminsite-0.1.0/src/adminsite/views/_actions.py +452 -0
- adminsite-0.1.0/src/adminsite/views/_audit.py +151 -0
- adminsite-0.1.0/src/adminsite/views/_checks.py +398 -0
- adminsite-0.1.0/src/adminsite/views/_fields.py +291 -0
- adminsite-0.1.0/src/adminsite/views/_forms.py +271 -0
- adminsite-0.1.0/src/adminsite/views/_inline_views.py +150 -0
- adminsite-0.1.0/src/adminsite/views/_linked_names.py +106 -0
- adminsite-0.1.0/src/adminsite/views/_links.py +124 -0
- adminsite-0.1.0/src/adminsite/views/_pages.py +423 -0
- adminsite-0.1.0/src/adminsite/views/_picker.py +203 -0
- adminsite-0.1.0/src/adminsite/views/_reader.py +407 -0
- adminsite-0.1.0/src/adminsite/views/_saver.py +414 -0
- adminsite-0.1.0/src/adminsite/views/_settings.py +277 -0
- adminsite-0.1.0/src/adminsite/views/contexts.py +225 -0
- adminsite-0.1.0/src/adminsite/views/inlines.py +67 -0
- adminsite-0.1.0/src/adminsite/views/layout.py +284 -0
- adminsite-0.1.0/src/adminsite/views/model_view.py +761 -0
- adminsite-0.1.0/src/adminsite/views/registry.py +152 -0
- adminsite-0.1.0/tests/__init__.py +0 -0
- adminsite-0.1.0/tests/conftest.py +309 -0
- adminsite-0.1.0/tests/doc_examples/__init__.py +0 -0
- adminsite-0.1.0/tests/doc_examples/__main__.py +308 -0
- adminsite-0.1.0/tests/doc_examples/context.py +709 -0
- adminsite-0.1.0/tests/factories.py +86 -0
- adminsite-0.1.0/tests/messages.py +178 -0
- adminsite-0.1.0/tests/models.py +203 -0
- adminsite-0.1.0/tests/reference/__init__.py +6 -0
- adminsite-0.1.0/tests/reference/field_mistakes.py +38 -0
- adminsite-0.1.0/tests/reference/gallery.py +95 -0
- adminsite-0.1.0/tests/reference/library.py +31 -0
- adminsite-0.1.0/tests/reference/mistakes.py +44 -0
- adminsite-0.1.0/tests/reference/models.py +179 -0
- adminsite-0.1.0/tests/reference/orders.py +183 -0
- adminsite-0.1.0/tests/reference/products.py +254 -0
- adminsite-0.1.0/tests/reference/startup_mistakes.py +151 -0
- adminsite-0.1.0/tests/support.py +103 -0
- adminsite-0.1.0/tests/test_access.py +189 -0
- adminsite-0.1.0/tests/test_accessibility.py +298 -0
- adminsite-0.1.0/tests/test_account_menu.py +100 -0
- adminsite-0.1.0/tests/test_action_icons.py +219 -0
- adminsite-0.1.0/tests/test_action_inputs.py +210 -0
- adminsite-0.1.0/tests/test_action_links.py +328 -0
- adminsite-0.1.0/tests/test_actions.py +528 -0
- adminsite-0.1.0/tests/test_activity.py +274 -0
- adminsite-0.1.0/tests/test_api.py +512 -0
- adminsite-0.1.0/tests/test_app.py +170 -0
- adminsite-0.1.0/tests/test_audit.py +716 -0
- adminsite-0.1.0/tests/test_audit_actions.py +436 -0
- adminsite-0.1.0/tests/test_audit_sign_in.py +242 -0
- adminsite-0.1.0/tests/test_audit_together.py +234 -0
- adminsite-0.1.0/tests/test_auth.py +467 -0
- adminsite-0.1.0/tests/test_banner.py +63 -0
- adminsite-0.1.0/tests/test_bulk_delete.py +291 -0
- adminsite-0.1.0/tests/test_choice_search.py +114 -0
- adminsite-0.1.0/tests/test_columns.py +296 -0
- adminsite-0.1.0/tests/test_composite_selection.py +181 -0
- adminsite-0.1.0/tests/test_computed.py +160 -0
- adminsite-0.1.0/tests/test_computed_load.py +145 -0
- adminsite-0.1.0/tests/test_custom_types.py +203 -0
- adminsite-0.1.0/tests/test_dashboard.py +319 -0
- adminsite-0.1.0/tests/test_database.py +241 -0
- adminsite-0.1.0/tests/test_deferred.py +118 -0
- adminsite-0.1.0/tests/test_demo.py +223 -0
- adminsite-0.1.0/tests/test_detail_fields.py +117 -0
- adminsite-0.1.0/tests/test_enum_values.py +144 -0
- adminsite-0.1.0/tests/test_exports.py +61 -0
- adminsite-0.1.0/tests/test_field_access.py +818 -0
- adminsite-0.1.0/tests/test_field_options.py +169 -0
- adminsite-0.1.0/tests/test_fields.py +262 -0
- adminsite-0.1.0/tests/test_files.py +310 -0
- adminsite-0.1.0/tests/test_filter_picker.py +237 -0
- adminsite-0.1.0/tests/test_filters.py +766 -0
- adminsite-0.1.0/tests/test_fixtures.py +71 -0
- adminsite-0.1.0/tests/test_form_layout.py +264 -0
- adminsite-0.1.0/tests/test_form_only.py +438 -0
- adminsite-0.1.0/tests/test_hooks.py +233 -0
- adminsite-0.1.0/tests/test_i18n.py +826 -0
- adminsite-0.1.0/tests/test_import.py +570 -0
- adminsite-0.1.0/tests/test_inline_editable.py +719 -0
- adminsite-0.1.0/tests/test_inlines.py +952 -0
- adminsite-0.1.0/tests/test_inspector.py +274 -0
- adminsite-0.1.0/tests/test_json_field.py +281 -0
- adminsite-0.1.0/tests/test_json_schema.py +813 -0
- adminsite-0.1.0/tests/test_keys.py +704 -0
- adminsite-0.1.0/tests/test_keyset.py +306 -0
- adminsite-0.1.0/tests/test_linked_names.py +146 -0
- adminsite-0.1.0/tests/test_list_address.py +70 -0
- adminsite-0.1.0/tests/test_list_field.py +259 -0
- adminsite-0.1.0/tests/test_list_page.py +496 -0
- adminsite-0.1.0/tests/test_list_refresh.py +100 -0
- adminsite-0.1.0/tests/test_list_totals.py +265 -0
- adminsite-0.1.0/tests/test_many_links.py +351 -0
- adminsite-0.1.0/tests/test_many_sites.py +186 -0
- adminsite-0.1.0/tests/test_markup.py +105 -0
- adminsite-0.1.0/tests/test_messages.py +157 -0
- adminsite-0.1.0/tests/test_model_view.py +350 -0
- adminsite-0.1.0/tests/test_page_size.py +113 -0
- adminsite-0.1.0/tests/test_pages.py +287 -0
- adminsite-0.1.0/tests/test_palette.py +124 -0
- adminsite-0.1.0/tests/test_permissions.py +342 -0
- adminsite-0.1.0/tests/test_picker.py +338 -0
- adminsite-0.1.0/tests/test_picker_access.py +207 -0
- adminsite-0.1.0/tests/test_record_names.py +145 -0
- adminsite-0.1.0/tests/test_record_pages.py +395 -0
- adminsite-0.1.0/tests/test_reference_docs.py +204 -0
- adminsite-0.1.0/tests/test_reference_orders.py +350 -0
- adminsite-0.1.0/tests/test_reference_views.py +241 -0
- adminsite-0.1.0/tests/test_relation_views.py +546 -0
- adminsite-0.1.0/tests/test_repository.py +421 -0
- adminsite-0.1.0/tests/test_row_actions.py +365 -0
- adminsite-0.1.0/tests/test_row_selection.py +57 -0
- adminsite-0.1.0/tests/test_save_context.py +663 -0
- adminsite-0.1.0/tests/test_saved_views.py +319 -0
- adminsite-0.1.0/tests/test_scope_links.py +165 -0
- adminsite-0.1.0/tests/test_scrolling.py +66 -0
- adminsite-0.1.0/tests/test_search_condition.py +103 -0
- adminsite-0.1.0/tests/test_security_review.py +597 -0
- adminsite-0.1.0/tests/test_sidebar.py +84 -0
- adminsite-0.1.0/tests/test_sqlmodel.py +126 -0
- adminsite-0.1.0/tests/test_startup_checks.py +603 -0
- adminsite-0.1.0/tests/test_switches.py +200 -0
- adminsite-0.1.0/tests/test_timezones.py +517 -0
- adminsite-0.1.0/tests/test_tones.py +178 -0
- adminsite-0.1.0/tests/test_type_mistakes.py +132 -0
- adminsite-0.1.0/tests/test_typed_actions.py +1024 -0
- adminsite-0.1.0/tests/test_typed_fields.py +294 -0
- adminsite-0.1.0/tests/test_view_fields.py +319 -0
- adminsite-0.1.0/tests/test_view_layers.py +104 -0
- adminsite-0.1.0/tests/test_web_layer.py +193 -0
- adminsite-0.1.0/tests/test_writing.py +497 -0
- adminsite-0.1.0/uv.lock +2170 -0
|
@@ -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.
|