fastapi-panel 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.
- fastapi_panel-0.1.0/.github/ISSUE_TEMPLATE/bug_report.yml +28 -0
- fastapi_panel-0.1.0/.github/ISSUE_TEMPLATE/feature_request.yml +18 -0
- fastapi_panel-0.1.0/.github/dependabot.yml +12 -0
- fastapi_panel-0.1.0/.github/pull_request_template.md +10 -0
- fastapi_panel-0.1.0/.github/workflows/ci.yml +45 -0
- fastapi_panel-0.1.0/.github/workflows/publish.yml +21 -0
- fastapi_panel-0.1.0/.gitignore +15 -0
- fastapi_panel-0.1.0/CHANGELOG.md +29 -0
- fastapi_panel-0.1.0/CODE_OF_CONDUCT.md +7 -0
- fastapi_panel-0.1.0/CONTRIBUTING.md +56 -0
- fastapi_panel-0.1.0/LICENSE +21 -0
- fastapi_panel-0.1.0/PKG-INFO +151 -0
- fastapi_panel-0.1.0/README.md +118 -0
- fastapi_panel-0.1.0/SECURITY.md +13 -0
- fastapi_panel-0.1.0/docs/auth-and-permissions.md +94 -0
- fastapi_panel-0.1.0/docs/customizing.md +147 -0
- fastapi_panel-0.1.0/docs/getting-started.md +119 -0
- fastapi_panel-0.1.0/docs/model-admin.md +264 -0
- fastapi_panel-0.1.0/docs/screenshots/changelist-dark.png +0 -0
- fastapi_panel-0.1.0/docs/screenshots/changelist.png +0 -0
- fastapi_panel-0.1.0/docs/screenshots/dashboard.png +0 -0
- fastapi_panel-0.1.0/docs/screenshots/delete.png +0 -0
- fastapi_panel-0.1.0/docs/screenshots/deleted.png +0 -0
- fastapi_panel-0.1.0/docs/screenshots/history.png +0 -0
- fastapi_panel-0.1.0/docs/screenshots/import-preview.png +0 -0
- fastapi_panel-0.1.0/docs/screenshots/login.png +0 -0
- fastapi_panel-0.1.0/docs/screenshots/mobile.png +0 -0
- fastapi_panel-0.1.0/docs/screenshots/order-inline.png +0 -0
- fastapi_panel-0.1.0/docs/screenshots/product-form.png +0 -0
- fastapi_panel-0.1.0/docs/screenshots/related-popup.png +0 -0
- fastapi_panel-0.1.0/docs/security.md +57 -0
- fastapi_panel-0.1.0/examples/shop/README.md +18 -0
- fastapi_panel-0.1.0/examples/shop/admin.py +141 -0
- fastapi_panel-0.1.0/examples/shop/main.py +121 -0
- fastapi_panel-0.1.0/examples/shop/models.py +132 -0
- fastapi_panel-0.1.0/pyproject.toml +76 -0
- fastapi_panel-0.1.0/scripts/screenshots.py +251 -0
- fastapi_panel-0.1.0/src/fastapi_panel/__init__.py +77 -0
- fastapi_panel-0.1.0/src/fastapi_panel/__main__.py +97 -0
- fastapi_panel-0.1.0/src/fastapi_panel/auth.py +189 -0
- fastapi_panel-0.1.0/src/fastapi_panel/db.py +41 -0
- fastapi_panel-0.1.0/src/fastapi_panel/filters.py +215 -0
- fastapi_panel-0.1.0/src/fastapi_panel/forms.py +733 -0
- fastapi_panel-0.1.0/src/fastapi_panel/imports.py +264 -0
- fastapi_panel-0.1.0/src/fastapi_panel/introspect.py +111 -0
- fastapi_panel-0.1.0/src/fastapi_panel/log.py +52 -0
- fastapi_panel-0.1.0/src/fastapi_panel/options.py +717 -0
- fastapi_panel-0.1.0/src/fastapi_panel/py.typed +0 -0
- fastapi_panel-0.1.0/src/fastapi_panel/security.py +139 -0
- fastapi_panel-0.1.0/src/fastapi_panel/site.py +602 -0
- fastapi_panel-0.1.0/src/fastapi_panel/static/panel.css +420 -0
- fastapi_panel-0.1.0/src/fastapi_panel/static/panel.js +404 -0
- fastapi_panel-0.1.0/src/fastapi_panel/static/theme.js +5 -0
- fastapi_panel-0.1.0/src/fastapi_panel/storage.py +38 -0
- fastapi_panel-0.1.0/src/fastapi_panel/templates/fastapi_panel/base.html +76 -0
- fastapi_panel-0.1.0/src/fastapi_panel/templates/fastapi_panel/delete.html +25 -0
- fastapi_panel-0.1.0/src/fastapi_panel/templates/fastapi_panel/error.html +10 -0
- fastapi_panel-0.1.0/src/fastapi_panel/templates/fastapi_panel/form.html +113 -0
- fastapi_panel-0.1.0/src/fastapi_panel/templates/fastapi_panel/history.html +23 -0
- fastapi_panel-0.1.0/src/fastapi_panel/templates/fastapi_panel/import.html +76 -0
- fastapi_panel-0.1.0/src/fastapi_panel/templates/fastapi_panel/index.html +43 -0
- fastapi_panel-0.1.0/src/fastapi_panel/templates/fastapi_panel/list.html +131 -0
- fastapi_panel-0.1.0/src/fastapi_panel/templates/fastapi_panel/login.html +15 -0
- fastapi_panel-0.1.0/src/fastapi_panel/templates/fastapi_panel/password.html +17 -0
- fastapi_panel-0.1.0/src/fastapi_panel/templates/fastapi_panel/popup_done.html +4 -0
- fastapi_panel-0.1.0/src/fastapi_panel/templates/fastapi_panel/widgets.html +74 -0
- fastapi_panel-0.1.0/src/fastapi_panel/views.py +710 -0
- fastapi_panel-0.1.0/tests/conftest.py +275 -0
- fastapi_panel-0.1.0/tests/test_async_and_sqlmodel.py +127 -0
- fastapi_panel-0.1.0/tests/test_csv_import.py +224 -0
- fastapi_panel-0.1.0/tests/test_extending.py +212 -0
- fastapi_panel-0.1.0/tests/test_files.py +288 -0
- fastapi_panel-0.1.0/tests/test_panel.py +352 -0
- fastapi_panel-0.1.0/tests/test_related_popup.py +66 -0
- fastapi_panel-0.1.0/tests/test_security.py +277 -0
- fastapi_panel-0.1.0/tests/test_soft_delete.py +189 -0
- fastapi_panel-0.1.0/tests/test_timezone_and_cli.py +156 -0
- fastapi_panel-0.1.0/uv.lock +1023 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
name: Bug report
|
|
2
|
+
description: Something doesn't work as documented
|
|
3
|
+
labels: [bug]
|
|
4
|
+
body:
|
|
5
|
+
- type: markdown
|
|
6
|
+
attributes:
|
|
7
|
+
value: For security issues, please use a [private advisory](https://github.com/zaman-labs/fastapi-panel/security/advisories/new) instead.
|
|
8
|
+
- type: textarea
|
|
9
|
+
id: what
|
|
10
|
+
attributes:
|
|
11
|
+
label: What happened, and what did you expect?
|
|
12
|
+
validations:
|
|
13
|
+
required: true
|
|
14
|
+
- type: textarea
|
|
15
|
+
id: reproduce
|
|
16
|
+
attributes:
|
|
17
|
+
label: How to reproduce
|
|
18
|
+
description: A small model and ModelAdmin help the most.
|
|
19
|
+
render: python
|
|
20
|
+
validations:
|
|
21
|
+
required: true
|
|
22
|
+
- type: input
|
|
23
|
+
id: versions
|
|
24
|
+
attributes:
|
|
25
|
+
label: Versions
|
|
26
|
+
placeholder: fastapi-panel 0.1.0, SQLAlchemy 2.0, FastAPI 0.115, Python 3.12, PostgreSQL 16
|
|
27
|
+
validations:
|
|
28
|
+
required: true
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
name: Feature request
|
|
2
|
+
description: Suggest something new
|
|
3
|
+
labels: [enhancement]
|
|
4
|
+
body:
|
|
5
|
+
- type: textarea
|
|
6
|
+
id: problem
|
|
7
|
+
attributes:
|
|
8
|
+
label: What are you trying to do?
|
|
9
|
+
description: Describe the problem, not only the solution.
|
|
10
|
+
validations:
|
|
11
|
+
required: true
|
|
12
|
+
- type: textarea
|
|
13
|
+
id: solution
|
|
14
|
+
attributes:
|
|
15
|
+
label: What would you like to happen?
|
|
16
|
+
description: If another admin tool has this, tell us how it works there.
|
|
17
|
+
validations:
|
|
18
|
+
required: true
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
## What and why
|
|
2
|
+
|
|
3
|
+
<!-- What does this change, and what problem does it solve? Link the issue if there is one. -->
|
|
4
|
+
|
|
5
|
+
## Checklist
|
|
6
|
+
|
|
7
|
+
- [ ] Targets the `develop` branch
|
|
8
|
+
- [ ] Tests added or updated
|
|
9
|
+
- [ ] `uv run ruff format . && uv run ruff check . && uv run pytest` passes
|
|
10
|
+
- [ ] Docs and `CHANGELOG.md` updated if users will notice
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main, develop]
|
|
6
|
+
pull_request:
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
lint:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
- uses: astral-sh/setup-uv@v5
|
|
18
|
+
- run: uv sync
|
|
19
|
+
- run: uv run ruff format --check .
|
|
20
|
+
- run: uv run ruff check .
|
|
21
|
+
|
|
22
|
+
test:
|
|
23
|
+
runs-on: ubuntu-latest
|
|
24
|
+
strategy:
|
|
25
|
+
fail-fast: false
|
|
26
|
+
matrix:
|
|
27
|
+
python: ["3.10", "3.11", "3.12", "3.13"]
|
|
28
|
+
steps:
|
|
29
|
+
- uses: actions/checkout@v4
|
|
30
|
+
- uses: astral-sh/setup-uv@v5
|
|
31
|
+
with:
|
|
32
|
+
python-version: ${{ matrix.python }}
|
|
33
|
+
- run: uv sync
|
|
34
|
+
- run: uv run pytest
|
|
35
|
+
|
|
36
|
+
build:
|
|
37
|
+
runs-on: ubuntu-latest
|
|
38
|
+
steps:
|
|
39
|
+
- uses: actions/checkout@v4
|
|
40
|
+
- uses: astral-sh/setup-uv@v5
|
|
41
|
+
- run: uv build
|
|
42
|
+
- name: Templates and static files are in the wheel
|
|
43
|
+
run: |
|
|
44
|
+
unzip -l dist/*.whl | grep -q 'templates/fastapi_panel/base.html'
|
|
45
|
+
unzip -l dist/*.whl | grep -q 'static/panel.css'
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
publish:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
environment: pypi
|
|
15
|
+
permissions:
|
|
16
|
+
id-token: write
|
|
17
|
+
steps:
|
|
18
|
+
- uses: actions/checkout@v4
|
|
19
|
+
- uses: astral-sh/setup-uv@v5
|
|
20
|
+
- run: uv build
|
|
21
|
+
- run: uv publish
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes are listed here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and versions follow [Semantic Versioning](https://semver.org/).
|
|
4
|
+
|
|
5
|
+
## [Unreleased]
|
|
6
|
+
|
|
7
|
+
## [0.1.0] - 2026-11-04
|
|
8
|
+
|
|
9
|
+
First release.
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- `AdminSite`, `ModelAdmin`, `register` and `autodiscover`, with startup checks for configuration mistakes
|
|
14
|
+
- List pages with sorting, search, filters, date drill-down, pagination, list editing, bulk actions and CSV export
|
|
15
|
+
- Forms generated from SQLAlchemy models, with fieldsets, read-only fields, validation hooks and Pydantic schemas
|
|
16
|
+
- Foreign key dropdowns and autocomplete, many-to-many pickers, tabular and stacked inlines
|
|
17
|
+
- `ModelAuth`, `StaticAuth`, and an `AuthBackend` base class for your own
|
|
18
|
+
- Per-model, per-action and row-level permissions
|
|
19
|
+
- Audit log and per-record history
|
|
20
|
+
- CSRF protection, signed sessions, login throttling, a strict Content-Security-Policy
|
|
21
|
+
- Sync and async engines, SQLModel, composite primary keys
|
|
22
|
+
- Dark mode and a responsive layout
|
|
23
|
+
- `FileField`, `ImageField`, `Storage` and `LocalStorage` for uploads, with previews, thumbnails and login-protected links
|
|
24
|
+
- A **+** button next to relation fields, to create a related record in a dialog and select it
|
|
25
|
+
- CSV import with `csv_import`: a preview first, updates through a primary key column, all or nothing
|
|
26
|
+
- `soft_delete_field`, with a Deleted page to restore rows or remove them for good
|
|
27
|
+
- `python -m fastapi_panel createuser`
|
|
28
|
+
- `AdminSite(timezone=...)` and `naive_utc`
|
|
29
|
+
- Collapsible sidebar groups
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Code of Conduct
|
|
2
|
+
|
|
3
|
+
This project follows the [Contributor Covenant, version 2.1](https://www.contributor-covenant.org/version/2/1/code_of_conduct/).
|
|
4
|
+
|
|
5
|
+
In short: be respectful, assume good intent, and keep criticism about the work. Harassment and personal attacks aren't tolerated in any project space.
|
|
6
|
+
|
|
7
|
+
To report unacceptable behavior, contact the maintainers privately through the [zaman-labs organization](https://github.com/zaman-labs). Reports are handled in confidence.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Bug reports, ideas and pull requests are all welcome. For anything big, open an issue first so we can agree on the approach.
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
You need [uv](https://docs.astral.sh/uv/).
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
git clone https://github.com/zaman-labs/fastapi-panel
|
|
11
|
+
cd fastapi-panel
|
|
12
|
+
uv sync
|
|
13
|
+
uv run pytest
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Before pushing:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
uv run ruff format .
|
|
20
|
+
uv run ruff check .
|
|
21
|
+
uv run pytest
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
To see your changes in a browser, run the [example shop](examples/shop). To check the interface end to end and refresh the screenshots, keep the example running on port 8765 and run:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
uv run --with playwright python scripts/screenshots.py
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Branches
|
|
31
|
+
|
|
32
|
+
- `main` is what's released, and always stable.
|
|
33
|
+
- `develop` is where work comes together. Open pull requests against it.
|
|
34
|
+
- Name your branch `feature/<name>`, `fix/<name>` or `docs/<name>`.
|
|
35
|
+
|
|
36
|
+
To release, merge `develop` into `main`, bump the version in `src/fastapi_panel/__init__.py`, update `CHANGELOG.md`, and publish a GitHub release tagged `vX.Y.Z`. That triggers the upload to PyPI.
|
|
37
|
+
|
|
38
|
+
## Commits
|
|
39
|
+
|
|
40
|
+
Use [Conventional Commits](https://www.conventionalcommits.org/): `feat: add radio fields`, `fix: keep filters after saving`, `docs: explain inlines`. Keep each commit about one thing.
|
|
41
|
+
|
|
42
|
+
## What we look for
|
|
43
|
+
|
|
44
|
+
- **Small and readable.** This library should stay easy to read in an afternoon. Prefer fewer options and plain code over flexibility nobody needs yet.
|
|
45
|
+
- **Consistent.** New options follow the naming of the existing ones.
|
|
46
|
+
- **Tested.** New behavior and bug fixes come with a test. Security behavior always does.
|
|
47
|
+
- **Secure by default.** No inline scripts or styles, and no new way to write a field that isn't on the form. Anything that touches login, sessions or permissions gets extra attention.
|
|
48
|
+
- **No new dependencies** unless there's really no other way.
|
|
49
|
+
|
|
50
|
+
## Reporting security issues
|
|
51
|
+
|
|
52
|
+
Please don't open a public issue. See [SECURITY.md](SECURITY.md).
|
|
53
|
+
|
|
54
|
+
## Code of conduct
|
|
55
|
+
|
|
56
|
+
Everyone taking part agrees to our [Code of Conduct](CODE_OF_CONDUCT.md).
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Zaman Labs
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: fastapi-panel
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: An admin panel for FastAPI and SQLAlchemy. Register a model, get a secure back office.
|
|
5
|
+
Project-URL: Homepage, https://github.com/zaman-labs/fastapi-panel
|
|
6
|
+
Project-URL: Documentation, https://github.com/zaman-labs/fastapi-panel/tree/main/docs
|
|
7
|
+
Project-URL: Changelog, https://github.com/zaman-labs/fastapi-panel/blob/main/CHANGELOG.md
|
|
8
|
+
Project-URL: Issues, https://github.com/zaman-labs/fastapi-panel/issues
|
|
9
|
+
Author: Zaman Labs
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: admin,admin-panel,backoffice,crud,fastapi,sqlalchemy,sqlmodel
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Environment :: Web Environment
|
|
15
|
+
Classifier: Framework :: AsyncIO
|
|
16
|
+
Classifier: Framework :: FastAPI
|
|
17
|
+
Classifier: Intended Audience :: Developers
|
|
18
|
+
Classifier: Operating System :: OS Independent
|
|
19
|
+
Classifier: Programming Language :: Python :: 3
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
24
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
|
25
|
+
Classifier: Typing :: Typed
|
|
26
|
+
Requires-Python: >=3.10
|
|
27
|
+
Requires-Dist: fastapi>=0.110
|
|
28
|
+
Requires-Dist: jinja2>=3.1
|
|
29
|
+
Requires-Dist: python-multipart>=0.0.18
|
|
30
|
+
Requires-Dist: sqlalchemy[asyncio]>=2.0.20
|
|
31
|
+
Requires-Dist: starlette>=0.40
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
|
|
34
|
+
<h1 align="center">FastAPI Panel</h1>
|
|
35
|
+
|
|
36
|
+
<p align="center">
|
|
37
|
+
<strong>A modular admin panel for FastAPI.</strong><br>
|
|
38
|
+
Register a SQLAlchemy model, get a secure back office.
|
|
39
|
+
</p>
|
|
40
|
+
|
|
41
|
+
<p align="center">
|
|
42
|
+
<a href="https://github.com/zaman-labs/fastapi-panel/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/zaman-labs/fastapi-panel/actions/workflows/ci.yml/badge.svg"></a>
|
|
43
|
+
<a href="https://pypi.org/project/fastapi-panel/"><img alt="PyPI" src="https://img.shields.io/pypi/v/fastapi-panel"></a>
|
|
44
|
+
<img alt="Python 3.10+" src="https://img.shields.io/badge/python-3.10%2B-blue">
|
|
45
|
+
<a href="https://github.com/zaman-labs/fastapi-panel/blob/main/LICENSE"><img alt="MIT license" src="https://img.shields.io/badge/license-MIT-green"></a>
|
|
46
|
+
</p>
|
|
47
|
+
|
|
48
|
+

|
|
49
|
+
|
|
50
|
+
If you know `ModelAdmin`, you already know this library: `list_display`, `search_fields`, `list_filter`, `fieldsets`, `inlines`, actions and the rest work the way you expect. There is no frontend build, no Node and no extra service. It's server-rendered pages inside your FastAPI app.
|
|
51
|
+
|
|
52
|
+
## Install
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
pip install fastapi-panel
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Quick start
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
from fastapi import FastAPI
|
|
62
|
+
from fastapi_panel import ModelAdmin, ModelAuth, register, site
|
|
63
|
+
from sqlalchemy.ext.asyncio import create_async_engine
|
|
64
|
+
|
|
65
|
+
from myapp.models import Product, User
|
|
66
|
+
|
|
67
|
+
engine = create_async_engine("postgresql+asyncpg://...")
|
|
68
|
+
app = FastAPI()
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@register(Product)
|
|
72
|
+
class ProductAdmin(ModelAdmin):
|
|
73
|
+
list_display = ("name", "category", "price", "is_published")
|
|
74
|
+
list_filter = ("is_published", "category")
|
|
75
|
+
search_fields = ("name", "category__name")
|
|
76
|
+
ordering = ("-created_at",)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
site.mount(
|
|
80
|
+
app,
|
|
81
|
+
engine=engine,
|
|
82
|
+
auth=ModelAuth(User, superuser_field="is_superuser"),
|
|
83
|
+
secret_key=settings.PANEL_SECRET_KEY,
|
|
84
|
+
)
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Open `/admin`, log in, and you have a list page, add and edit forms, delete confirmation, bulk actions, CSV export and a change history. See the [getting started guide](https://github.com/zaman-labs/fastapi-panel/blob/main/docs/getting-started.md) for creating your first user with `python -m fastapi_panel createuser` and setting up migrations.
|
|
88
|
+
|
|
89
|
+
Want to try it first? The [example shop](https://github.com/zaman-labs/fastapi-panel/tree/main/examples/shop) runs in one command.
|
|
90
|
+
|
|
91
|
+
## What you get
|
|
92
|
+
|
|
93
|
+
| | |
|
|
94
|
+
|---|---|
|
|
95
|
+
| **Change lists** | Columns, computed columns, sorting, search across relations, sidebar filters, date drill-down, pagination, editing in the list |
|
|
96
|
+
| **Forms** | Built from your models, with fieldsets, read-only fields, prefilled slugs, validation hooks, Pydantic schemas, foreign key autocomplete, many-to-many pickers, and a **+** button to create a related record on the spot |
|
|
97
|
+
| **Files and images** | Uploads with previews and thumbnails, stored on disk or anywhere else through a small storage class |
|
|
98
|
+
| **Import and restore** | CSV import with a preview before anything is saved, and a Deleted page to restore soft-deleted records |
|
|
99
|
+
| **Inlines** | Tabular and stacked, with add, edit and delete on the parent's page |
|
|
100
|
+
| **Actions** | Your own bulk actions, plus delete with a summary of what cascades, CSV export and "select all across pages" |
|
|
101
|
+
| **Permissions** | Per model and per action, row-level through `get_queryset` and `has_*_permission` |
|
|
102
|
+
| **Audit log** | Who changed which fields and when, shown on each record's history page |
|
|
103
|
+
| **Any SQLAlchemy setup** | Sync and async engines, SQLModel, composite primary keys |
|
|
104
|
+
| **Timezones** | Show and enter times in your site's timezone, while the database stays in UTC |
|
|
105
|
+
| **Interface** | Dark mode, works on a phone, keyboard friendly, no external assets |
|
|
106
|
+
|
|
107
|
+
<table>
|
|
108
|
+
<tr>
|
|
109
|
+
<td><img alt="An order with its items edited inline" src="https://raw.githubusercontent.com/zaman-labs/fastapi-panel/main/docs/screenshots/order-inline.png"></td>
|
|
110
|
+
<td><img alt="A product form with fieldsets and a many-to-many picker" src="https://raw.githubusercontent.com/zaman-labs/fastapi-panel/main/docs/screenshots/product-form.png"></td>
|
|
111
|
+
</tr>
|
|
112
|
+
<tr>
|
|
113
|
+
<td><img alt="The dashboard with recent actions" src="https://raw.githubusercontent.com/zaman-labs/fastapi-panel/main/docs/screenshots/dashboard.png"></td>
|
|
114
|
+
<td><img alt="The product list in dark mode" src="https://raw.githubusercontent.com/zaman-labs/fastapi-panel/main/docs/screenshots/changelist-dark.png"></td>
|
|
115
|
+
</tr>
|
|
116
|
+
<tr>
|
|
117
|
+
<td><img alt="Adding a category in a dialog from the product form" src="https://raw.githubusercontent.com/zaman-labs/fastapi-panel/main/docs/screenshots/related-popup.png"></td>
|
|
118
|
+
<td><img alt="A CSV import preview before anything is saved" src="https://raw.githubusercontent.com/zaman-labs/fastapi-panel/main/docs/screenshots/import-preview.png"></td>
|
|
119
|
+
</tr>
|
|
120
|
+
</table>
|
|
121
|
+
|
|
122
|
+
## Security
|
|
123
|
+
|
|
124
|
+
An admin panel can reach all your data, so these are on by default and have no off switch to forget:
|
|
125
|
+
|
|
126
|
+
- CSRF tokens and `Origin` checks on every POST
|
|
127
|
+
- Signed `HttpOnly`, `SameSite=Lax` session cookies, `Secure` on HTTPS, ended by a password change or by deactivating the user
|
|
128
|
+
- Login throttling, no way to tell a wrong username from a wrong password, scrypt password hashing
|
|
129
|
+
- A strict Content-Security-Policy with no inline scripts or styles, `X-Frame-Options: DENY`, `no-store` caching
|
|
130
|
+
- Columns named like `password`, `token` or `secret` stay hidden unless you ask for them, and the audit log stores field names, never values
|
|
131
|
+
- Only fields on the form can be written, and sort and filter parameters are allowlisted
|
|
132
|
+
- Spreadsheet formulas are neutralised in CSV exports
|
|
133
|
+
- Uploads get random names, images are checked against their contents, and files are only served behind login
|
|
134
|
+
|
|
135
|
+
The panel refuses to start without authentication or with a short secret key. Read [the security guide](https://github.com/zaman-labs/fastapi-panel/blob/main/docs/security.md) for what's left to you.
|
|
136
|
+
|
|
137
|
+
## Documentation
|
|
138
|
+
|
|
139
|
+
- [Getting started](https://github.com/zaman-labs/fastapi-panel/blob/main/docs/getting-started.md)
|
|
140
|
+
- [ModelAdmin reference](https://github.com/zaman-labs/fastapi-panel/blob/main/docs/model-admin.md): lists, forms, inlines, actions, filters
|
|
141
|
+
- [Authentication and permissions](https://github.com/zaman-labs/fastapi-panel/blob/main/docs/auth-and-permissions.md)
|
|
142
|
+
- [Modular projects and customizing](https://github.com/zaman-labs/fastapi-panel/blob/main/docs/customizing.md): autodiscovery, multiple sites, custom pages, templates, themes
|
|
143
|
+
- [Security](https://github.com/zaman-labs/fastapi-panel/blob/main/docs/security.md)
|
|
144
|
+
|
|
145
|
+
## Roadmap
|
|
146
|
+
|
|
147
|
+
Translations, a saved-filters UI, password reset by email, two-factor login, and a Redis option for login throttling across workers. Ideas and pull requests are welcome, see [CONTRIBUTING](https://github.com/zaman-labs/fastapi-panel/blob/main/CONTRIBUTING.md).
|
|
148
|
+
|
|
149
|
+
## License
|
|
150
|
+
|
|
151
|
+
[MIT](https://github.com/zaman-labs/fastapi-panel/blob/main/LICENSE)
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
<h1 align="center">FastAPI Panel</h1>
|
|
2
|
+
|
|
3
|
+
<p align="center">
|
|
4
|
+
<strong>A modular admin panel for FastAPI.</strong><br>
|
|
5
|
+
Register a SQLAlchemy model, get a secure back office.
|
|
6
|
+
</p>
|
|
7
|
+
|
|
8
|
+
<p align="center">
|
|
9
|
+
<a href="https://github.com/zaman-labs/fastapi-panel/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/zaman-labs/fastapi-panel/actions/workflows/ci.yml/badge.svg"></a>
|
|
10
|
+
<a href="https://pypi.org/project/fastapi-panel/"><img alt="PyPI" src="https://img.shields.io/pypi/v/fastapi-panel"></a>
|
|
11
|
+
<img alt="Python 3.10+" src="https://img.shields.io/badge/python-3.10%2B-blue">
|
|
12
|
+
<a href="https://github.com/zaman-labs/fastapi-panel/blob/main/LICENSE"><img alt="MIT license" src="https://img.shields.io/badge/license-MIT-green"></a>
|
|
13
|
+
</p>
|
|
14
|
+
|
|
15
|
+

|
|
16
|
+
|
|
17
|
+
If you know `ModelAdmin`, you already know this library: `list_display`, `search_fields`, `list_filter`, `fieldsets`, `inlines`, actions and the rest work the way you expect. There is no frontend build, no Node and no extra service. It's server-rendered pages inside your FastAPI app.
|
|
18
|
+
|
|
19
|
+
## Install
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pip install fastapi-panel
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Quick start
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
from fastapi import FastAPI
|
|
29
|
+
from fastapi_panel import ModelAdmin, ModelAuth, register, site
|
|
30
|
+
from sqlalchemy.ext.asyncio import create_async_engine
|
|
31
|
+
|
|
32
|
+
from myapp.models import Product, User
|
|
33
|
+
|
|
34
|
+
engine = create_async_engine("postgresql+asyncpg://...")
|
|
35
|
+
app = FastAPI()
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@register(Product)
|
|
39
|
+
class ProductAdmin(ModelAdmin):
|
|
40
|
+
list_display = ("name", "category", "price", "is_published")
|
|
41
|
+
list_filter = ("is_published", "category")
|
|
42
|
+
search_fields = ("name", "category__name")
|
|
43
|
+
ordering = ("-created_at",)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
site.mount(
|
|
47
|
+
app,
|
|
48
|
+
engine=engine,
|
|
49
|
+
auth=ModelAuth(User, superuser_field="is_superuser"),
|
|
50
|
+
secret_key=settings.PANEL_SECRET_KEY,
|
|
51
|
+
)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Open `/admin`, log in, and you have a list page, add and edit forms, delete confirmation, bulk actions, CSV export and a change history. See the [getting started guide](https://github.com/zaman-labs/fastapi-panel/blob/main/docs/getting-started.md) for creating your first user with `python -m fastapi_panel createuser` and setting up migrations.
|
|
55
|
+
|
|
56
|
+
Want to try it first? The [example shop](https://github.com/zaman-labs/fastapi-panel/tree/main/examples/shop) runs in one command.
|
|
57
|
+
|
|
58
|
+
## What you get
|
|
59
|
+
|
|
60
|
+
| | |
|
|
61
|
+
|---|---|
|
|
62
|
+
| **Change lists** | Columns, computed columns, sorting, search across relations, sidebar filters, date drill-down, pagination, editing in the list |
|
|
63
|
+
| **Forms** | Built from your models, with fieldsets, read-only fields, prefilled slugs, validation hooks, Pydantic schemas, foreign key autocomplete, many-to-many pickers, and a **+** button to create a related record on the spot |
|
|
64
|
+
| **Files and images** | Uploads with previews and thumbnails, stored on disk or anywhere else through a small storage class |
|
|
65
|
+
| **Import and restore** | CSV import with a preview before anything is saved, and a Deleted page to restore soft-deleted records |
|
|
66
|
+
| **Inlines** | Tabular and stacked, with add, edit and delete on the parent's page |
|
|
67
|
+
| **Actions** | Your own bulk actions, plus delete with a summary of what cascades, CSV export and "select all across pages" |
|
|
68
|
+
| **Permissions** | Per model and per action, row-level through `get_queryset` and `has_*_permission` |
|
|
69
|
+
| **Audit log** | Who changed which fields and when, shown on each record's history page |
|
|
70
|
+
| **Any SQLAlchemy setup** | Sync and async engines, SQLModel, composite primary keys |
|
|
71
|
+
| **Timezones** | Show and enter times in your site's timezone, while the database stays in UTC |
|
|
72
|
+
| **Interface** | Dark mode, works on a phone, keyboard friendly, no external assets |
|
|
73
|
+
|
|
74
|
+
<table>
|
|
75
|
+
<tr>
|
|
76
|
+
<td><img alt="An order with its items edited inline" src="https://raw.githubusercontent.com/zaman-labs/fastapi-panel/main/docs/screenshots/order-inline.png"></td>
|
|
77
|
+
<td><img alt="A product form with fieldsets and a many-to-many picker" src="https://raw.githubusercontent.com/zaman-labs/fastapi-panel/main/docs/screenshots/product-form.png"></td>
|
|
78
|
+
</tr>
|
|
79
|
+
<tr>
|
|
80
|
+
<td><img alt="The dashboard with recent actions" src="https://raw.githubusercontent.com/zaman-labs/fastapi-panel/main/docs/screenshots/dashboard.png"></td>
|
|
81
|
+
<td><img alt="The product list in dark mode" src="https://raw.githubusercontent.com/zaman-labs/fastapi-panel/main/docs/screenshots/changelist-dark.png"></td>
|
|
82
|
+
</tr>
|
|
83
|
+
<tr>
|
|
84
|
+
<td><img alt="Adding a category in a dialog from the product form" src="https://raw.githubusercontent.com/zaman-labs/fastapi-panel/main/docs/screenshots/related-popup.png"></td>
|
|
85
|
+
<td><img alt="A CSV import preview before anything is saved" src="https://raw.githubusercontent.com/zaman-labs/fastapi-panel/main/docs/screenshots/import-preview.png"></td>
|
|
86
|
+
</tr>
|
|
87
|
+
</table>
|
|
88
|
+
|
|
89
|
+
## Security
|
|
90
|
+
|
|
91
|
+
An admin panel can reach all your data, so these are on by default and have no off switch to forget:
|
|
92
|
+
|
|
93
|
+
- CSRF tokens and `Origin` checks on every POST
|
|
94
|
+
- Signed `HttpOnly`, `SameSite=Lax` session cookies, `Secure` on HTTPS, ended by a password change or by deactivating the user
|
|
95
|
+
- Login throttling, no way to tell a wrong username from a wrong password, scrypt password hashing
|
|
96
|
+
- A strict Content-Security-Policy with no inline scripts or styles, `X-Frame-Options: DENY`, `no-store` caching
|
|
97
|
+
- Columns named like `password`, `token` or `secret` stay hidden unless you ask for them, and the audit log stores field names, never values
|
|
98
|
+
- Only fields on the form can be written, and sort and filter parameters are allowlisted
|
|
99
|
+
- Spreadsheet formulas are neutralised in CSV exports
|
|
100
|
+
- Uploads get random names, images are checked against their contents, and files are only served behind login
|
|
101
|
+
|
|
102
|
+
The panel refuses to start without authentication or with a short secret key. Read [the security guide](https://github.com/zaman-labs/fastapi-panel/blob/main/docs/security.md) for what's left to you.
|
|
103
|
+
|
|
104
|
+
## Documentation
|
|
105
|
+
|
|
106
|
+
- [Getting started](https://github.com/zaman-labs/fastapi-panel/blob/main/docs/getting-started.md)
|
|
107
|
+
- [ModelAdmin reference](https://github.com/zaman-labs/fastapi-panel/blob/main/docs/model-admin.md): lists, forms, inlines, actions, filters
|
|
108
|
+
- [Authentication and permissions](https://github.com/zaman-labs/fastapi-panel/blob/main/docs/auth-and-permissions.md)
|
|
109
|
+
- [Modular projects and customizing](https://github.com/zaman-labs/fastapi-panel/blob/main/docs/customizing.md): autodiscovery, multiple sites, custom pages, templates, themes
|
|
110
|
+
- [Security](https://github.com/zaman-labs/fastapi-panel/blob/main/docs/security.md)
|
|
111
|
+
|
|
112
|
+
## Roadmap
|
|
113
|
+
|
|
114
|
+
Translations, a saved-filters UI, password reset by email, two-factor login, and a Redis option for login throttling across workers. Ideas and pull requests are welcome, see [CONTRIBUTING](https://github.com/zaman-labs/fastapi-panel/blob/main/CONTRIBUTING.md).
|
|
115
|
+
|
|
116
|
+
## License
|
|
117
|
+
|
|
118
|
+
[MIT](https://github.com/zaman-labs/fastapi-panel/blob/main/LICENSE)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Security policy
|
|
2
|
+
|
|
3
|
+
## Supported versions
|
|
4
|
+
|
|
5
|
+
Security fixes go into the latest release.
|
|
6
|
+
|
|
7
|
+
## Reporting a vulnerability
|
|
8
|
+
|
|
9
|
+
Please report it privately through [GitHub's advisory form](https://github.com/zaman-labs/fastapi-panel/security/advisories/new), not in a public issue.
|
|
10
|
+
|
|
11
|
+
Include what you found, how to reproduce it, and the impact you expect. You'll get a reply within a week, and we'll keep you updated until there's a fix. If you'd like credit, we'll name you in the advisory.
|
|
12
|
+
|
|
13
|
+
For how the panel protects itself and what's left to you, see the [security guide](docs/security.md).
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Authentication and permissions
|
|
2
|
+
|
|
3
|
+
`site.mount` requires an `auth` backend. There's no anonymous mode.
|
|
4
|
+
|
|
5
|
+
## ModelAuth
|
|
6
|
+
|
|
7
|
+
```python
|
|
8
|
+
ModelAuth(
|
|
9
|
+
User,
|
|
10
|
+
username_field="email",
|
|
11
|
+
password_field="hashed_password",
|
|
12
|
+
active_field="is_active", # None to skip
|
|
13
|
+
staff_field="is_staff", # None to skip
|
|
14
|
+
superuser_field="is_superuser",
|
|
15
|
+
permissions_field="permissions",
|
|
16
|
+
)
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
`active_field` and `staff_field` must exist on the model or be passed as `None`. That's deliberate. If your `users` table also holds customers, a missing staff check would let all of them log in, so the panel refuses to start until you decide.
|
|
20
|
+
|
|
21
|
+
## Permissions
|
|
22
|
+
|
|
23
|
+
Permissions are strings in the form `<slug>.<action>`, where the action is `view`, `add`, `change` or `delete`.
|
|
24
|
+
|
|
25
|
+
| Configuration | Who can do what |
|
|
26
|
+
|---|---|
|
|
27
|
+
| No `superuser_field`, no `permissions_field` | Everyone who can log in can do everything |
|
|
28
|
+
| `superuser_field` only | Superusers can do everything, other staff can do nothing |
|
|
29
|
+
| `permissions_field` | Superusers can do everything, others get what's in the list |
|
|
30
|
+
|
|
31
|
+
`permissions_field` points at something iterable on the user, usually a JSON column:
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
["product.view", "product.change", "order.*"]
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
`order.*` covers all actions on orders and `*` covers everything. Having `change` implies `view`.
|
|
38
|
+
|
|
39
|
+
For roles or groups, subclass the backend. Load what you need in `get_user`, because the database session is closed by the time `has_perm` runs:
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
class RoleAuth(ModelAuth):
|
|
43
|
+
def get_user(self, session, user_id):
|
|
44
|
+
user = super().get_user(session, user_id)
|
|
45
|
+
if user is not None:
|
|
46
|
+
user.granted = {p.code for role in user.roles for p in role.permissions}
|
|
47
|
+
return user
|
|
48
|
+
|
|
49
|
+
def has_perm(self, user, perm):
|
|
50
|
+
return user.is_superuser or perm in user.granted
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Per-model and per-row rules
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
class InvoiceAdmin(ModelAdmin):
|
|
57
|
+
def get_queryset(self, request):
|
|
58
|
+
user = request.state.user
|
|
59
|
+
stmt = super().get_queryset(request)
|
|
60
|
+
return stmt if user.is_superuser else stmt.where(Invoice.team_id == user.team_id)
|
|
61
|
+
|
|
62
|
+
def has_delete_permission(self, request, obj=None):
|
|
63
|
+
return obj is None or obj.status == "draft"
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
`get_queryset` is the safest place for row-level rules. Lists, edit pages, deletes, history, bulk actions, autocomplete and foreign key validation all go through it, and a row outside it returns 404.
|
|
67
|
+
|
|
68
|
+
Inline rows follow the parent's change permission by default. Override `has_add_permission`, `has_change_permission` and `has_delete_permission` on the inline to change that.
|
|
69
|
+
|
|
70
|
+
The current user is `request.state.user` in every hook and view.
|
|
71
|
+
|
|
72
|
+
## Your own backend
|
|
73
|
+
|
|
74
|
+
Single sign-on, LDAP or anything else: subclass `AuthBackend`.
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
from fastapi_panel import AuthBackend
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class LdapAuth(AuthBackend):
|
|
81
|
+
def find_user(self, session, username): ...
|
|
82
|
+
def get_user(self, session, user_id): ...
|
|
83
|
+
def check_password(self, user, password): ... # user may be None. Runs in a worker thread
|
|
84
|
+
def user_id(self, user): ...
|
|
85
|
+
def is_allowed(self, user): ...
|
|
86
|
+
def has_perm(self, user, perm): ...
|
|
87
|
+
def session_hash(self, user): ... # sessions end when this value changes
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Sessions
|
|
91
|
+
|
|
92
|
+
Sessions live in a signed cookie, so there's no session table. A session ends when it expires (`session_age`, 12 hours by default), on logout, when the password changes, or when the user is deactivated. The last two apply on the very next request.
|
|
93
|
+
|
|
94
|
+
Logout clears the cookie in that browser. A stolen cookie stays valid until one of the other conditions is met, so changing the password is how you revoke every session of one user, and changing `secret_key` revokes everyone's.
|