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,162 @@
1
+ """Dashboard view handler factory."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import importlib
6
+ from datetime import datetime, timedelta, timezone
7
+ from typing import Any
8
+
9
+ from fastapi import Depends, Request
10
+ from fastapi.responses import HTMLResponse
11
+ from sqlalchemy import func, select
12
+
13
+ from fastapi_admin_kit.auth.dependencies import get_current_admin_user
14
+ from fastapi_admin_kit.db import get_db_session
15
+
16
+
17
+ def _resolve_callback(dotted_path: str) -> Any | None:
18
+ """Resolve a dotted path like 'myapp.dashboards.custom_stats' to the function."""
19
+ if not dotted_path:
20
+ return None
21
+ try:
22
+ module_path, _, func_name = dotted_path.rpartition(".")
23
+ module = importlib.import_module(module_path)
24
+ return getattr(module, func_name, None)
25
+ except (ImportError, AttributeError):
26
+ return None
27
+
28
+
29
+ def dashboard_view_factory(admin: Any):
30
+ """Return a dashboard view function bound to the given admin instance."""
31
+ async def dashboard_view(
32
+ request: Request,
33
+ current_user: Any = Depends(get_current_admin_user),
34
+ ):
35
+ templates = request.app.state.admin_jinja_env
36
+ admin_instance = request.app.state.admin
37
+ config = request.app.state.admin_config
38
+ session = get_db_session(request)
39
+
40
+ # Get registered models
41
+ registered_models = admin_instance.registry.all()
42
+
43
+ # Determine which models to show stats for
44
+ dashboard_stats = config.get("dashboard_stats", [])
45
+ if dashboard_stats:
46
+ models_for_stats = [
47
+ m for m in registered_models if m.table_name in dashboard_stats
48
+ ]
49
+ else:
50
+ models_for_stats = registered_models
51
+
52
+ # Get record counts for each model
53
+ now = datetime.now(timezone.utc) # noqa: UP017
54
+ thirty_days_ago = now - timedelta(days=30)
55
+
56
+ stat_cards = []
57
+ for model in models_for_stats:
58
+ count_query = select(func.count()).select_from(model.model)
59
+ count = (await session.execute(count_query)).scalar()
60
+
61
+ # Trend: count in last 30 days vs previous 30 days
62
+ model_cls = model.model
63
+ trend_current = 0
64
+ trend_previous = 0
65
+ has_timestamp = False
66
+ # Look for a created_at or timestamp column
67
+ for col_name in ("created_at", "timestamp", "date", "created"):
68
+ if hasattr(model_cls, col_name):
69
+ has_timestamp = True
70
+ col = getattr(model_cls, col_name)
71
+ recent_q = select(func.count()).select_from(model_cls).where(
72
+ col >= thirty_days_ago
73
+ )
74
+ trend_current = (await session.execute(recent_q)).scalar() or 0
75
+ older_q = select(func.count()).select_from(model_cls).where(
76
+ col < thirty_days_ago
77
+ )
78
+ trend_previous = (await session.execute(older_q)).scalar() or 0
79
+ break
80
+
81
+ # Calculate trend percentage
82
+ trend_pct = 0
83
+ trend_direction = "up"
84
+ if has_timestamp and trend_previous > 0:
85
+ trend_pct = int(((trend_current - trend_previous) / trend_previous) * 100)
86
+ trend_direction = "up" if trend_pct >= 0 else "down"
87
+ elif has_timestamp and trend_current > 0:
88
+ trend_pct = 100
89
+ trend_direction = "up"
90
+
91
+ stat_cards.append({
92
+ "title": model.verbose_name_plural,
93
+ "icon": getattr(model.admin, "icon", None) or "cube",
94
+ "count": count,
95
+ "url": f"{admin_instance.admin_path}/{model.table_name}/",
96
+ "trend_pct": trend_pct,
97
+ "trend_direction": trend_direction,
98
+ "has_trend": has_timestamp,
99
+ })
100
+
101
+ # Fetch last 10 audit entries
102
+ from fastapi_admin_kit.audit.models import AuditLog
103
+ audit_query = select(AuditLog).order_by(AuditLog.timestamp.desc()).limit(10)
104
+ recent_audit = (await session.execute(audit_query)).scalars().all()
105
+
106
+ # Check if charts are enabled
107
+ show_charts = config.get("dashboard_charts", True)
108
+
109
+ # Resolve dashboard callback if configured
110
+ dashboard_callback = admin_instance.config.behavior.dashboard_callback
111
+ callback_data = {}
112
+ if dashboard_callback:
113
+ cb_fn = _resolve_callback(dashboard_callback)
114
+ if cb_fn and callable(cb_fn):
115
+ import asyncio
116
+ if asyncio.iscoroutinefunction(cb_fn):
117
+ callback_data = await cb_fn(request, session) or {}
118
+ else:
119
+ callback_data = cb_fn(request, session) or {}
120
+
121
+ # Dashboard components
122
+ dashboard_components = admin_instance.config.behavior.dashboard_components
123
+
124
+ # System overview data for donut chart
125
+ overview_data = []
126
+ overview_colors = [
127
+ "#14b8a6", "#8b5cf6", "#3b82f6", "#f97316",
128
+ "#22c55e", "#eab308", "#ec4899", "#ef4444",
129
+ ]
130
+ total_count = 0
131
+ for i, model in enumerate(registered_models):
132
+ count_query = select(func.count()).select_from(model.model)
133
+ count = (await session.execute(count_query)).scalar() or 0
134
+ total_count += count
135
+ overview_data.append({
136
+ "label": model.verbose_name_plural,
137
+ "value": count,
138
+ "color": overview_colors[i % len(overview_colors)],
139
+ })
140
+
141
+ template = templates.get_template("pages/dashboard.html")
142
+ context: dict[str, Any] = {
143
+ "request": request,
144
+ "registered_models": registered_models,
145
+ "stat_cards": stat_cards,
146
+ "recent_audit": recent_audit,
147
+ "show_charts": show_charts,
148
+ "admin_path": admin_instance.admin_path,
149
+ "title": admin_instance.title,
150
+ "dashboard_callback_data": callback_data,
151
+ "dashboard_components": dashboard_components,
152
+ "overview_data": overview_data,
153
+ "overview_total": total_count,
154
+ }
155
+ if hasattr(admin_instance, "build_sidebar_context") and current_user is not None:
156
+ from fastapi_admin_kit.views.sidebar import inject_sidebar_context
157
+ context = await inject_sidebar_context(request, context)
158
+ html = template.render(**context)
159
+ return HTMLResponse(content=html)
160
+
161
+ dashboard_view.__name__ = "dashboard"
162
+ return dashboard_view
@@ -0,0 +1,31 @@
1
+ """Delete handler factory for registered models.
2
+
3
+ Backward-compatible wrapper — delegates to DeleteView class.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from typing import Any
9
+
10
+ from fastapi import Request
11
+
12
+ from fastapi_admin_kit.registry import RegisteredModel
13
+
14
+
15
+ def delete_factory(registered: RegisteredModel):
16
+ """Create a delete handler — delegates to DeleteView.html_response."""
17
+ from fastapi_admin_kit.views.class_views import (
18
+ DeleteView,
19
+ _resolve_view_class,
20
+ )
21
+
22
+ view_class = _resolve_view_class(
23
+ registered.admin, "delete_view_class", DeleteView
24
+ )
25
+ view_instance = view_class(registered)
26
+
27
+ async def _handler(request: Request, **kwargs: Any):
28
+ return await view_instance.html_response(request, **kwargs)
29
+
30
+ _handler.__name__ = f"delete_{registered.table_name}"
31
+ return _handler
@@ -0,0 +1,65 @@
1
+ """AdminExtra — per-model extra CSS and JS asset declarations."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+
8
+ class AdminExtra:
9
+ """Per-model extra CSS and JS configuration.
10
+
11
+ SRP: Only manages asset declarations. No rendering logic.
12
+
13
+ Usage::
14
+
15
+ class ProductAdmin(ModelAdmin):
16
+ extra = AdminExtra(
17
+ css=["css/product-admin.css"],
18
+ js=["js/product-admin.js"],
19
+ css_urls=["https://cdn.example.com/lib.css"],
20
+ js_urls=["https://cdn.example.com/lib.js"],
21
+ css_inline=".product-table { color: red; }",
22
+ js_inline="console.log('product admin loaded');",
23
+ )
24
+ """
25
+
26
+ def __init__(
27
+ self,
28
+ css: list[str] | None = None,
29
+ js: list[str] | None = None,
30
+ css_urls: list[str] | None = None,
31
+ js_urls: list[str] | None = None,
32
+ css_inline: str | None = None,
33
+ js_inline: str | None = None,
34
+ ):
35
+ self.css = css or []
36
+ self.js = js or []
37
+ self.css_urls = css_urls or []
38
+ self.js_urls = js_urls or []
39
+ self.css_inline = css_inline
40
+ self.js_inline = js_inline
41
+
42
+ def get_css_urls(self, admin_path: str = "/admin") -> list[str]:
43
+ """Return all CSS URLs (static + external)."""
44
+ urls = []
45
+ for path in self.css:
46
+ urls.append(f"{admin_path}/static/{path.lstrip('/')}")
47
+ urls.extend(self.css_urls)
48
+ return urls
49
+
50
+ def get_js_urls(self, admin_path: str = "/admin") -> list[str]:
51
+ """Return all JS URLs (static + external)."""
52
+ urls = []
53
+ for path in self.js:
54
+ urls.append(f"{admin_path}/static/{path.lstrip('/')}")
55
+ urls.extend(self.js_urls)
56
+ return urls
57
+
58
+ def to_context(self, admin_path: str = "/admin") -> dict[str, Any]:
59
+ """Convert to template context dict for base.html."""
60
+ return {
61
+ "extra_css": self.get_css_urls(admin_path),
62
+ "extra_js": self.get_js_urls(admin_path),
63
+ "extra_css_inline": self.css_inline,
64
+ "extra_js_inline": self.js_inline,
65
+ }