fastapi-admin-kit 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (163) hide show
  1. fastapi_admin_kit/__init__.py +73 -0
  2. fastapi_admin_kit/actions/__init__.py +63 -0
  3. fastapi_admin_kit/actions/base.py +68 -0
  4. fastapi_admin_kit/actions/registry.py +43 -0
  5. fastapi_admin_kit/admin/__init__.py +17 -0
  6. fastapi_admin_kit/admin/admin_config.py +95 -0
  7. fastapi_admin_kit/admin/admin_database.py +138 -0
  8. fastapi_admin_kit/admin/admin_router.py +74 -0
  9. fastapi_admin_kit/admin/admin_template.py +203 -0
  10. fastapi_admin_kit/admin/builtin_models.py +284 -0
  11. fastapi_admin_kit/admin/core.py +1036 -0
  12. fastapi_admin_kit/admin/decorators.py +70 -0
  13. fastapi_admin_kit/admin/state.py +76 -0
  14. fastapi_admin_kit/admin.py +728 -0
  15. fastapi_admin_kit/api/__init__.py +44 -0
  16. fastapi_admin_kit/api/auth.py +342 -0
  17. fastapi_admin_kit/api/crud.py +128 -0
  18. fastapi_admin_kit/api/deps.py +79 -0
  19. fastapi_admin_kit/api/roles.py +128 -0
  20. fastapi_admin_kit/api/schema_generator.py +171 -0
  21. fastapi_admin_kit/api/schemas.py +81 -0
  22. fastapi_admin_kit/api/search.py +132 -0
  23. fastapi_admin_kit/audit/__init__.py +36 -0
  24. fastapi_admin_kit/audit/context.py +62 -0
  25. fastapi_admin_kit/audit/diff.py +77 -0
  26. fastapi_admin_kit/audit/event_bus.py +96 -0
  27. fastapi_admin_kit/audit/events.py +48 -0
  28. fastapi_admin_kit/audit/listener.py +159 -0
  29. fastapi_admin_kit/audit/logger.py +28 -0
  30. fastapi_admin_kit/audit/middleware.py +39 -0
  31. fastapi_admin_kit/audit/models.py +53 -0
  32. fastapi_admin_kit/audit/sqlalchemy_logger.py +58 -0
  33. fastapi_admin_kit/auth/__init__.py +34 -0
  34. fastapi_admin_kit/auth/backend.py +95 -0
  35. fastapi_admin_kit/auth/csrf.py +240 -0
  36. fastapi_admin_kit/auth/dependencies.py +150 -0
  37. fastapi_admin_kit/auth/identity.py +181 -0
  38. fastapi_admin_kit/auth/models.py +246 -0
  39. fastapi_admin_kit/auth/password.py +35 -0
  40. fastapi_admin_kit/auth/permissions.py +205 -0
  41. fastapi_admin_kit/auth/protocol.py +22 -0
  42. fastapi_admin_kit/auth/ratelimit.py +88 -0
  43. fastapi_admin_kit/auth/router.py +10 -0
  44. fastapi_admin_kit/auth/session.py +79 -0
  45. fastapi_admin_kit/auth/totp.py +83 -0
  46. fastapi_admin_kit/auth/views.py +165 -0
  47. fastapi_admin_kit/cli.py +229 -0
  48. fastapi_admin_kit/config/__init__.py +19 -0
  49. fastapi_admin_kit/config/audit.py +18 -0
  50. fastapi_admin_kit/config/auth.py +54 -0
  51. fastapi_admin_kit/config/behavior.py +27 -0
  52. fastapi_admin_kit/config/nav.py +32 -0
  53. fastapi_admin_kit/config/storage.py +22 -0
  54. fastapi_admin_kit/config/theme.py +215 -0
  55. fastapi_admin_kit/config/ui.py +147 -0
  56. fastapi_admin_kit/dashboard/__init__.py +64 -0
  57. fastapi_admin_kit/db.py +133 -0
  58. fastapi_admin_kit/exceptions.py +5 -0
  59. fastapi_admin_kit/field_types.py +81 -0
  60. fastapi_admin_kit/filters/__init__.py +21 -0
  61. fastapi_admin_kit/filters/base.py +170 -0
  62. fastapi_admin_kit/filters/registry.py +68 -0
  63. fastapi_admin_kit/flash.py +45 -0
  64. fastapi_admin_kit/form/__init__.py +1 -0
  65. fastapi_admin_kit/form/pipeline.py +106 -0
  66. fastapi_admin_kit/inspection/__init__.py +117 -0
  67. fastapi_admin_kit/inspection/registry.py +253 -0
  68. fastapi_admin_kit/inspection.py +115 -0
  69. fastapi_admin_kit/modeladmin.py +375 -0
  70. fastapi_admin_kit/models/__init__.py +7 -0
  71. fastapi_admin_kit/models/base.py +7 -0
  72. fastapi_admin_kit/nav.py +208 -0
  73. fastapi_admin_kit/pagination/__init__.py +14 -0
  74. fastapi_admin_kit/pagination/base.py +40 -0
  75. fastapi_admin_kit/pagination/cursor.py +97 -0
  76. fastapi_admin_kit/pagination/dynamic.py +48 -0
  77. fastapi_admin_kit/pagination/offset.py +42 -0
  78. fastapi_admin_kit/plugins/__init__.py +1 -0
  79. fastapi_admin_kit/py.typed +0 -0
  80. fastapi_admin_kit/registry/__init__.py +5 -0
  81. fastapi_admin_kit/registry/core.py +287 -0
  82. fastapi_admin_kit/registry/validation.py +107 -0
  83. fastapi_admin_kit/registry.py +15 -0
  84. fastapi_admin_kit/router.py +335 -0
  85. fastapi_admin_kit/static/css/admin.css +4736 -0
  86. fastapi_admin_kit/static/css/presets.css +317 -0
  87. fastapi_admin_kit/static/css/tokens.css +217 -0
  88. fastapi_admin_kit/static/css/variables.css +74 -0
  89. fastapi_admin_kit/static/icons/heroicons.svg +160 -0
  90. fastapi_admin_kit/static/js/admin.js +692 -0
  91. fastapi_admin_kit/static/js/htmx-config.js +42 -0
  92. fastapi_admin_kit/storage/__init__.py +6 -0
  93. fastapi_admin_kit/storage/base.py +48 -0
  94. fastapi_admin_kit/storage/local.py +73 -0
  95. fastapi_admin_kit/templates/base.html +142 -0
  96. fastapi_admin_kit/templates/macros/form_fields.html +660 -0
  97. fastapi_admin_kit/templates/macros/icons.html +50 -0
  98. fastapi_admin_kit/templates/macros/table.html +108 -0
  99. fastapi_admin_kit/templates/macros/widgets.html +159 -0
  100. fastapi_admin_kit/templates/pages/2fa/setup.html +122 -0
  101. fastapi_admin_kit/templates/pages/2fa/verify.html +55 -0
  102. fastapi_admin_kit/templates/pages/audit_detail.html +122 -0
  103. fastapi_admin_kit/templates/pages/audit_log.html +102 -0
  104. fastapi_admin_kit/templates/pages/dashboard.html +295 -0
  105. fastapi_admin_kit/templates/pages/detail.html +183 -0
  106. fastapi_admin_kit/templates/pages/form.html +119 -0
  107. fastapi_admin_kit/templates/pages/list.html +277 -0
  108. fastapi_admin_kit/templates/pages/login.html +85 -0
  109. fastapi_admin_kit/templates/pages/profile/password.html +78 -0
  110. fastapi_admin_kit/templates/pages/profile/profile.html +73 -0
  111. fastapi_admin_kit/templates/pages/role_form.html +75 -0
  112. fastapi_admin_kit/templates/pages/roles/form.html +117 -0
  113. fastapi_admin_kit/templates/pages/roles/list.html +69 -0
  114. fastapi_admin_kit/templates/pages/roles.html +77 -0
  115. fastapi_admin_kit/templates/pages/settings/theme.html +255 -0
  116. fastapi_admin_kit/templates/pages/users/form.html +229 -0
  117. fastapi_admin_kit/templates/pages/users/list.html +83 -0
  118. fastapi_admin_kit/templates/partials/command_palette.html +52 -0
  119. fastapi_admin_kit/templates/partials/field_wrapper.html +2 -0
  120. fastapi_admin_kit/templates/partials/flash_messages.html +39 -0
  121. fastapi_admin_kit/templates/partials/head.html +21 -0
  122. fastapi_admin_kit/templates/partials/head_minimal.html +18 -0
  123. fastapi_admin_kit/templates/partials/list_table.html +178 -0
  124. fastapi_admin_kit/templates/partials/mobile_backdrop.html +2 -0
  125. fastapi_admin_kit/templates/partials/pagination.html +82 -0
  126. fastapi_admin_kit/templates/partials/permission_widget.html +86 -0
  127. fastapi_admin_kit/templates/partials/scripts.html +13 -0
  128. fastapi_admin_kit/templates/partials/sidebar.html +94 -0
  129. fastapi_admin_kit/templates/partials/topbar.html +95 -0
  130. fastapi_admin_kit/types.py +145 -0
  131. fastapi_admin_kit/validation.py +43 -0
  132. fastapi_admin_kit/views/__init__.py +78 -0
  133. fastapi_admin_kit/views/audit.py +134 -0
  134. fastapi_admin_kit/views/bulk.py +28 -0
  135. fastapi_admin_kit/views/class_views.py +1040 -0
  136. fastapi_admin_kit/views/context.py +588 -0
  137. fastapi_admin_kit/views/dashboard.py +162 -0
  138. fastapi_admin_kit/views/delete.py +31 -0
  139. fastapi_admin_kit/views/extra.py +65 -0
  140. fastapi_admin_kit/views/factory.py +667 -0
  141. fastapi_admin_kit/views/form.py +159 -0
  142. fastapi_admin_kit/views/list.py +28 -0
  143. fastapi_admin_kit/views/profile.py +219 -0
  144. fastapi_admin_kit/views/protocols.py +54 -0
  145. fastapi_admin_kit/views/renderers.py +634 -0
  146. fastapi_admin_kit/views/roles.py +230 -0
  147. fastapi_admin_kit/views/search.py +31 -0
  148. fastapi_admin_kit/views/settings.py +31 -0
  149. fastapi_admin_kit/views/sidebar.py +101 -0
  150. fastapi_admin_kit/views/totp.py +249 -0
  151. fastapi_admin_kit/views/users.py +347 -0
  152. fastapi_admin_kit/views.py +117 -0
  153. fastapi_admin_kit/widgets/__init__.py +44 -0
  154. fastapi_admin_kit/widgets/base.py +44 -0
  155. fastapi_admin_kit/widgets/inputs.py +363 -0
  156. fastapi_admin_kit/widgets/registry.py +110 -0
  157. fastapi_admin_kit/widgets/relation.py +70 -0
  158. fastapi_admin_kit/widgets/resolver.py +102 -0
  159. fastapi_admin_kit-0.1.0.dist-info/METADATA +210 -0
  160. fastapi_admin_kit-0.1.0.dist-info/RECORD +163 -0
  161. fastapi_admin_kit-0.1.0.dist-info/WHEEL +4 -0
  162. fastapi_admin_kit-0.1.0.dist-info/entry_points.txt +3 -0
  163. fastapi_admin_kit-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,73 @@
