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.
- fastapi_admin_kit/__init__.py +73 -0
- fastapi_admin_kit/actions/__init__.py +63 -0
- fastapi_admin_kit/actions/base.py +68 -0
- fastapi_admin_kit/actions/registry.py +43 -0
- fastapi_admin_kit/admin/__init__.py +17 -0
- fastapi_admin_kit/admin/admin_config.py +95 -0
- fastapi_admin_kit/admin/admin_database.py +138 -0
- fastapi_admin_kit/admin/admin_router.py +74 -0
- fastapi_admin_kit/admin/admin_template.py +203 -0
- fastapi_admin_kit/admin/builtin_models.py +284 -0
- fastapi_admin_kit/admin/core.py +1036 -0
- fastapi_admin_kit/admin/decorators.py +70 -0
- fastapi_admin_kit/admin/state.py +76 -0
- fastapi_admin_kit/admin.py +728 -0
- fastapi_admin_kit/api/__init__.py +44 -0
- fastapi_admin_kit/api/auth.py +342 -0
- fastapi_admin_kit/api/crud.py +128 -0
- fastapi_admin_kit/api/deps.py +79 -0
- fastapi_admin_kit/api/roles.py +128 -0
- fastapi_admin_kit/api/schema_generator.py +171 -0
- fastapi_admin_kit/api/schemas.py +81 -0
- fastapi_admin_kit/api/search.py +132 -0
- fastapi_admin_kit/audit/__init__.py +36 -0
- fastapi_admin_kit/audit/context.py +62 -0
- fastapi_admin_kit/audit/diff.py +77 -0
- fastapi_admin_kit/audit/event_bus.py +96 -0
- fastapi_admin_kit/audit/events.py +48 -0
- fastapi_admin_kit/audit/listener.py +159 -0
- fastapi_admin_kit/audit/logger.py +28 -0
- fastapi_admin_kit/audit/middleware.py +39 -0
- fastapi_admin_kit/audit/models.py +53 -0
- fastapi_admin_kit/audit/sqlalchemy_logger.py +58 -0
- fastapi_admin_kit/auth/__init__.py +34 -0
- fastapi_admin_kit/auth/backend.py +95 -0
- fastapi_admin_kit/auth/csrf.py +240 -0
- fastapi_admin_kit/auth/dependencies.py +150 -0
- fastapi_admin_kit/auth/identity.py +181 -0
- fastapi_admin_kit/auth/models.py +246 -0
- fastapi_admin_kit/auth/password.py +35 -0
- fastapi_admin_kit/auth/permissions.py +205 -0
- fastapi_admin_kit/auth/protocol.py +22 -0
- fastapi_admin_kit/auth/ratelimit.py +88 -0
- fastapi_admin_kit/auth/router.py +10 -0
- fastapi_admin_kit/auth/session.py +79 -0
- fastapi_admin_kit/auth/totp.py +83 -0
- fastapi_admin_kit/auth/views.py +165 -0
- fastapi_admin_kit/cli.py +229 -0
- fastapi_admin_kit/config/__init__.py +19 -0
- fastapi_admin_kit/config/audit.py +18 -0
- fastapi_admin_kit/config/auth.py +54 -0
- fastapi_admin_kit/config/behavior.py +27 -0
- fastapi_admin_kit/config/nav.py +32 -0
- fastapi_admin_kit/config/storage.py +22 -0
- fastapi_admin_kit/config/theme.py +215 -0
- fastapi_admin_kit/config/ui.py +147 -0
- fastapi_admin_kit/dashboard/__init__.py +64 -0
- fastapi_admin_kit/db.py +133 -0
- fastapi_admin_kit/exceptions.py +5 -0
- fastapi_admin_kit/field_types.py +81 -0
- fastapi_admin_kit/filters/__init__.py +21 -0
- fastapi_admin_kit/filters/base.py +170 -0
- fastapi_admin_kit/filters/registry.py +68 -0
- fastapi_admin_kit/flash.py +45 -0
- fastapi_admin_kit/form/__init__.py +1 -0
- fastapi_admin_kit/form/pipeline.py +106 -0
- fastapi_admin_kit/inspection/__init__.py +117 -0
- fastapi_admin_kit/inspection/registry.py +253 -0
- fastapi_admin_kit/inspection.py +115 -0
- fastapi_admin_kit/modeladmin.py +375 -0
- fastapi_admin_kit/models/__init__.py +7 -0
- fastapi_admin_kit/models/base.py +7 -0
- fastapi_admin_kit/nav.py +208 -0
- fastapi_admin_kit/pagination/__init__.py +14 -0
- fastapi_admin_kit/pagination/base.py +40 -0
- fastapi_admin_kit/pagination/cursor.py +97 -0
- fastapi_admin_kit/pagination/dynamic.py +48 -0
- fastapi_admin_kit/pagination/offset.py +42 -0
- fastapi_admin_kit/plugins/__init__.py +1 -0
- fastapi_admin_kit/py.typed +0 -0
- fastapi_admin_kit/registry/__init__.py +5 -0
- fastapi_admin_kit/registry/core.py +287 -0
- fastapi_admin_kit/registry/validation.py +107 -0
- fastapi_admin_kit/registry.py +15 -0
- fastapi_admin_kit/router.py +335 -0
- fastapi_admin_kit/static/css/admin.css +4736 -0
- fastapi_admin_kit/static/css/presets.css +317 -0
- fastapi_admin_kit/static/css/tokens.css +217 -0
- fastapi_admin_kit/static/css/variables.css +74 -0
- fastapi_admin_kit/static/icons/heroicons.svg +160 -0
- fastapi_admin_kit/static/js/admin.js +692 -0
- fastapi_admin_kit/static/js/htmx-config.js +42 -0
- fastapi_admin_kit/storage/__init__.py +6 -0
- fastapi_admin_kit/storage/base.py +48 -0
- fastapi_admin_kit/storage/local.py +73 -0
- fastapi_admin_kit/templates/base.html +142 -0
- fastapi_admin_kit/templates/macros/form_fields.html +660 -0
- fastapi_admin_kit/templates/macros/icons.html +50 -0
- fastapi_admin_kit/templates/macros/table.html +108 -0
- fastapi_admin_kit/templates/macros/widgets.html +159 -0
- fastapi_admin_kit/templates/pages/2fa/setup.html +122 -0
- fastapi_admin_kit/templates/pages/2fa/verify.html +55 -0
- fastapi_admin_kit/templates/pages/audit_detail.html +122 -0
- fastapi_admin_kit/templates/pages/audit_log.html +102 -0
- fastapi_admin_kit/templates/pages/dashboard.html +295 -0
- fastapi_admin_kit/templates/pages/detail.html +183 -0
- fastapi_admin_kit/templates/pages/form.html +119 -0
- fastapi_admin_kit/templates/pages/list.html +277 -0
- fastapi_admin_kit/templates/pages/login.html +85 -0
- fastapi_admin_kit/templates/pages/profile/password.html +78 -0
- fastapi_admin_kit/templates/pages/profile/profile.html +73 -0
- fastapi_admin_kit/templates/pages/role_form.html +75 -0
- fastapi_admin_kit/templates/pages/roles/form.html +117 -0
- fastapi_admin_kit/templates/pages/roles/list.html +69 -0
- fastapi_admin_kit/templates/pages/roles.html +77 -0
- fastapi_admin_kit/templates/pages/settings/theme.html +255 -0
- fastapi_admin_kit/templates/pages/users/form.html +229 -0
- fastapi_admin_kit/templates/pages/users/list.html +83 -0
- fastapi_admin_kit/templates/partials/command_palette.html +52 -0
- fastapi_admin_kit/templates/partials/field_wrapper.html +2 -0
- fastapi_admin_kit/templates/partials/flash_messages.html +39 -0
- fastapi_admin_kit/templates/partials/head.html +21 -0
- fastapi_admin_kit/templates/partials/head_minimal.html +18 -0
- fastapi_admin_kit/templates/partials/list_table.html +178 -0
- fastapi_admin_kit/templates/partials/mobile_backdrop.html +2 -0
- fastapi_admin_kit/templates/partials/pagination.html +82 -0
- fastapi_admin_kit/templates/partials/permission_widget.html +86 -0
- fastapi_admin_kit/templates/partials/scripts.html +13 -0
- fastapi_admin_kit/templates/partials/sidebar.html +94 -0
- fastapi_admin_kit/templates/partials/topbar.html +95 -0
- fastapi_admin_kit/types.py +145 -0
- fastapi_admin_kit/validation.py +43 -0
- fastapi_admin_kit/views/__init__.py +78 -0
- fastapi_admin_kit/views/audit.py +134 -0
- fastapi_admin_kit/views/bulk.py +28 -0
- fastapi_admin_kit/views/class_views.py +1040 -0
- fastapi_admin_kit/views/context.py +588 -0
- fastapi_admin_kit/views/dashboard.py +162 -0
- fastapi_admin_kit/views/delete.py +31 -0
- fastapi_admin_kit/views/extra.py +65 -0
- fastapi_admin_kit/views/factory.py +667 -0
- fastapi_admin_kit/views/form.py +159 -0
- fastapi_admin_kit/views/list.py +28 -0
- fastapi_admin_kit/views/profile.py +219 -0
- fastapi_admin_kit/views/protocols.py +54 -0
- fastapi_admin_kit/views/renderers.py +634 -0
- fastapi_admin_kit/views/roles.py +230 -0
- fastapi_admin_kit/views/search.py +31 -0
- fastapi_admin_kit/views/settings.py +31 -0
- fastapi_admin_kit/views/sidebar.py +101 -0
- fastapi_admin_kit/views/totp.py +249 -0
- fastapi_admin_kit/views/users.py +347 -0
- fastapi_admin_kit/views.py +117 -0
- fastapi_admin_kit/widgets/__init__.py +44 -0
- fastapi_admin_kit/widgets/base.py +44 -0
- fastapi_admin_kit/widgets/inputs.py +363 -0
- fastapi_admin_kit/widgets/registry.py +110 -0
- fastapi_admin_kit/widgets/relation.py +70 -0
- fastapi_admin_kit/widgets/resolver.py +102 -0
- fastapi_admin_kit-0.1.0.dist-info/METADATA +210 -0
- fastapi_admin_kit-0.1.0.dist-info/RECORD +163 -0
- fastapi_admin_kit-0.1.0.dist-info/WHEEL +4 -0
- fastapi_admin_kit-0.1.0.dist-info/entry_points.txt +3 -0
- 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
|
+
}
|