fasthx-admin 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fasthx_admin-0.1.0/.gitignore +25 -0
- fasthx_admin-0.1.0/PKG-INFO +197 -0
- fasthx_admin-0.1.0/README.md +165 -0
- fasthx_admin-0.1.0/examples/demo/app.py +579 -0
- fasthx_admin-0.1.0/examples/demo/models.py +92 -0
- fasthx_admin-0.1.0/pyproject.toml +49 -0
- fasthx_admin-0.1.0/src/fasthx_admin/__init__.py +26 -0
- fasthx_admin-0.1.0/src/fasthx_admin/auth.py +145 -0
- fasthx_admin-0.1.0/src/fasthx_admin/crud.py +588 -0
- fasthx_admin-0.1.0/src/fasthx_admin/database.py +57 -0
- fasthx_admin-0.1.0/src/fasthx_admin/static/css/style.css +818 -0
- fasthx_admin-0.1.0/src/fasthx_admin/static/js/app.js +31 -0
- fasthx_admin-0.1.0/src/fasthx_admin/templates/base.html +98 -0
- fasthx_admin-0.1.0/src/fasthx_admin/templates/dashboard.html +163 -0
- fasthx_admin-0.1.0/src/fasthx_admin/templates/detail.html +32 -0
- fasthx_admin-0.1.0/src/fasthx_admin/templates/form.html +57 -0
- fasthx_admin-0.1.0/src/fasthx_admin/templates/list.html +89 -0
- fasthx_admin-0.1.0/src/fasthx_admin/templates/login.html +219 -0
- fasthx_admin-0.1.0/src/fasthx_admin/templates/partials/_form_field.html +55 -0
- fasthx_admin-0.1.0/src/fasthx_admin/templates/partials/_wizard_indicators.html +21 -0
- fasthx_admin-0.1.0/src/fasthx_admin/templates/partials/dropdown_options.html +4 -0
- fasthx_admin-0.1.0/src/fasthx_admin/templates/partials/progress_bar.html +25 -0
- fasthx_admin-0.1.0/src/fasthx_admin/templates/partials/row_actions.html +39 -0
- fasthx_admin-0.1.0/src/fasthx_admin/templates/partials/status_cell.html +21 -0
- fasthx_admin-0.1.0/src/fasthx_admin/templates/partials/table_body.html +29 -0
- fasthx_admin-0.1.0/src/fasthx_admin/templates/partials/wizard_step.html +163 -0
- fasthx_admin-0.1.0/src/fasthx_admin/templates/wizard.html +19 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
__pycache__/
|
|
2
|
+
*.py[cod]
|
|
3
|
+
*$py.class
|
|
4
|
+
*.so
|
|
5
|
+
*.egg-info/
|
|
6
|
+
dist/
|
|
7
|
+
build/
|
|
8
|
+
*.egg
|
|
9
|
+
.eggs/
|
|
10
|
+
*.db
|
|
11
|
+
*.sqlite3
|
|
12
|
+
.env
|
|
13
|
+
.venv/
|
|
14
|
+
venv/
|
|
15
|
+
env/
|
|
16
|
+
.idea/
|
|
17
|
+
.vscode/
|
|
18
|
+
*.swp
|
|
19
|
+
*.swo
|
|
20
|
+
*~
|
|
21
|
+
.DS_Store
|
|
22
|
+
Thumbs.db
|
|
23
|
+
.pytest_cache/
|
|
24
|
+
.mypy_cache/
|
|
25
|
+
.ruff_cache/
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: fasthx-admin
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: FastAPI + HTMX + Jinja2 admin interface framework — a modern replacement for Flask-Admin
|
|
5
|
+
Project-URL: Homepage, https://github.com/talbiston/fasthx-admin
|
|
6
|
+
Project-URL: Repository, https://github.com/talbiston/fasthx-admin
|
|
7
|
+
Author: talbiston
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
Keywords: admin,crud,fastapi,htmx,jinja2
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Framework :: FastAPI
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
|
20
|
+
Requires-Python: >=3.10
|
|
21
|
+
Requires-Dist: fastapi
|
|
22
|
+
Requires-Dist: itsdangerous
|
|
23
|
+
Requires-Dist: jinja2
|
|
24
|
+
Requires-Dist: python-multipart
|
|
25
|
+
Requires-Dist: requests
|
|
26
|
+
Requires-Dist: sqlalchemy
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: httpx; extra == 'dev'
|
|
29
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
30
|
+
Requires-Dist: uvicorn[standard]; extra == 'dev'
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
# fasthx-admin
|
|
34
|
+
|
|
35
|
+
FastAPI + HTMX + Jinja2 admin interface framework — a modern replacement for Flask-Admin.
|
|
36
|
+
|
|
37
|
+
## Features
|
|
38
|
+
|
|
39
|
+
- Auto-generated CRUD routes from SQLAlchemy models
|
|
40
|
+
- Dark/light theme with Bootstrap 5.3
|
|
41
|
+
- HTMX-powered interactions (search, sorting, polling, dependent dropdowns)
|
|
42
|
+
- Accordion-grouped form sections
|
|
43
|
+
- Custom column formatters and row actions
|
|
44
|
+
- OIDC/Keycloak authentication (with `AUTH_DISABLED` dev mode)
|
|
45
|
+
- Deploy wizard with real-time progress tracking
|
|
46
|
+
- Responsive sidebar navigation
|
|
47
|
+
|
|
48
|
+
## Installation
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pip install fasthx-admin
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
For development (includes uvicorn):
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
pip install fasthx-admin[dev]
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Quick Start
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
from contextlib import asynccontextmanager
|
|
64
|
+
|
|
65
|
+
from fastapi import FastAPI
|
|
66
|
+
from starlette.middleware.sessions import SessionMiddleware
|
|
67
|
+
|
|
68
|
+
from fasthx_admin import Admin, CRUDView, Base, init_db
|
|
69
|
+
|
|
70
|
+
# 1. Define your models
|
|
71
|
+
from sqlalchemy import Column, Integer, String
|
|
72
|
+
|
|
73
|
+
class Customer(Base):
|
|
74
|
+
__tablename__ = "customers"
|
|
75
|
+
id = Column(Integer, primary_key=True)
|
|
76
|
+
name = Column(String(100), nullable=False)
|
|
77
|
+
sid = Column(String(50), nullable=False)
|
|
78
|
+
|
|
79
|
+
__admin_category__ = "CRM"
|
|
80
|
+
__admin_icon__ = "building"
|
|
81
|
+
__admin_name__ = "Customers"
|
|
82
|
+
|
|
83
|
+
# 2. Initialise the database
|
|
84
|
+
engine = init_db("sqlite:///./app.db", connect_args={"check_same_thread": False})
|
|
85
|
+
|
|
86
|
+
# 3. Create the app
|
|
87
|
+
@asynccontextmanager
|
|
88
|
+
async def lifespan(app):
|
|
89
|
+
Base.metadata.create_all(bind=engine)
|
|
90
|
+
yield
|
|
91
|
+
|
|
92
|
+
app = FastAPI(lifespan=lifespan)
|
|
93
|
+
app.add_middleware(SessionMiddleware, secret_key="change-me")
|
|
94
|
+
|
|
95
|
+
# 4. Create the admin and register views
|
|
96
|
+
admin = Admin(app, title="My Admin")
|
|
97
|
+
|
|
98
|
+
class CustomerView(CRUDView):
|
|
99
|
+
model = Customer
|
|
100
|
+
column_list = ["id", "name", "sid"]
|
|
101
|
+
|
|
102
|
+
admin.add_view(CustomerView)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Run with auth disabled for development:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
AUTH_DISABLED=1 uvicorn app:app --reload
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## CRUDView Configuration
|
|
112
|
+
|
|
113
|
+
Subclass `CRUDView` and set class-level attributes:
|
|
114
|
+
|
|
115
|
+
| Attribute | Description |
|
|
116
|
+
|---|---|
|
|
117
|
+
| `model` | SQLAlchemy model class (required) |
|
|
118
|
+
| `name` | URL prefix (defaults to `__tablename__`) |
|
|
119
|
+
| `display_name` | Sidebar label (defaults to model's `__admin_name__`) |
|
|
120
|
+
| `category` | Sidebar group (defaults to model's `__admin_category__`) |
|
|
121
|
+
| `icon` | Bootstrap Icons name (defaults to model's `__admin_icon__`) |
|
|
122
|
+
| `column_list` | Columns to show in the list view |
|
|
123
|
+
| `column_exclude` | Columns to exclude (alternative to `column_list`) |
|
|
124
|
+
| `column_labels` | Display name overrides, e.g. `{"customer_id": "Customer"}` |
|
|
125
|
+
| `column_formatters` | `{col: fn(value, obj) -> html_string}` |
|
|
126
|
+
| `column_searchable` | Columns to search (defaults to all String columns) |
|
|
127
|
+
| `column_sortable` | Columns that can be sorted |
|
|
128
|
+
| `form_columns` | Editable fields (defaults to all except `id`) |
|
|
129
|
+
| `form_sections` | Accordion groups: `{"Section": ["field1", "field2"]}` |
|
|
130
|
+
| `form_widget_overrides` | Per-field HTMX attrs or select choices |
|
|
131
|
+
| `row_actions` | Custom action buttons per row |
|
|
132
|
+
| `htmx_columns` | Auto-polling cells: `{"field": {"url": "...", "trigger": "every 3s"}}` |
|
|
133
|
+
| `page_size` | Records per page (default 20) |
|
|
134
|
+
| `can_create` / `can_edit` / `can_delete` | Permission flags |
|
|
135
|
+
|
|
136
|
+
## Custom Endpoints
|
|
137
|
+
|
|
138
|
+
Override `setup_endpoints()` on your CRUDView subclass to add custom routes:
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
class OrchestratorView(CRUDView):
|
|
142
|
+
model = Orchestrator
|
|
143
|
+
|
|
144
|
+
def setup_endpoints(self):
|
|
145
|
+
@self.router.post(f"/{self.name}/{{item_id}}/build")
|
|
146
|
+
async def build(request: Request, item_id: int, db=Depends(get_db)):
|
|
147
|
+
# custom logic
|
|
148
|
+
return HTMLResponse("", headers={"HX-Redirect": f"/{self.name}"})
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Admin Class
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
admin = Admin(
|
|
155
|
+
app,
|
|
156
|
+
title="My Admin", # Sidebar brand + page titles
|
|
157
|
+
static_url="/static/fasthx-admin", # Where package statics are mounted
|
|
158
|
+
public_pages={"login.html"}, # Pages that skip auth check
|
|
159
|
+
)
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
The `Admin` instance:
|
|
163
|
+
- Mounts built-in static files (CSS, JS)
|
|
164
|
+
- Sets up Jinja2 templates from the package
|
|
165
|
+
- Wraps template responses with nav context + auth check
|
|
166
|
+
- Provides `admin.templates` for rendering custom pages
|
|
167
|
+
|
|
168
|
+
## Model Metadata
|
|
169
|
+
|
|
170
|
+
Set these on your SQLAlchemy model classes for automatic sidebar grouping:
|
|
171
|
+
|
|
172
|
+
```python
|
|
173
|
+
class MyModel(Base):
|
|
174
|
+
__tablename__ = "my_models"
|
|
175
|
+
__admin_category__ = "Section Name" # Sidebar group
|
|
176
|
+
__admin_icon__ = "table" # Bootstrap Icons name
|
|
177
|
+
__admin_name__ = "My Models" # Display label
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## Environment Variables
|
|
181
|
+
|
|
182
|
+
| Variable | Purpose | Default |
|
|
183
|
+
|---|---|---|
|
|
184
|
+
| `AUTH_DISABLED` | Bypass authentication (`1`/`true`/`yes`) | disabled |
|
|
185
|
+
| `SESSION_SECRET` | Session signing key | (set in your app) |
|
|
186
|
+
| `OIDC_SECRETS` | Path to `client_secrets.json` | `./client_secrets.json` |
|
|
187
|
+
|
|
188
|
+
## Running the Demo
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
cd examples/demo
|
|
192
|
+
pip install -e ../..
|
|
193
|
+
pip install uvicorn[standard]
|
|
194
|
+
AUTH_DISABLED=1 uvicorn app:app --reload
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Open http://127.0.0.1:8000
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# fasthx-admin
|
|
2
|
+
|
|
3
|
+
FastAPI + HTMX + Jinja2 admin interface framework — a modern replacement for Flask-Admin.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Auto-generated CRUD routes from SQLAlchemy models
|
|
8
|
+
- Dark/light theme with Bootstrap 5.3
|
|
9
|
+
- HTMX-powered interactions (search, sorting, polling, dependent dropdowns)
|
|
10
|
+
- Accordion-grouped form sections
|
|
11
|
+
- Custom column formatters and row actions
|
|
12
|
+
- OIDC/Keycloak authentication (with `AUTH_DISABLED` dev mode)
|
|
13
|
+
- Deploy wizard with real-time progress tracking
|
|
14
|
+
- Responsive sidebar navigation
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pip install fasthx-admin
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
For development (includes uvicorn):
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pip install fasthx-admin[dev]
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Quick Start
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
from contextlib import asynccontextmanager
|
|
32
|
+
|
|
33
|
+
from fastapi import FastAPI
|
|
34
|
+
from starlette.middleware.sessions import SessionMiddleware
|
|
35
|
+
|
|
36
|
+
from fasthx_admin import Admin, CRUDView, Base, init_db
|
|
37
|
+
|
|
38
|
+
# 1. Define your models
|
|
39
|
+
from sqlalchemy import Column, Integer, String
|
|
40
|
+
|
|
41
|
+
class Customer(Base):
|
|
42
|
+
__tablename__ = "customers"
|
|
43
|
+
id = Column(Integer, primary_key=True)
|
|
44
|
+
name = Column(String(100), nullable=False)
|
|
45
|
+
sid = Column(String(50), nullable=False)
|
|
46
|
+
|
|
47
|
+
__admin_category__ = "CRM"
|
|
48
|
+
__admin_icon__ = "building"
|
|
49
|
+
__admin_name__ = "Customers"
|
|
50
|
+
|
|
51
|
+
# 2. Initialise the database
|
|
52
|
+
engine = init_db("sqlite:///./app.db", connect_args={"check_same_thread": False})
|
|
53
|
+
|
|
54
|
+
# 3. Create the app
|
|
55
|
+
@asynccontextmanager
|
|
56
|
+
async def lifespan(app):
|
|
57
|
+
Base.metadata.create_all(bind=engine)
|
|
58
|
+
yield
|
|
59
|
+
|
|
60
|
+
app = FastAPI(lifespan=lifespan)
|
|
61
|
+
app.add_middleware(SessionMiddleware, secret_key="change-me")
|
|
62
|
+
|
|
63
|
+
# 4. Create the admin and register views
|
|
64
|
+
admin = Admin(app, title="My Admin")
|
|
65
|
+
|
|
66
|
+
class CustomerView(CRUDView):
|
|
67
|
+
model = Customer
|
|
68
|
+
column_list = ["id", "name", "sid"]
|
|
69
|
+
|
|
70
|
+
admin.add_view(CustomerView)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Run with auth disabled for development:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
AUTH_DISABLED=1 uvicorn app:app --reload
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## CRUDView Configuration
|
|
80
|
+
|
|
81
|
+
Subclass `CRUDView` and set class-level attributes:
|
|
82
|
+
|
|
83
|
+
| Attribute | Description |
|
|
84
|
+
|---|---|
|
|
85
|
+
| `model` | SQLAlchemy model class (required) |
|
|
86
|
+
| `name` | URL prefix (defaults to `__tablename__`) |
|
|
87
|
+
| `display_name` | Sidebar label (defaults to model's `__admin_name__`) |
|
|
88
|
+
| `category` | Sidebar group (defaults to model's `__admin_category__`) |
|
|
89
|
+
| `icon` | Bootstrap Icons name (defaults to model's `__admin_icon__`) |
|
|
90
|
+
| `column_list` | Columns to show in the list view |
|
|
91
|
+
| `column_exclude` | Columns to exclude (alternative to `column_list`) |
|
|
92
|
+
| `column_labels` | Display name overrides, e.g. `{"customer_id": "Customer"}` |
|
|
93
|
+
| `column_formatters` | `{col: fn(value, obj) -> html_string}` |
|
|
94
|
+
| `column_searchable` | Columns to search (defaults to all String columns) |
|
|
95
|
+
| `column_sortable` | Columns that can be sorted |
|
|
96
|
+
| `form_columns` | Editable fields (defaults to all except `id`) |
|
|
97
|
+
| `form_sections` | Accordion groups: `{"Section": ["field1", "field2"]}` |
|
|
98
|
+
| `form_widget_overrides` | Per-field HTMX attrs or select choices |
|
|
99
|
+
| `row_actions` | Custom action buttons per row |
|
|
100
|
+
| `htmx_columns` | Auto-polling cells: `{"field": {"url": "...", "trigger": "every 3s"}}` |
|
|
101
|
+
| `page_size` | Records per page (default 20) |
|
|
102
|
+
| `can_create` / `can_edit` / `can_delete` | Permission flags |
|
|
103
|
+
|
|
104
|
+
## Custom Endpoints
|
|
105
|
+
|
|
106
|
+
Override `setup_endpoints()` on your CRUDView subclass to add custom routes:
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
class OrchestratorView(CRUDView):
|
|
110
|
+
model = Orchestrator
|
|
111
|
+
|
|
112
|
+
def setup_endpoints(self):
|
|
113
|
+
@self.router.post(f"/{self.name}/{{item_id}}/build")
|
|
114
|
+
async def build(request: Request, item_id: int, db=Depends(get_db)):
|
|
115
|
+
# custom logic
|
|
116
|
+
return HTMLResponse("", headers={"HX-Redirect": f"/{self.name}"})
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Admin Class
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
admin = Admin(
|
|
123
|
+
app,
|
|
124
|
+
title="My Admin", # Sidebar brand + page titles
|
|
125
|
+
static_url="/static/fasthx-admin", # Where package statics are mounted
|
|
126
|
+
public_pages={"login.html"}, # Pages that skip auth check
|
|
127
|
+
)
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
The `Admin` instance:
|
|
131
|
+
- Mounts built-in static files (CSS, JS)
|
|
132
|
+
- Sets up Jinja2 templates from the package
|
|
133
|
+
- Wraps template responses with nav context + auth check
|
|
134
|
+
- Provides `admin.templates` for rendering custom pages
|
|
135
|
+
|
|
136
|
+
## Model Metadata
|
|
137
|
+
|
|
138
|
+
Set these on your SQLAlchemy model classes for automatic sidebar grouping:
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
class MyModel(Base):
|
|
142
|
+
__tablename__ = "my_models"
|
|
143
|
+
__admin_category__ = "Section Name" # Sidebar group
|
|
144
|
+
__admin_icon__ = "table" # Bootstrap Icons name
|
|
145
|
+
__admin_name__ = "My Models" # Display label
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Environment Variables
|
|
149
|
+
|
|
150
|
+
| Variable | Purpose | Default |
|
|
151
|
+
|---|---|---|
|
|
152
|
+
| `AUTH_DISABLED` | Bypass authentication (`1`/`true`/`yes`) | disabled |
|
|
153
|
+
| `SESSION_SECRET` | Session signing key | (set in your app) |
|
|
154
|
+
| `OIDC_SECRETS` | Path to `client_secrets.json` | `./client_secrets.json` |
|
|
155
|
+
|
|
156
|
+
## Running the Demo
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
cd examples/demo
|
|
160
|
+
pip install -e ../..
|
|
161
|
+
pip install uvicorn[standard]
|
|
162
|
+
AUTH_DISABLED=1 uvicorn app:app --reload
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Open http://127.0.0.1:8000
|