1
+ """FastAPI Admin Kit — Drop-in admin panel for FastAPI + SQLAlchemy apps."""
2
+
3
+ from fastapi_admin_kit.admin import Admin
4
+ from fastapi_admin_kit.admin.decorators import column
5
+ from fastapi_admin_kit.exceptions import ConfigError
6
+ from fastapi_admin_kit.nav import (
7
+ BuiltNavGroup,
8
+ BuiltNavItem,
9
+ DefaultSidebarBuilder,
10
+ NavGroupConfig,
11
+ NavItemConfig,
12
+ SidebarBuilder,
13
+ )
14
+ from fastapi_admin_kit.registry import AdminRegistry, RegisteredModel
15
+ from fastapi_admin_kit.types import (
16
+ ColumnMeta,
17
+ ExtraField,
18
+ FieldMeta,
19
+ FieldRenderContext,
20
+ FieldsetContext,
21
+ FieldsetSpec,
22
+ FormContext,
23
+ PermissionSet,
24
+ RelationMeta,
25
+ SeedRole,
26
+ )
27
+ from fastapi_admin_kit.views import (
28
+ AdminExtra,
29
+ BaseView,
30
+ BulkView,
31
+ CreateView,
32
+ DeleteView,
33
+ EditView,
34
+ ListView,
35
+ ModelAdmin,
36
+ SearchView,
37
+ )
38
+
39
+ __all__ = [
40
+ "Admin",
41
+ "AdminRegistry",
42
+ "ConfigError",
43
+ "RegisteredModel",
44
+ "ModelAdmin",
45
+ "column",
46
+ "BuiltNavGroup",
47
+ "BuiltNavItem",
48
+ "DefaultSidebarBuilder",
49
+ "NavGroupConfig",
50
+ "NavItemConfig",
51
+ "SidebarBuilder",
52
+ "ColumnMeta",
53
+ "RelationMeta",
54
+ "FieldMeta",
55
+ "PermissionSet",
56
+ "SeedRole",
57
+ "ExtraField",
58
+ "FieldRenderContext",
59
+ "FieldsetContext",
60
+ "FieldsetSpec",
61
+ "FormContext",
62
+ # View classes
63
+ "BaseView",
64
+ "ListView",
65
+ "CreateView",
66
+ "EditView",
67
+ "DeleteView",
68
+ "BulkView",
69
+ "SearchView",
70
+ # Per-model assets
71
+ "AdminExtra",
72
+ ]
73
+ __version__ = "0.1.0"
@@ -0,0 +1,63 @@
1
+ """Bulk actions for list views."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Callable
6
+ from typing import Any
7
+
8
+ from fastapi_admin_kit.actions.base import Action, ModelAction
9
+ from fastapi_admin_kit.actions.registry import ActionRegistry
10
+
11
+ __all__ = ["Action", "ModelAction", "ActionRegistry", "action"]
12
+
13
+
14
+ def action(
15
+ name: str = "",
16
+ label: str = "",
17
+ confirmation_message: str = "",
18
+ icon: str | None = None,
19
+ variant: str = "default",
20
+ permissions: list[str] | None = None,
21
+ location: str = "list",
22
+ description: str = "",
23
+ ) -> Callable:
24
+ """Decorator to register an action on a ModelAdmin class.
25
+
26
+ Usage::
27
+
28
+ @admin.register(Product)
29
+ class ProductAdmin(ModelAdmin):
30
+ @action(
31
+ description="Export selected products",
32
+ icon="arrow-down-tray",
33
+ variant="primary",
34
+ )
35
+ async def export_products(self, objects, request):
36
+ ...
37
+ """
38
+
39
+ def decorator(fn: Callable) -> Callable:
40
+ action_name = name or fn.__name__
41
+ action_label = label or action_name.replace("_", " ").title()
42
+
43
+ class DecoratedAction(Action):
44
+ def __init__(self) -> None:
45
+ super().__init__(
46
+ name=action_name,
47
+ label=action_label,
48
+ confirmation_message=confirmation_message,
49
+ icon=icon,
50
+ variant=variant,
51
+ permissions=permissions or [],
52
+ location=location,
53
+ description=description,
54
+ )
55
+
56
+ async def execute(self, objects: list[Any], request: Any) -> None:
57
+ await fn(self, objects, request)
58
+
59
+ fn._admin_action = DecoratedAction
60
+ fn._admin_action_name = action_name
61
+ return fn
62
+
63
+ return decorator
@@ -0,0 +1,68 @@
1
+ """Action ABCs."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from abc import ABC, abstractmethod
6
+ from typing import Any
7
+
8
+ from fastapi import Request
9
+
10
+
11
+ class Action(ABC):
12
+ """Abstract base class for admin actions.
13
+
14
+ Supports list-level, row-level, detail-level, and submit-line actions
15
+ with Unfold-style icon, variant, and permission configuration.
16
+ """
17
+
18
+ def __init__(
19
+ self,
20
+ name: str,
21
+ label: str = "",
22
+ confirmation_message: str = "",
23
+ icon: str | None = None,
24
+ variant: str = "default",
25
+ permissions: list[str] | None = None,
26
+ location: str = "list",
27
+ description: str = "",
28
+ ) -> None:
29
+ self.name = name
30
+ self.label = label or name.replace("_", " ").title()
31
+ self.confirmation_message = confirmation_message
32
+ self.icon = icon
33
+ self.variant = variant # default, primary, danger, success, warning
34
+ self.permissions = permissions or []
35
+ self.location = location # list, row, detail, submit_line
36
+ self.description = description
37
+
38
+ @abstractmethod
39
+ async def execute(
40
+ self, objects: list[Any], request: Request | None
41
+ ) -> None:
42
+ """Run the action against the selected objects."""
43
+ ...
44
+
45
+ def get_confirmation_message(self) -> str:
46
+ return self.confirmation_message or f"Run {self.label}?"
47
+
48
+ def has_permission(self, user: Any) -> bool:
49
+ """Check if the user has permission to run this action."""
50
+ if not self.permissions:
51
+ return True
52
+ if getattr(user, "is_superuser", False):
53
+ return True
54
+ user_perms = getattr(user, "permissions", []) or []
55
+ return any(p in user_perms for p in self.permissions)
56
+
57
+
58
+ class ModelAction(Action):
59
+ """Action that operates on a single model instance (row/detail actions)."""
60
+
61
+ async def execute_single(
62
+ self, obj: Any, request: Request | None
63
+ ) -> None:
64
+ """Run the action on a single object.
65
+
66
+ Override this instead of execute for single-object actions.
67
+ """
68
+ await self.execute([obj], request)
@@ -0,0 +1,43 @@
1
+ """Action registry."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ from fastapi import Request
8
+
9
+ from fastapi_admin_kit.actions.base import Action
10
+
11
+
12
+ class DeleteSelectedAction(Action):
13
+ """Built-in bulk delete action."""
14
+
15
+ def __init__(self) -> None:
16
+ super().__init__(
17
+ name="delete_selected",
18
+ label="Delete selected",
19
+ confirmation_message="Are you sure you want to delete the selected items?",
20
+ )
21
+
22
+ async def execute(
23
+ self, objects: list[Any], request: Request | None
24
+ ) -> None:
25
+ # Actual deletion is handled by the existing bulk handler in views/factory.py
26
+ pass
27
+
28
+
29
+ class ActionRegistry:
30
+ """Registry for custom actions per model."""
31
+
32
+ def __init__(self) -> None:
33
+ self._actions: dict[str, list[Action]] = {}
34
+
35
+ def register(self, model_name: str, action: Action) -> None:
36
+ self._actions.setdefault(model_name, []).append(action)
37
+
38
+ def get_actions(self, model_name: str) -> list[Action]:
39
+ return self._actions.get(model_name, []).copy()
40
+
41
+ def auto_generate(self, model_name: str) -> list[Action]:
42
+ """Auto-generate default bulk actions for a model."""
43
+ return [DeleteSelectedAction()]
@@ -0,0 +1,17 @@
1
+ """Admin component classes for FastAPI Admin Kit."""
2
+
3
+ from fastapi_admin_kit.admin.admin_config import AdminConfig
4
+ from fastapi_admin_kit.admin.admin_database import AdminDatabase
5
+ from fastapi_admin_kit.admin.admin_router import AdminRouter
6
+ from fastapi_admin_kit.admin.admin_template import AdminTemplate
7
+ from fastapi_admin_kit.admin.core import Admin
8
+ from fastapi_admin_kit.admin.state import AdminState
9
+
10
+ __all__ = [
11
+ "Admin",
12
+ "AdminConfig",
13
+ "AdminDatabase",
14
+ "AdminRouter",
15
+ "AdminState",
16
+ "AdminTemplate",
17
+ ]
@@ -0,0 +1,95 @@
1
+ """Admin configuration orchestration."""
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from fastapi_admin_kit.config import (
6
+ AuditConfig,
7
+ AuthConfig,
8
+ BehaviorConfig,
9
+ NavConfig,
10
+ StorageConfig,
11
+ UIConfig,
12
+ )
13
+
14
+ if TYPE_CHECKING:
15
+ pass
16
+
17
+
18
+ class AdminConfig:
19
+ """Orchestrates all configuration classes with validation."""
20
+
21
+ def __init__(
22
+ self,
23
+ ui: UIConfig | None = None,
24
+ auth: AuthConfig | None = None,
25
+ audit: AuditConfig | None = None,
26
+ behavior: BehaviorConfig | None = None,
27
+ storage: StorageConfig | None = None,
28
+ nav: NavConfig | None = None,
29
+ ):
30
+ self.ui = ui or UIConfig()
31
+ self.auth = auth or AuthConfig()
32
+ self.audit = audit or AuditConfig()
33
+ self.behavior = behavior or BehaviorConfig()
34
+ self.storage = storage or StorageConfig()
35
+ self.nav = nav or NavConfig()
36
+
37
+ def validate_all(self) -> None:
38
+ """Validate all configuration components."""
39
+ self.auth.validate_auth_model()
40
+ self.audit.validate_audit_config()
41
+ self.storage.validate_storage_config()
42
+ self.nav.validate_nav_config()
43
+
44
+ def get_ui_context(self) -> dict:
45
+ """Get UI configuration for template context."""
46
+ return self.ui.apply_to_template_context()
47
+
48
+ def get_branding_config(self) -> dict:
49
+ """Get branding configuration."""
50
+ return {
51
+ "title": self.ui.title,
52
+ "logo_url": self.ui.logo_url,
53
+ "favicon_url": self.ui.favicon_url,
54
+ "primary_color": self.ui.primary_color,
55
+ "primary_color_dark": self.ui.primary_color_dark,
56
+ "dark_mode_default": self.ui.dark_mode_default,
57
+ }
58
+
59
+ def get_session_config(self) -> dict:
60
+ """Get session configuration."""
61
+ return {
62
+ "session_ttl": self.auth.session_ttl,
63
+ "session_cookie_name": self.auth.session_cookie_name,
64
+ "session_secure": self.auth.session_secure,
65
+ }
66
+
67
+ def get_audit_config(self) -> dict:
68
+ """Get audit configuration."""
69
+ return {
70
+ "audit_retention_days": self.audit.audit_retention_days,
71
+ }
72
+
73
+ def get_storage_config(self) -> dict:
74
+ """Get storage configuration."""
75
+ return {
76
+ "uploads_url": self.storage.uploads_url,
77
+ }
78
+
79
+ def get_behavior_config(self) -> dict:
80
+ """Get behavior configuration."""
81
+ return {
82
+ "auto_discover": self.behavior.auto_discover,
83
+ "dashboard_stats": self.behavior.dashboard_stats,
84
+ "dashboard_charts": self.behavior.dashboard_charts,
85
+ }
86
+
87
+ def get_nav_config(self) -> dict:
88
+ """Get navigation configuration."""
89
+ return {
90
+ "nav_groups": self.nav.nav_groups,
91
+ "sidebar_builder": self.nav.sidebar_builder,
92
+ "require_tags": self.nav.require_tags,
93
+ "dashboard_permission": self.nav.dashboard_permission,
94
+ "settings_permission": self.nav.settings_permission,
95
+ }
@@ -0,0 +1,138 @@
1
+ """Admin database setup and initialization."""
2
+
3
+ from typing import TYPE_CHECKING, Any
4
+
5
+ if TYPE_CHECKING:
6
+ pass
7
+
8
+
9
+ class AdminDatabase:
10
+ """Handles database setup, table creation, and role seeding."""
11
+
12
+ def __init__(
13
+ self,
14
+ engine: Any | None = None,
15
+ base: Any | None = None,
16
+ ):
17
+ self.engine = engine
18
+ self.base = base
19
+
20
+ async def _create_tables(self) -> None:
21
+ """Create all admin database tables (async-safe)."""
22
+ from sqlalchemy.ext.asyncio import AsyncEngine
23
+
24
+ from fastapi_admin_kit.audit import models as _audit_models # noqa: F401
25
+
26
+ # Import models to register them with metadata
27
+ from fastapi_admin_kit.auth import models as _auth_models # noqa: F401
28
+ from fastapi_admin_kit.models.base import Base as AdminBase
29
+
30
+ if isinstance(self.engine, AsyncEngine):
31
+ # Async engine - use run_sync
32
+ async with self.engine.begin() as conn:
33
+ # Create admin tables
34
+ await conn.run_sync(AdminBase.metadata.create_all)
35
+ # Create user tables if Base is provided
36
+ if self.base is not None:
37
+ await conn.run_sync(self.base.metadata.create_all)
38
+ else:
39
+ # Sync engine - direct call
40
+ AdminBase.metadata.create_all(bind=self.engine)
41
+ if self.base is not None:
42
+ self.base.metadata.create_all(bind=self.engine)
43
+
44
+ async def _seed_roles(
45
+ self, seed_roles: list, seed_roles_overwrite: bool = False
46
+ ) -> None:
47
+ """Seed default roles if none exist (or if overwrite is enabled)."""
48
+ from sqlalchemy import select
49
+ from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession
50
+ from sqlalchemy.orm import Session, sessionmaker
51
+
52
+ from fastapi_admin_kit.auth.models import AdminPermission, AdminRole
53
+
54
+ is_async = isinstance(self.engine, AsyncEngine)
55
+
56
+ if is_async:
57
+ # Use AsyncSession for async engine
58
+ session_local = sessionmaker(
59
+ self.engine, class_=AsyncSession, expire_on_commit=False
60
+ )
61
+ async with session_local() as session:
62
+ # Check existing count
63
+ result = await session.execute(select(AdminRole))
64
+ existing_count = len(result.scalars().all())
65
+
66
+ if existing_count > 0 and not seed_roles_overwrite:
67
+ return
68
+
69
+ if seed_roles_overwrite:
70
+ await session.execute(select(AdminRole).delete())
71
+
72
+ for role_spec in seed_roles:
73
+ role = AdminRole(
74
+ name=role_spec.name, description=role_spec.description
75
+ )
76
+ session.add(role)
77
+ await session.flush() # get role.id
78
+
79
+ if role_spec.permissions:
80
+ for table_name, perms in role_spec.permissions.items():
81
+ perm = AdminPermission(
82
+ role_id=role.id,
83
+ table_name=table_name,
84
+ can_view=perms.get("view", False),
85
+ can_create=perms.get("create", False),
86
+ can_edit=perms.get("edit", False),
87
+ can_delete=perms.get("delete", False),
88
+ )
89
+ session.add(perm)
90
+
91
+ await session.commit()
92
+ else:
93
+ # Use sync Session for sync engine
94
+ session = Session(bind=self.engine)
95
+ try:
96
+ existing_count = session.query(AdminRole).count()
97
+
98
+ if existing_count > 0 and not seed_roles_overwrite:
99
+ return
100
+
101
+ if seed_roles_overwrite:
102
+ session.query(AdminRole).delete()
103
+
104
+ for role_spec in seed_roles:
105
+ role = AdminRole(
106
+ name=role_spec.name, description=role_spec.description
107
+ )
108
+ session.add(role)
109
+ session.flush() # get role.id
110
+
111
+ if role_spec.permissions:
112
+ for table_name, perms in role_spec.permissions.items():
113
+ perm = AdminPermission(
114
+ role_id=role.id,
115
+ table_name=table_name,
116
+ can_view=perms.get("view", False),
117
+ can_create=perms.get("create", False),
118
+ can_edit=perms.get("edit", False),
119
+ can_delete=perms.get("delete", False),
120
+ )
121
+ session.add(perm)
122
+
123
+ session.commit()
124
+ finally:
125
+ session.close()
126
+
127
+ def _init_session_backend(
128
+ self, secret_key: str, session_ttl: int, cookie_name: str, secure: bool
129
+ ) -> Any:
130
+ """Create and store the signed-cookie session backend."""
131
+ from fastapi_admin_kit.auth.session import SignedCookieSessionBackend
132
+
133
+ return SignedCookieSessionBackend(
134
+ secret_key=secret_key,
135
+ session_ttl=session_ttl,
136
+ cookie_name=cookie_name,
137
+ secure=secure,
138
+ )
@@ -0,0 +1,74 @@
1
+ """Admin router building and static file mounting."""
2
+
3
+ from typing import TYPE_CHECKING, Any
4
+
5
+ if TYPE_CHECKING:
6
+ pass
7
+
8
+
9
+ class AdminRouter:
10
+ """Builds and mounts routers for all registered models and admin routes."""
11
+
12
+ def __init__(
13
+ self,
14
+ admin_path: str = "/admin",
15
+ secret_key: str = "",
16
+ ):
17
+ self.admin_path = admin_path.rstrip("/")
18
+ self.secret_key = secret_key or ""
19
+
20
+ def _build_router(self, app: Any) -> None:
21
+ """Build and mount routers for all registered models."""
22
+ from fastapi_admin_kit.api.search import router as search_api_router
23
+ from fastapi_admin_kit.auth.router import router as auth_router
24
+ from fastapi_admin_kit.registry import AdminRegistry
25
+ from fastapi_admin_kit.router import build_model_router
26
+ from fastapi_admin_kit.views.audit import router as audit_router
27
+ from fastapi_admin_kit.views.roles import router as roles_router
28
+
29
+ registry = AdminRegistry()
30
+
31
+ for registered in registry.all():
32
+ model_router = build_model_router(registered)
33
+ app.include_router(model_router, prefix=self.admin_path)
34
+
35
+ # Auth routes (login/logout)
36
+ app.include_router(auth_router, prefix=self.admin_path)
37
+
38
+ # Global search API
39
+ app.include_router(search_api_router, prefix=self.admin_path)
40
+
41
+ # Audit & role management routes
42
+ app.include_router(audit_router, prefix=self.admin_path)
43
+ app.include_router(roles_router, prefix=self.admin_path)
44
+
45
+ def _mount_static(self, app: Any) -> None:
46
+ """Mount the static files directory and uploads directory."""
47
+ from pathlib import Path
48
+
49
+ from fastapi.staticfiles import StaticFiles
50
+
51
+ static_dir = Path(__file__).parent.parent / "static"
52
+ if static_dir.is_dir():
53
+ # Primary mount — matches template references (/static/...)
54
+ app.mount(
55
+ "/static",
56
+ StaticFiles(directory=str(static_dir)),
57
+ name="admin_static",
58
+ )
59
+
60
+ def _init_jinja(self, app: Any) -> None:
61
+ """Initialise the Jinja2 template environment."""
62
+ import re
63
+ from pathlib import Path
64
+
65
+ from starlette.templating import Jinja2Templates
66
+
67
+ templates_dir = Path(__file__).parent / "templates"
68
+ jinja_env = Jinja2Templates(directory=str(templates_dir))
69
+
70
+ def slugify(s: str) -> str:
71
+ return re.sub(r"[^\w]", "-", s, flags=re.A).strip("-").lower()
72
+
73
+ jinja_env.env.filters["slugify"] = slugify
74
+ app.state.admin_jinja_env = jinja_env