fastapi-admin-kit 0.2.1__tar.gz → 0.3.1__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.
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/PKG-INFO +3 -1
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/__init__.py +14 -1
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/admin/admin_database.py +11 -7
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/admin/admin_template.py +54 -17
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/admin/core.py +39 -9
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/admin/state.py +4 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/audit/context.py +33 -1
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/audit/diff.py +14 -24
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/audit/events.py +2 -2
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/audit/listener.py +15 -17
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/audit/logger.py +8 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/audit/sqlalchemy_logger.py +6 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/auth/__init__.py +16 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/auth/backend.py +0 -20
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/auth/dependencies.py +28 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/auth/hasher.py +10 -9
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/auth/mixins.py +21 -24
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/auth/models.py +2 -0
- fastapi_admin_kit-0.3.1/fastapi_admin_kit/auth/password.py +80 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/auth/permissions.py +28 -3
- fastapi_admin_kit-0.3.1/fastapi_admin_kit/auth/protocol.py +72 -0
- fastapi_admin_kit-0.3.1/fastapi_admin_kit/auth/types.py +26 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/auth/views.py +1 -1
- fastapi_admin_kit-0.3.1/fastapi_admin_kit/backends/__init__.py +63 -0
- fastapi_admin_kit-0.3.1/fastapi_admin_kit/backends/protocols.py +196 -0
- fastapi_admin_kit-0.3.1/fastapi_admin_kit/backends/sqlalchemy.py +678 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/cli/permissions.py +5 -4
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/config/nav.py +2 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/db.py +51 -23
- fastapi_admin_kit-0.3.1/fastapi_admin_kit/export_import/__init__.py +28 -0
- fastapi_admin_kit-0.3.1/fastapi_admin_kit/export_import/base.py +387 -0
- fastapi_admin_kit-0.3.1/fastapi_admin_kit/export_import/csv.py +181 -0
- fastapi_admin_kit-0.3.1/fastapi_admin_kit/export_import/excel.py +193 -0
- fastapi_admin_kit-0.3.1/fastapi_admin_kit/export_import/registry.py +85 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/filters/__init__.py +12 -0
- fastapi_admin_kit-0.3.1/fastapi_admin_kit/filters/base.py +335 -0
- fastapi_admin_kit-0.3.1/fastapi_admin_kit/filters/registry.py +163 -0
- fastapi_admin_kit-0.3.1/fastapi_admin_kit/form/pipeline.py +440 -0
- fastapi_admin_kit-0.3.1/fastapi_admin_kit/form/types.py +129 -0
- fastapi_admin_kit-0.3.1/fastapi_admin_kit/inline.py +111 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/inspection/__init__.py +37 -58
- fastapi_admin_kit-0.3.1/fastapi_admin_kit/inspection/registry.py +144 -0
- fastapi_admin_kit-0.3.1/fastapi_admin_kit/inspection/types.py +37 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/inspection.py +1 -1
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/modeladmin.py +131 -6
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/pagination/base.py +14 -1
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/pagination/cursor.py +25 -11
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/pagination/dynamic.py +15 -6
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/pagination/offset.py +15 -5
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/router.py +205 -3
- fastapi_admin_kit-0.3.1/fastapi_admin_kit/schemas/__init__.py +37 -0
- fastapi_admin_kit-0.3.1/fastapi_admin_kit/schemas/builtin.py +164 -0
- fastapi_admin_kit-0.3.1/fastapi_admin_kit/schemas/schema.py +119 -0
- fastapi_admin_kit-0.3.1/fastapi_admin_kit/search_utils.py +108 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/static/css/admin.css +22 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/static/js/admin.js +117 -1
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/templates/macros/icons.html +1 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/templates/pages/form.html +23 -1
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/templates/pages/list.html +86 -3
- fastapi_admin_kit-0.3.1/fastapi_admin_kit/templates/partials/inline_stacked.html +179 -0
- fastapi_admin_kit-0.3.1/fastapi_admin_kit/templates/partials/inline_tabular.html +180 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/templates/partials/list_table.html +14 -2
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/templates/partials/sidebar.html +17 -5
- fastapi_admin_kit-0.3.1/fastapi_admin_kit/types.py +46 -0
- fastapi_admin_kit-0.3.1/fastapi_admin_kit/ui/__init__.py +1 -0
- fastapi_admin_kit-0.3.1/fastapi_admin_kit/ui/types.py +35 -0
- fastapi_admin_kit-0.3.1/fastapi_admin_kit/views/__init__.py +80 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/views/audit.py +3 -11
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/views/class_views.py +448 -99
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/views/context.py +11 -241
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/views/factory.py +24 -25
- fastapi_admin_kit-0.3.1/fastapi_admin_kit/views/file_handler.py +96 -0
- fastapi_admin_kit-0.3.1/fastapi_admin_kit/views/form.py +89 -0
- fastapi_admin_kit-0.3.1/fastapi_admin_kit/views/list_context.py +459 -0
- fastapi_admin_kit-0.3.1/fastapi_admin_kit/views/model_saver.py +395 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/views/renderers.py +189 -247
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/views/roles.py +9 -17
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/views/sidebar.py +5 -2
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/views/users.py +9 -17
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/views.py +1 -1
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/widgets/resolver.py +1 -1
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/pyproject.toml +1 -0
- fastapi_admin_kit-0.2.1/fastapi_admin_kit/auth/password.py +0 -37
- fastapi_admin_kit-0.2.1/fastapi_admin_kit/auth/protocol.py +0 -22
- fastapi_admin_kit-0.2.1/fastapi_admin_kit/filters/base.py +0 -170
- fastapi_admin_kit-0.2.1/fastapi_admin_kit/filters/registry.py +0 -62
- fastapi_admin_kit-0.2.1/fastapi_admin_kit/form/pipeline.py +0 -118
- fastapi_admin_kit-0.2.1/fastapi_admin_kit/inspection/registry.py +0 -249
- fastapi_admin_kit-0.2.1/fastapi_admin_kit/search_utils.py +0 -71
- fastapi_admin_kit-0.2.1/fastapi_admin_kit/types.py +0 -160
- fastapi_admin_kit-0.2.1/fastapi_admin_kit/views/__init__.py +0 -64
- fastapi_admin_kit-0.2.1/fastapi_admin_kit/views/form.py +0 -161
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/.gitignore +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/CHANGELOG.md +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/LICENSE +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/README.md +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/actions/__init__.py +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/actions/base.py +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/actions/registry.py +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/admin/__init__.py +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/admin/admin_config.py +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/admin/admin_router.py +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/admin/builtin_models.py +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/admin/decorators.py +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/api/__init__.py +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/api/auth.py +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/api/crud.py +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/api/deps.py +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/api/roles.py +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/api/schema_generator.py +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/api/schemas.py +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/api/search.py +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/audit/__init__.py +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/audit/event_bus.py +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/audit/middleware.py +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/audit/models.py +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/auth/csrf.py +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/auth/identity.py +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/auth/ratelimit.py +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/auth/router.py +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/auth/session.py +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/auth/totp.py +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/cli/__init__.py +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/cli/__main__.py +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/cli/helpers.py +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/cli/migrate.py +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/cli/scaffold.py +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/cli/user.py +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/config/__init__.py +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/config/audit.py +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/config/auth.py +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/config/behavior.py +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/config/database.py +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/config/storage.py +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/config/theme.py +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/config/ui.py +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/dashboard/__init__.py +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/exceptions.py +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/field_types.py +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/flash.py +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/form/__init__.py +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/models/__init__.py +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/models/base.py +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/nav.py +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/pagination/__init__.py +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/plugins/__init__.py +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/py.typed +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/registry/__init__.py +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/registry/core.py +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/registry/validation.py +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/registry.py +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/static/css/presets.css +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/static/css/tokens.css +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/static/css/variables.css +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/static/icons/heroicons.svg +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/static/js/htmx-config.js +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/storage/__init__.py +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/storage/base.py +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/storage/local.py +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/templates/base.html +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/templates/macros/form_fields.html +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/templates/macros/table.html +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/templates/macros/widgets.html +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/templates/pages/2fa/setup.html +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/templates/pages/2fa/verify.html +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/templates/pages/403.html +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/templates/pages/audit_detail.html +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/templates/pages/audit_log.html +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/templates/pages/dashboard.html +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/templates/pages/detail.html +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/templates/pages/login.html +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/templates/pages/profile/password.html +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/templates/pages/profile/profile.html +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/templates/pages/role_form.html +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/templates/pages/roles/form.html +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/templates/pages/roles/list.html +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/templates/pages/roles.html +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/templates/pages/search.html +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/templates/pages/settings/theme.html +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/templates/pages/users/form.html +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/templates/pages/users/list.html +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/templates/partials/_inline_edit_row.html +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/templates/partials/command_palette.html +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/templates/partials/field_wrapper.html +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/templates/partials/flash_messages.html +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/templates/partials/head.html +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/templates/partials/head_minimal.html +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/templates/partials/inline_edit_form.html +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/templates/partials/mobile_backdrop.html +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/templates/partials/pagination.html +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/templates/partials/perm_select_widget.html +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/templates/partials/permission_widget.html +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/templates/partials/scripts.html +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/templates/partials/topbar.html +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/validation.py +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/views/bulk.py +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/views/dashboard.py +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/views/delete.py +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/views/extra.py +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/views/list.py +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/views/profile.py +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/views/protocols.py +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/views/search.py +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/views/settings.py +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/views/totp.py +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/widgets/__init__.py +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/widgets/base.py +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/widgets/inputs.py +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/widgets/registry.py +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/widgets/relation.py +0 -0
- {fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/mkdocs.yml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fastapi-admin-kit
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.1
|
|
4
4
|
Summary: Drop-in admin panel for FastAPI + SQLAlchemy + SQLModel apps
|
|
5
5
|
Project-URL: Homepage, https://github.com/borhanst/fastapi-admin-kit
|
|
6
6
|
Project-URL: Documentation, https://borhanst.github.io/fastapi-admin-kit/
|
|
@@ -44,6 +44,8 @@ Provides-Extra: docs
|
|
|
44
44
|
Requires-Dist: mkdocs-material>=9.5.0; extra == 'docs'
|
|
45
45
|
Requires-Dist: mkdocs>=1.6.0; extra == 'docs'
|
|
46
46
|
Requires-Dist: mkdocstrings[python]>=0.25.0; extra == 'docs'
|
|
47
|
+
Provides-Extra: excel
|
|
48
|
+
Requires-Dist: openpyxl>=3.1.0; extra == 'excel'
|
|
47
49
|
Provides-Extra: full
|
|
48
50
|
Requires-Dist: pyjwt>=2.13.0; extra == 'full'
|
|
49
51
|
Requires-Dist: uvicorn>=0.30.0; extra == 'full'
|
|
@@ -5,6 +5,8 @@ from fastapi_admin_kit.admin.decorators import column
|
|
|
5
5
|
from fastapi_admin_kit.auth.mixins import AuthModelMixin
|
|
6
6
|
from fastapi_admin_kit.config import DatabaseConfig, DatabaseType
|
|
7
7
|
from fastapi_admin_kit.exceptions import ConfigError
|
|
8
|
+
from fastapi_admin_kit.export_import import CSVExport, CSVImport, ExportBase, ImportBase
|
|
9
|
+
from fastapi_admin_kit.inline import InlineModelAdmin, StackedInline, TabularInline
|
|
8
10
|
from fastapi_admin_kit.nav import (
|
|
9
11
|
BuiltNavGroup,
|
|
10
12
|
BuiltNavItem,
|
|
@@ -22,6 +24,7 @@ from fastapi_admin_kit.types import (
|
|
|
22
24
|
FieldsetContext,
|
|
23
25
|
FieldsetSpec,
|
|
24
26
|
FormContext,
|
|
27
|
+
InlineFormsetData,
|
|
25
28
|
PermissionSet,
|
|
26
29
|
RelationMeta,
|
|
27
30
|
SeedRole,
|
|
@@ -64,6 +67,11 @@ __all__ = [
|
|
|
64
67
|
"FieldsetContext",
|
|
65
68
|
"FieldsetSpec",
|
|
66
69
|
"FormContext",
|
|
70
|
+
"InlineFormsetData",
|
|
71
|
+
# Inline admin
|
|
72
|
+
"InlineModelAdmin",
|
|
73
|
+
"StackedInline",
|
|
74
|
+
"TabularInline",
|
|
67
75
|
# View classes
|
|
68
76
|
"BaseView",
|
|
69
77
|
"ListView",
|
|
@@ -74,5 +82,10 @@ __all__ = [
|
|
|
74
82
|
"SearchView",
|
|
75
83
|
# Per-model assets
|
|
76
84
|
"AdminExtra",
|
|
85
|
+
# Export/Import
|
|
86
|
+
"ExportBase",
|
|
87
|
+
"ImportBase",
|
|
88
|
+
"CSVExport",
|
|
89
|
+
"CSVImport",
|
|
77
90
|
]
|
|
78
|
-
__version__ = "0.
|
|
91
|
+
__version__ = "0.3.1"
|
{fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/admin/admin_database.py
RENAMED
|
@@ -6,6 +6,7 @@ import logging
|
|
|
6
6
|
import re
|
|
7
7
|
from typing import Any
|
|
8
8
|
|
|
9
|
+
from fastapi_admin_kit.backends.sqlalchemy import SqlAlchemyDatabaseBackend
|
|
9
10
|
from fastapi_admin_kit.config.database import DatabaseConfig
|
|
10
11
|
|
|
11
12
|
logger = logging.getLogger(__name__)
|
|
@@ -21,7 +22,11 @@ def _validate_identifier(name: str, kind: str = "table") -> str:
|
|
|
21
22
|
|
|
22
23
|
|
|
23
24
|
class AdminDatabase:
|
|
24
|
-
"""Handles database setup, table creation, and role seeding.
|
|
25
|
+
"""Handles database setup, table creation, and role seeding.
|
|
26
|
+
|
|
27
|
+
Delegates engine creation, table creation, and auto-migration to
|
|
28
|
+
:class:`SqlAlchemyDatabaseBackend`.
|
|
29
|
+
"""
|
|
25
30
|
|
|
26
31
|
def __init__(
|
|
27
32
|
self,
|
|
@@ -32,11 +37,12 @@ class AdminDatabase:
|
|
|
32
37
|
self.engine = engine
|
|
33
38
|
self.base = base
|
|
34
39
|
self.database_config = database_config
|
|
40
|
+
self._backend = SqlAlchemyDatabaseBackend(
|
|
41
|
+
admin_database=self, database_config=database_config
|
|
42
|
+
)
|
|
35
43
|
|
|
36
44
|
def _ensure_engine(self) -> Any:
|
|
37
|
-
"""
|
|
38
|
-
Create the async engine from ``database_config`` if no engine is set.
|
|
39
|
-
"""
|
|
45
|
+
"""Create the async engine from ``database_config`` if no engine is set."""
|
|
40
46
|
if self.engine is None and self.database_config is not None:
|
|
41
47
|
self.engine = self.database_config.create_engine()
|
|
42
48
|
return self.engine
|
|
@@ -108,9 +114,7 @@ class AdminDatabase:
|
|
|
108
114
|
conn.execute(sql)
|
|
109
115
|
|
|
110
116
|
def _auto_migrate(self, sync_conn: Any, metadata: Any) -> None:
|
|
111
|
-
"""
|
|
112
|
-
Add missing columns to existing tables (sync, called via run_sync).
|
|
113
|
-
"""
|
|
117
|
+
"""Add missing columns to existing tables (sync, called via run_sync)."""
|
|
114
118
|
from sqlalchemy import inspect as sa_inspect
|
|
115
119
|
from sqlalchemy import text
|
|
116
120
|
|
{fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/admin/admin_template.py
RENAMED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
"""Admin template management and context building."""
|
|
2
2
|
|
|
3
|
+
import logging
|
|
3
4
|
from typing import TYPE_CHECKING, Any
|
|
4
5
|
|
|
5
6
|
if TYPE_CHECKING:
|
|
6
7
|
pass
|
|
7
8
|
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
8
11
|
|
|
9
12
|
class AdminTemplate:
|
|
10
13
|
"""Manages Jinja2 template environment and sidebar context."""
|
|
@@ -19,6 +22,7 @@ class AdminTemplate:
|
|
|
19
22
|
dark_mode_default: bool = False,
|
|
20
23
|
dashboard_permission: str | None = None,
|
|
21
24
|
settings_permission: str | None = None,
|
|
25
|
+
sidebar_bottom_links: list[dict[str, str]] | None = None,
|
|
22
26
|
):
|
|
23
27
|
self.title = title
|
|
24
28
|
self.logo_url = logo_url
|
|
@@ -28,6 +32,7 @@ class AdminTemplate:
|
|
|
28
32
|
self.dark_mode_default = dark_mode_default
|
|
29
33
|
self.dashboard_permission = dashboard_permission
|
|
30
34
|
self.settings_permission = settings_permission
|
|
35
|
+
self.sidebar_bottom_links: list[dict[str, str]] = sidebar_bottom_links or []
|
|
31
36
|
self._nav_groups_built: list = []
|
|
32
37
|
|
|
33
38
|
def _init_jinja(self, app: Any) -> None:
|
|
@@ -46,11 +51,11 @@ class AdminTemplate:
|
|
|
46
51
|
jinja_env.env.filters["slugify"] = slugify
|
|
47
52
|
app.state.admin_jinja_env = jinja_env
|
|
48
53
|
|
|
49
|
-
def sidebar_template_kwargs(self, request: Any) -> dict[str, Any]:
|
|
54
|
+
async def sidebar_template_kwargs(self, request: Any) -> dict[str, Any]:
|
|
50
55
|
"""Thin wrapper — returns sidebar kwargs for TemplateResponse contexts."""
|
|
51
|
-
return self.build_sidebar_context(request)
|
|
56
|
+
return await self.build_sidebar_context(request)
|
|
52
57
|
|
|
53
|
-
def build_sidebar_context(
|
|
58
|
+
async def build_sidebar_context(
|
|
54
59
|
self,
|
|
55
60
|
request: Any,
|
|
56
61
|
user: Any = None,
|
|
@@ -84,28 +89,34 @@ class AdminTemplate:
|
|
|
84
89
|
permissions_map = {}
|
|
85
90
|
|
|
86
91
|
if user and not is_superuser:
|
|
92
|
+
user_id = snapshot.get("id") if snapshot else getattr(user, "id", None)
|
|
87
93
|
role_ids = (
|
|
88
94
|
snapshot.get("role_ids", []) if snapshot else getattr(user, "role_ids", [])
|
|
89
95
|
)
|
|
90
|
-
if role_ids:
|
|
96
|
+
if role_ids or user_id is not None:
|
|
91
97
|
try:
|
|
92
98
|
from sqlalchemy import select
|
|
93
|
-
from sqlalchemy.orm import Session
|
|
94
99
|
|
|
95
|
-
from fastapi_admin_kit.auth.models import
|
|
100
|
+
from fastapi_admin_kit.auth.models import (
|
|
101
|
+
Permission,
|
|
102
|
+
UserPermission,
|
|
103
|
+
admin_role_permissions,
|
|
104
|
+
)
|
|
105
|
+
from fastapi_admin_kit.db import get_db_session
|
|
106
|
+
|
|
107
|
+
session = get_db_session(request)
|
|
96
108
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
result =
|
|
109
|
+
# Load permissions from all roles, merge with OR logic
|
|
110
|
+
if role_ids:
|
|
111
|
+
result = await session.execute(
|
|
100
112
|
select(Permission)
|
|
101
113
|
.join(
|
|
102
114
|
admin_role_permissions,
|
|
103
115
|
Permission.id == admin_role_permissions.c.permission_id,
|
|
104
116
|
)
|
|
105
|
-
.
|
|
117
|
+
.where(admin_role_permissions.c.role_id.in_(role_ids))
|
|
106
118
|
)
|
|
107
|
-
|
|
108
|
-
for perm in rows:
|
|
119
|
+
for perm in result.scalars():
|
|
109
120
|
if perm.table_name in permissions_map:
|
|
110
121
|
existing = permissions_map[perm.table_name]
|
|
111
122
|
permissions_map[perm.table_name] = PermissionSet(
|
|
@@ -121,10 +132,35 @@ class AdminTemplate:
|
|
|
121
132
|
can_edit=perm.can_edit,
|
|
122
133
|
can_delete=perm.can_delete,
|
|
123
134
|
)
|
|
124
|
-
except Exception as exc:
|
|
125
|
-
import logging
|
|
126
135
|
|
|
127
|
-
|
|
136
|
+
# Load direct user permission overrides, merge on top
|
|
137
|
+
if user_id is not None:
|
|
138
|
+
result = await session.execute(
|
|
139
|
+
select(UserPermission, Permission)
|
|
140
|
+
.join(Permission, UserPermission.permission_id == Permission.id)
|
|
141
|
+
.where(UserPermission.user_id == user_id)
|
|
142
|
+
)
|
|
143
|
+
for up, perm in result:
|
|
144
|
+
table = perm.table_name
|
|
145
|
+
if table in permissions_map:
|
|
146
|
+
existing = permissions_map[table]
|
|
147
|
+
permissions_map[table] = PermissionSet(
|
|
148
|
+
can_view=existing.can_view or perm.can_view,
|
|
149
|
+
can_create=existing.can_create or perm.can_create,
|
|
150
|
+
can_edit=existing.can_edit or perm.can_edit,
|
|
151
|
+
can_delete=existing.can_delete or perm.can_delete,
|
|
152
|
+
)
|
|
153
|
+
else:
|
|
154
|
+
permissions_map[table] = PermissionSet(
|
|
155
|
+
can_view=perm.can_view,
|
|
156
|
+
can_create=perm.can_create,
|
|
157
|
+
can_edit=perm.can_edit,
|
|
158
|
+
can_delete=perm.can_delete,
|
|
159
|
+
)
|
|
160
|
+
except Exception as exc:
|
|
161
|
+
logger.warning(
|
|
162
|
+
"Permission query failed in sidebar fallback: %s", exc, exc_info=True
|
|
163
|
+
)
|
|
128
164
|
|
|
129
165
|
def _item_visible(item: Any) -> bool:
|
|
130
166
|
return (
|
|
@@ -187,9 +223,10 @@ class AdminTemplate:
|
|
|
187
223
|
"current_user": user_for_template,
|
|
188
224
|
"dashboard_visible": dashboard_visible,
|
|
189
225
|
"settings_visible": settings_visible,
|
|
226
|
+
"sidebar_bottom_links": self.sidebar_bottom_links,
|
|
190
227
|
}
|
|
191
228
|
|
|
192
|
-
def apply_sidebar_context(self, request: Any, user: Any, context: dict) -> dict:
|
|
229
|
+
async def apply_sidebar_context(self, request: Any, user: Any, context: dict) -> dict:
|
|
193
230
|
"""Inject nav_groups + permissions_map into a template context dict."""
|
|
194
|
-
context.update(self.build_sidebar_context(request, user=user))
|
|
231
|
+
context.update(await self.build_sidebar_context(request, user=user))
|
|
195
232
|
return context
|
|
@@ -35,6 +35,7 @@ if TYPE_CHECKING:
|
|
|
35
35
|
from sqlalchemy.engine import Engine
|
|
36
36
|
|
|
37
37
|
from fastapi_admin_kit.auth.backend import AuthBackend
|
|
38
|
+
from fastapi_admin_kit.backends.sqlalchemy import SqlAlchemyBackend
|
|
38
39
|
from fastapi_admin_kit.nav import NavGroupConfig, SidebarBuilder
|
|
39
40
|
from fastapi_admin_kit.storage.base import StorageBackend
|
|
40
41
|
from fastapi_admin_kit.views import ModelAdmin
|
|
@@ -124,6 +125,7 @@ class Admin:
|
|
|
124
125
|
database: AdminDatabase | None = None,
|
|
125
126
|
router: AdminRouter | None = None,
|
|
126
127
|
template: AdminTemplate | None = None,
|
|
128
|
+
backend: SqlAlchemyBackend | None = None,
|
|
127
129
|
# Legacy kwargs for backward compatibility
|
|
128
130
|
base: type | None = None,
|
|
129
131
|
title: str = "FastAPI Admin Kit",
|
|
@@ -178,6 +180,7 @@ class Admin:
|
|
|
178
180
|
mobile_sidebar: str = "overlay",
|
|
179
181
|
dashboard_permission: str | None = None,
|
|
180
182
|
settings_permission: str | None = None,
|
|
183
|
+
sidebar_bottom_links: list[dict[str, str]] | None = None,
|
|
181
184
|
):
|
|
182
185
|
self.registry = AdminRegistry()
|
|
183
186
|
self._app: FastAPI | None = app
|
|
@@ -259,6 +262,7 @@ class Admin:
|
|
|
259
262
|
require_tags=require_tags,
|
|
260
263
|
dashboard_permission=dashboard_permission,
|
|
261
264
|
settings_permission=settings_permission,
|
|
265
|
+
sidebar_bottom_links=sidebar_bottom_links,
|
|
262
266
|
),
|
|
263
267
|
)
|
|
264
268
|
|
|
@@ -281,6 +285,7 @@ class Admin:
|
|
|
281
285
|
dark_mode_default=config.ui.dark_mode_default,
|
|
282
286
|
dashboard_permission=config.nav.dashboard_permission,
|
|
283
287
|
settings_permission=config.nav.settings_permission,
|
|
288
|
+
sidebar_bottom_links=config.nav.sidebar_bottom_links,
|
|
284
289
|
)
|
|
285
290
|
|
|
286
291
|
self.config = config
|
|
@@ -288,6 +293,16 @@ class Admin:
|
|
|
288
293
|
self.router = router
|
|
289
294
|
self.template = template
|
|
290
295
|
|
|
296
|
+
# Backend: defaults to composed SqlAlchemyBackend
|
|
297
|
+
if backend is None:
|
|
298
|
+
from fastapi_admin_kit.backends.sqlalchemy import SqlAlchemyBackend
|
|
299
|
+
|
|
300
|
+
backend = SqlAlchemyBackend.from_admin_database(database)
|
|
301
|
+
self.backend = backend
|
|
302
|
+
|
|
303
|
+
# Inject backend's introspection adapter into the registry's ModelInspector
|
|
304
|
+
self.registry.inspector._adapter = self.backend.introspection
|
|
305
|
+
|
|
291
306
|
# RBAC
|
|
292
307
|
self.seed_roles = seed_roles if seed_roles is not None else DEFAULT_SEED_ROLES
|
|
293
308
|
self.seed_roles_overwrite = seed_roles_overwrite
|
|
@@ -535,8 +550,6 @@ class Admin:
|
|
|
535
550
|
registered.admin.skip_auto_routes = True
|
|
536
551
|
|
|
537
552
|
# 8.3 Attach audit event listeners (after registry is populated)
|
|
538
|
-
from fastapi_admin_kit.audit.listener import attach_audit_listener
|
|
539
|
-
|
|
540
553
|
engine = self.database.engine
|
|
541
554
|
if engine is not None:
|
|
542
555
|
from sqlalchemy.ext.asyncio import AsyncEngine
|
|
@@ -545,7 +558,12 @@ class Admin:
|
|
|
545
558
|
from fastapi_admin_kit.db import create_session_factory
|
|
546
559
|
|
|
547
560
|
session_factory = create_session_factory(engine)
|
|
548
|
-
|
|
561
|
+
from fastapi_admin_kit.backends.sqlalchemy import (
|
|
562
|
+
SqlAlchemyAuditBackend,
|
|
563
|
+
)
|
|
564
|
+
|
|
565
|
+
audit_backend = SqlAlchemyAuditBackend()
|
|
566
|
+
audit_backend.attach_listeners(session_factory, self.registry)
|
|
549
567
|
|
|
550
568
|
# 9. Validate require_tags
|
|
551
569
|
if self.config.nav.require_tags:
|
|
@@ -703,6 +721,7 @@ class Admin:
|
|
|
703
721
|
admin_instance=self,
|
|
704
722
|
secret_key=self.router.secret_key,
|
|
705
723
|
session_samesite=self.config.auth.session_samesite,
|
|
724
|
+
backend=self.backend,
|
|
706
725
|
)
|
|
707
726
|
|
|
708
727
|
# Store typed state as single attribute
|
|
@@ -721,6 +740,16 @@ class Admin:
|
|
|
721
740
|
app.state.admin_jinja_env = state.jinja_env
|
|
722
741
|
# Unified signing-key source for sessions, CSRF, and JWT (see AdminState).
|
|
723
742
|
app.state.admin_secret_key = state.secret_key
|
|
743
|
+
# Multi-ORM backend: store composed backend and derive individual adapters
|
|
744
|
+
from fastapi_admin_kit.backends.sqlalchemy import (
|
|
745
|
+
SqlAlchemySessionAdapter,
|
|
746
|
+
)
|
|
747
|
+
|
|
748
|
+
app.state.admin_backend = self.backend
|
|
749
|
+
app.state.admin_session_backend_class = SqlAlchemySessionAdapter
|
|
750
|
+
app.state.admin_query_adapter = self.backend.query
|
|
751
|
+
app.state.admin_introspection_adapter = self.backend.introspection
|
|
752
|
+
app.state.admin_audit_backend = self.backend.audit
|
|
724
753
|
|
|
725
754
|
# Wire the password hasher to the User model
|
|
726
755
|
from fastapi_admin_kit.auth.models import User
|
|
@@ -826,6 +855,7 @@ class Admin:
|
|
|
826
855
|
"bars-": "menu",
|
|
827
856
|
"bars-3": "menu",
|
|
828
857
|
"arrow-down-tray": "download",
|
|
858
|
+
"arrow-up-tray": "upload",
|
|
829
859
|
"arrow-path": "refresh",
|
|
830
860
|
"paper-airplane": "send",
|
|
831
861
|
"exclamation-triangle": "warning",
|
|
@@ -1018,21 +1048,21 @@ class Admin:
|
|
|
1018
1048
|
admin_path=self.router.admin_path,
|
|
1019
1049
|
)
|
|
1020
1050
|
|
|
1021
|
-
def build_sidebar_context(
|
|
1051
|
+
async def build_sidebar_context(
|
|
1022
1052
|
self,
|
|
1023
1053
|
request: Any,
|
|
1024
1054
|
user: Any = None,
|
|
1025
1055
|
permissions_map: dict | None = None,
|
|
1026
1056
|
) -> dict:
|
|
1027
1057
|
"""Build per-request sidebar context (RBAC filter + permissions map)."""
|
|
1028
|
-
return self.template.build_sidebar_context(
|
|
1058
|
+
return await self.template.build_sidebar_context(
|
|
1029
1059
|
request, user=user, permissions_map=permissions_map
|
|
1030
1060
|
)
|
|
1031
1061
|
|
|
1032
|
-
def sidebar_template_kwargs(self, request: Any) -> dict[str, Any]:
|
|
1062
|
+
async def sidebar_template_kwargs(self, request: Any) -> dict[str, Any]:
|
|
1033
1063
|
"""Thin wrapper — returns sidebar kwargs for TemplateResponse contexts."""
|
|
1034
|
-
return self.template.sidebar_template_kwargs(request)
|
|
1064
|
+
return await self.template.sidebar_template_kwargs(request)
|
|
1035
1065
|
|
|
1036
|
-
def apply_sidebar_context(self, request: Any, user: Any, context: dict) -> dict:
|
|
1066
|
+
async def apply_sidebar_context(self, request: Any, user: Any, context: dict) -> dict:
|
|
1037
1067
|
"""Inject nav_groups + permissions_map into a template context dict."""
|
|
1038
|
-
return self.template.apply_sidebar_context(request, user, context)
|
|
1068
|
+
return await self.template.apply_sidebar_context(request, user, context)
|
|
@@ -12,6 +12,7 @@ if TYPE_CHECKING:
|
|
|
12
12
|
from fastapi_admin_kit.admin.core import Admin
|
|
13
13
|
from fastapi_admin_kit.auth.backend import AuthBackend
|
|
14
14
|
from fastapi_admin_kit.auth.session import SignedCookieSessionBackend
|
|
15
|
+
from fastapi_admin_kit.backends.sqlalchemy import SqlAlchemyBackend
|
|
15
16
|
from fastapi_admin_kit.registry import AdminRegistry
|
|
16
17
|
from fastapi_admin_kit.storage.base import StorageBackend
|
|
17
18
|
|
|
@@ -36,6 +37,8 @@ class AdminState:
|
|
|
36
37
|
# Unified signing-key source — used by signed-cookie sessions, CSRF, and JWT.
|
|
37
38
|
secret_key: str = ""
|
|
38
39
|
session_samesite: str = "strict"
|
|
40
|
+
# Multi-ORM backend — composes introspection, query, audit, database adapters.
|
|
41
|
+
backend: SqlAlchemyBackend | None = None
|
|
39
42
|
|
|
40
43
|
@classmethod
|
|
41
44
|
def from_request(cls, request: Any) -> AdminState:
|
|
@@ -73,4 +76,5 @@ class AdminState:
|
|
|
73
76
|
jinja_env=getattr(app_state, "admin_jinja_env", None),
|
|
74
77
|
admin_instance=getattr(app_state, "admin", None),
|
|
75
78
|
secret_key=getattr(app_state, "admin_secret_key", ""),
|
|
79
|
+
backend=getattr(app_state, "admin_backend", None),
|
|
76
80
|
)
|
|
@@ -3,7 +3,10 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
from contextvars import ContextVar
|
|
6
|
-
from typing import Any
|
|
6
|
+
from typing import TYPE_CHECKING, Any
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from fastapi_admin_kit.audit.events import AuditEvent
|
|
7
10
|
|
|
8
11
|
# Context variable to store audit context (user, IP, user_agent, etc.)
|
|
9
12
|
_current_audit_context: ContextVar[dict] = ContextVar("_current_audit_context", default={})
|
|
@@ -60,3 +63,32 @@ class AuditContext:
|
|
|
60
63
|
def clear_context(self) -> None:
|
|
61
64
|
"""Clear the audit context."""
|
|
62
65
|
clear_audit_context()
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
async def log_audit_event(request: Any, event: AuditEvent) -> None:
|
|
69
|
+
"""Persist an AuditEvent directly to the database.
|
|
70
|
+
|
|
71
|
+
Used for EXPORT/IMPORT events that don't go through the SQLAlchemy
|
|
72
|
+
before_flush listener.
|
|
73
|
+
"""
|
|
74
|
+
from fastapi_admin_kit.audit.models import AuditLog
|
|
75
|
+
from fastapi_admin_kit.db import get_db_session
|
|
76
|
+
|
|
77
|
+
session = get_db_session(request)
|
|
78
|
+
ctx = get_audit_context()
|
|
79
|
+
|
|
80
|
+
entry = AuditLog(
|
|
81
|
+
action=event.event_type,
|
|
82
|
+
model_name=event.model_name,
|
|
83
|
+
table_name=event.table_name,
|
|
84
|
+
object_id=event.object_id,
|
|
85
|
+
object_repr=event.object_repr,
|
|
86
|
+
changes=event.changes,
|
|
87
|
+
full_snapshot=event.full_snapshot,
|
|
88
|
+
user_id=event.user_id or ctx.get("user_id"),
|
|
89
|
+
user_email=event.user_email or ctx.get("user_email"),
|
|
90
|
+
ip_address=event.ip_address or ctx.get("ip_address"),
|
|
91
|
+
user_agent=event.user_agent or ctx.get("user_agent"),
|
|
92
|
+
)
|
|
93
|
+
session.add(entry)
|
|
94
|
+
await session.flush()
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
"""Audit diff utilities — snapshot and diff computation for SQLAlchemy models.
|
|
1
|
+
"""Audit diff utilities — snapshot and diff computation for SQLAlchemy models.
|
|
2
|
+
|
|
3
|
+
``serialize_value()`` is ORM-agnostic and stays here.
|
|
4
|
+
``snapshot()`` and ``compute_diff()`` delegate to the active AuditBackend.
|
|
5
|
+
"""
|
|
2
6
|
|
|
3
7
|
from __future__ import annotations
|
|
4
8
|
|
|
@@ -8,8 +12,6 @@ import enum
|
|
|
8
12
|
from typing import Any
|
|
9
13
|
from uuid import UUID
|
|
10
14
|
|
|
11
|
-
from sqlalchemy.inspection import inspect as sqlalchemy_inspect
|
|
12
|
-
|
|
13
15
|
|
|
14
16
|
def serialize_value(val: Any) -> Any:
|
|
15
17
|
"""Convert a value to a JSON-serializable form.
|
|
@@ -33,12 +35,9 @@ def serialize_value(val: Any) -> Any:
|
|
|
33
35
|
if isinstance(val, UUID):
|
|
34
36
|
return str(val)
|
|
35
37
|
if isinstance(val, bytes):
|
|
36
|
-
# For simplicity, we'll return the hex representation.
|
|
37
|
-
# Alternatively, we could use base64, but hex is simpler.
|
|
38
38
|
return val.hex()
|
|
39
39
|
if isinstance(val, enum.Enum):
|
|
40
40
|
return val.value
|
|
41
|
-
# For other types, we assume they are JSON serializable (or let JSON encoder handle it)
|
|
42
41
|
return val
|
|
43
42
|
|
|
44
43
|
|
|
@@ -46,18 +45,12 @@ def snapshot(obj: Any) -> dict[str, Any]:
|
|
|
46
45
|
"""Snapshot all mapped columns of a SQLAlchemy model instance.
|
|
47
46
|
|
|
48
47
|
Returns a dict mapping column name to serialized value.
|
|
48
|
+
Delegates to the active AuditBackend.
|
|
49
49
|
"""
|
|
50
|
-
|
|
51
|
-
raise ValueError("Object is not a SQLAlchemy model instance")
|
|
50
|
+
from fastapi_admin_kit.backends.sqlalchemy import SqlAlchemyAuditBackend
|
|
52
51
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
for column in mapper.columns:
|
|
56
|
-
# Skip foreign key columns that are represented by relationships?
|
|
57
|
-
# We'll include all columns for simplicity.
|
|
58
|
-
value = getattr(obj, column.key)
|
|
59
|
-
data[column.key] = serialize_value(value)
|
|
60
|
-
return data
|
|
52
|
+
backend = SqlAlchemyAuditBackend()
|
|
53
|
+
return backend.snapshot(obj)
|
|
61
54
|
|
|
62
55
|
|
|
63
56
|
def compute_diff(before: dict[str, Any], after: dict[str, Any]) -> dict[str, Any]:
|
|
@@ -66,12 +59,9 @@ def compute_diff(before: dict[str, Any], after: dict[str, Any]) -> dict[str, Any
|
|
|
66
59
|
Returns a dict of changed fields, each containing:
|
|
67
60
|
{"old": <value>, "new": <value>}
|
|
68
61
|
Only fields that have changed are included.
|
|
62
|
+
Delegates to the active AuditBackend.
|
|
69
63
|
"""
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
new_val = after.get(key)
|
|
75
|
-
if old_val != new_val:
|
|
76
|
-
diff[key] = {"old": old_val, "new": new_val}
|
|
77
|
-
return diff
|
|
64
|
+
from fastapi_admin_kit.backends.sqlalchemy import SqlAlchemyAuditBackend
|
|
65
|
+
|
|
66
|
+
backend = SqlAlchemyAuditBackend()
|
|
67
|
+
return backend.compute_diff(before, after)
|
|
@@ -9,13 +9,13 @@ from typing import Any
|
|
|
9
9
|
|
|
10
10
|
@dataclass
|
|
11
11
|
class AuditEvent:
|
|
12
|
-
"""Represents a single audit event (CREATE, UPDATE, or
|
|
12
|
+
"""Represents a single audit event (CREATE, UPDATE, DELETE, EXPORT, or IMPORT).
|
|
13
13
|
|
|
14
14
|
This is a pure data structure with no side effects, making it
|
|
15
15
|
easy to test and serialize.
|
|
16
16
|
"""
|
|
17
17
|
|
|
18
|
-
event_type: str # "CREATE" | "UPDATE" | "DELETE"
|
|
18
|
+
event_type: str # "CREATE" | "UPDATE" | "DELETE" | "EXPORT" | "IMPORT"
|
|
19
19
|
model_name: str
|
|
20
20
|
table_name: str
|
|
21
21
|
object_id: str
|
|
@@ -55,31 +55,29 @@ def _snapshot_from_committed(obj: Any) -> dict[str, Any]:
|
|
|
55
55
|
|
|
56
56
|
|
|
57
57
|
def _snapshot_current(obj: Any) -> dict[str, Any]:
|
|
58
|
-
"""Snapshot all mapped columns of a SQLAlchemy model instance.
|
|
59
|
-
if not hasattr(obj, "__table__"):
|
|
60
|
-
return {}
|
|
61
|
-
from sqlalchemy.inspection import inspect as sa_inspect
|
|
58
|
+
"""Snapshot all mapped columns of a SQLAlchemy model instance.
|
|
62
59
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
60
|
+
Delegates to the SqlAlchemyAuditBackend. Returns {} for non-model objects.
|
|
61
|
+
"""
|
|
62
|
+
from fastapi_admin_kit.backends.sqlalchemy import SqlAlchemyAuditBackend
|
|
63
|
+
|
|
64
|
+
backend = SqlAlchemyAuditBackend()
|
|
65
|
+
try:
|
|
66
|
+
return backend.snapshot(obj)
|
|
67
|
+
except ValueError:
|
|
68
|
+
return {}
|
|
68
69
|
|
|
69
70
|
|
|
70
71
|
def _compute_diff(before: dict[str, Any], after: dict[str, Any]) -> dict[str, Any]:
|
|
71
72
|
"""Compute changed fields between two snapshots.
|
|
72
73
|
|
|
73
74
|
Returns ``{"field": {"old": ..., "new": ...}}`` for each difference.
|
|
75
|
+
Delegates to the SqlAlchemyAuditBackend.
|
|
74
76
|
"""
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
new_val = after.get(key)
|
|
80
|
-
if old_val != new_val:
|
|
81
|
-
diff[key] = {"old": old_val, "new": new_val}
|
|
82
|
-
return diff
|
|
77
|
+
from fastapi_admin_kit.backends.sqlalchemy import SqlAlchemyAuditBackend
|
|
78
|
+
|
|
79
|
+
backend = SqlAlchemyAuditBackend()
|
|
80
|
+
return backend.compute_diff(before, after)
|
|
83
81
|
|
|
84
82
|
|
|
85
83
|
def _build_audit_row(
|
|
@@ -26,3 +26,11 @@ class AuditLogger(ABC):
|
|
|
26
26
|
@abstractmethod
|
|
27
27
|
def log_delete(self, event: AuditEvent) -> None:
|
|
28
28
|
"""Persist a DELETE audit event."""
|
|
29
|
+
|
|
30
|
+
@abstractmethod
|
|
31
|
+
def log_export(self, event: AuditEvent) -> None:
|
|
32
|
+
"""Persist an EXPORT audit event."""
|
|
33
|
+
|
|
34
|
+
@abstractmethod
|
|
35
|
+
def log_import(self, event: AuditEvent) -> None:
|
|
36
|
+
"""Persist an IMPORT audit event."""
|
{fastapi_admin_kit-0.2.1 → fastapi_admin_kit-0.3.1}/fastapi_admin_kit/audit/sqlalchemy_logger.py
RENAMED
|
@@ -32,6 +32,12 @@ class SqlAlchemyAuditLogger(AuditLogger):
|
|
|
32
32
|
def log_delete(self, event: AuditEvent) -> None:
|
|
33
33
|
self._buffer(event)
|
|
34
34
|
|
|
35
|
+
def log_export(self, event: AuditEvent) -> None:
|
|
36
|
+
self._buffer(event)
|
|
37
|
+
|
|
38
|
+
def log_import(self, event: AuditEvent) -> None:
|
|
39
|
+
self._buffer(event)
|
|
40
|
+
|
|
35
41
|
def _buffer(self, event: AuditEvent) -> None:
|
|
36
42
|
entry = AuditLog(
|
|
37
43
|
user_id=event.user_id,
|
|
@@ -17,14 +17,28 @@ from fastapi_admin_kit.auth.models import (
|
|
|
17
17
|
User,
|
|
18
18
|
UserPermission,
|
|
19
19
|
)
|
|
20
|
+
from fastapi_admin_kit.auth.password import (
|
|
21
|
+
PasswordManager,
|
|
22
|
+
password_manager,
|
|
23
|
+
validate_password_strength,
|
|
24
|
+
)
|
|
25
|
+
from fastapi_admin_kit.auth.protocol import (
|
|
26
|
+
AdminPermissionProtocol,
|
|
27
|
+
AdminRoleProtocol,
|
|
28
|
+
AdminUserProtocol,
|
|
29
|
+
)
|
|
20
30
|
from fastapi_admin_kit.auth.session import (
|
|
21
31
|
SessionBackend,
|
|
22
32
|
SignedCookieSessionBackend,
|
|
23
33
|
)
|
|
24
34
|
|
|
25
35
|
__all__ = [
|
|
36
|
+
"AdminPermissionProtocol",
|
|
37
|
+
"AdminRoleProtocol",
|
|
38
|
+
"AdminUserProtocol",
|
|
26
39
|
"AuthModelMixin",
|
|
27
40
|
"BcryptHasher",
|
|
41
|
+
"PasswordManager",
|
|
28
42
|
"Permission",
|
|
29
43
|
"PasswordHasher",
|
|
30
44
|
"Role",
|
|
@@ -36,6 +50,8 @@ __all__ = [
|
|
|
36
50
|
"SignedCookieSessionBackend",
|
|
37
51
|
"auth_redirect_handler",
|
|
38
52
|
"forbidden_handler",
|
|
53
|
+
"password_manager",
|
|
39
54
|
"require_csrf_token",
|
|
40
55
|
"set_csrf_cookie",
|
|
56
|
+
"validate_password_strength",
|
|
41
57
|
]
|