Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ jobs:
- name: Install dependencies
run: uv sync --all-extras
- name: Run tests with 100% coverage gate
run: uv run pytest -n 1 --cov=fastadmin --cov-report=term-missing --cov-report=xml -s tests
run: uv run pytest -n auto --cov=fastadmin --cov-report=term-missing --cov-report=xml tests
- name: Upload coverage to Codecov
if: matrix.python-version == '3.13'
uses: codecov/codecov-action@v5
Expand All @@ -74,6 +74,7 @@ jobs:
- "fastapi,tortoise-orm"
- "fastapi,pony"
- "fastapi,sqlalchemy"
- "fastapi,yara-orm"
- "django"
- "django,pony"
- "flask,sqlalchemy"
Expand Down
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@

All notable changes to FastAdmin are documented in this file.

## 0.7.0

- Add support for [Yara ORM](https://github.com/vsdudakov/yara-orm) — a fast,
async Python ORM with a Rust engine. New `YaraOrmModelAdmin` /
`YaraOrmInlineModelAdmin` admin classes and a `fastadmin[yara-orm]` extra, with
a runnable FastAPI example (`examples/fastapi_yaraorm`).
- Add a refresh button to the widget action results (toolbar and expand modal)
so results can be re-run in place without losing scroll position (#132).

## 0.6.0

Security hardening release. No changes to the documented public API, but
Expand Down
7 changes: 4 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,11 @@ format:
uv run ruff format $(LINT_PATHS)
make -C frontend fix

# Use -n 1: -n auto causes flaky failures with Django+SQLite (database is locked, 500s). conftest
# uses a per-worker DB and SQLite timeout when xdist is used, but parallel runs remain unreliable.
# -n auto runs the suite in parallel via pytest-xdist. Each worker gets its own
# SQLite file (Django/Pony) with a busy timeout, so there are no "database is
# locked" failures.
test:
uv run pytest -n 1 --cov=fastadmin --cov-report=term-missing --cov-report=xml -s tests
uv run pytest -n auto --cov=fastadmin --cov-report=term-missing --cov-report=xml tests
make -C frontend test

cov: test
Expand Down
13 changes: 10 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
**FastAdmin** is an easy-to-use **admin dashboard (admin panel) for FastAPI, Flask and
Django**, inspired by Django Admin. It gives your Python web application a
production-ready **CRUD admin interface** in minutes — on top of **Tortoise ORM,
Django ORM, SQLAlchemy or Pony ORM** — with authentication, filters, search,
Django ORM, SQLAlchemy, Pony ORM or [Yara ORM](https://github.com/vsdudakov/yara-orm)** —
with authentication, filters, search,
inline editing, file uploads, CSV/JSON export and dashboard charts out of the box.

FastAdmin is built with relationships in mind and admiration for Django Admin.
Expand All @@ -31,7 +32,8 @@ already know FastAdmin.
- **Any web framework** — mount as a FastAPI sub-app, a Flask blueprint or
Django urlpatterns.
- **Any ORM** — first-class admin classes for Tortoise ORM, Django ORM,
SQLAlchemy (async) and Pony ORM.
SQLAlchemy (async), Pony ORM and [Yara ORM](https://github.com/vsdudakov/yara-orm)
(a fast, Rust-engine async ORM).
- **Authentication & permissions** — pluggable sign-in against your own user
model, per-action permission hooks, request/user context in every admin method.
- **Rich form widgets** — 20+ antd-based widgets (rich text, JSON, async
Expand All @@ -56,6 +58,7 @@ pip install fastadmin[fastapi,django] # FastAPI with Django ORM
pip install fastadmin[fastapi,tortoise-orm] # FastAPI with Tortoise ORM
pip install fastadmin[fastapi,pony] # FastAPI with Pony ORM
pip install fastadmin[fastapi,sqlalchemy] # FastAPI with SQLAlchemy (includes greenlet)
pip install fastadmin[fastapi,yara-orm] # FastAPI with Yara ORM
pip install fastadmin[django] # Django with Django ORM
pip install fastadmin[django,pony] # Django with Pony ORM
pip install fastadmin[flask,sqlalchemy] # Flask with SQLAlchemy (includes greenlet)
Expand Down Expand Up @@ -147,11 +150,15 @@ Runnable example apps for every framework/ORM combination are in
## Why FastAdmin?

If you are looking for a **Django-Admin-like admin panel for FastAPI**, an
**admin interface for SQLAlchemy or Tortoise ORM**, or a lightweight
**admin interface for SQLAlchemy, Tortoise ORM or
[Yara ORM](https://github.com/vsdudakov/yara-orm)**, or a lightweight
**alternative to building a custom back office**, FastAdmin gives you a
batteries-included, themeable admin UI without code generation, without tying
your app to a specific framework, and without writing a single React component.

Pair it with [Yara ORM](https://github.com/vsdudakov/yara-orm) — our fast, async
Python ORM with a Rust engine — for a high-performance FastAPI + admin stack.

## Contributing

Contributions are welcome — see the
Expand Down
1 change: 1 addition & 0 deletions docs/api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ package.
| `DjangoModelAdmin` / `DjangoInlineModelAdmin` | Django ORM admins. |
| `SqlAlchemyModelAdmin` / `SqlAlchemyInlineModelAdmin` | SQLAlchemy (async) admins. |
| `PonyORMModelAdmin` / `PonyORMInlineModelAdmin` | Pony ORM admins. |
| `YaraOrmModelAdmin` / `YaraOrmInlineModelAdmin` | [Yara ORM](https://github.com/vsdudakov/yara-orm) admins. |

Attributes are documented in [Model admins](guides/model-admins.md) and
[Inline admins](guides/inline-admins.md).
Expand Down
3 changes: 3 additions & 0 deletions docs/getting-started/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Install the package with the extras matching your web framework and ORM:
pip install fastadmin[fastapi,tortoise-orm] # FastAPI with Tortoise ORM
pip install fastadmin[fastapi,pony] # FastAPI with Pony ORM
pip install fastadmin[fastapi,sqlalchemy] # FastAPI with SQLAlchemy (includes greenlet)
pip install fastadmin[fastapi,yara-orm] # FastAPI with Yara ORM
pip install fastadmin[django] # Django with Django ORM
pip install fastadmin[django,pony] # Django with Pony ORM
pip install fastadmin[flask,sqlalchemy] # Flask with SQLAlchemy (includes greenlet)
Expand All @@ -28,6 +29,7 @@ Install the package with the extras matching your web framework and ORM:
uv add 'fastadmin[fastapi,tortoise-orm]'
uv add 'fastadmin[fastapi,pony]'
uv add 'fastadmin[fastapi,sqlalchemy]'
uv add 'fastadmin[fastapi,yara-orm]'
uv add 'fastadmin[django]'
uv add 'fastadmin[django,pony]'
uv add 'fastadmin[flask,sqlalchemy]'
Expand All @@ -40,6 +42,7 @@ Install the package with the extras matching your web framework and ORM:
poetry add 'fastadmin[fastapi,tortoise-orm]'
poetry add 'fastadmin[fastapi,pony]'
poetry add 'fastadmin[fastapi,sqlalchemy]'
poetry add 'fastadmin[fastapi,yara-orm]'
poetry add 'fastadmin[django]'
poetry add 'fastadmin[django,pony]'
poetry add 'fastadmin[flask,sqlalchemy]'
Expand Down
9 changes: 8 additions & 1 deletion docs/guides/registering-models.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: Registering models
description: Register ORM models with FastAdmin — complete runnable examples for Tortoise ORM, Django ORM, SQLAlchemy and Pony ORM.
description: Register ORM models with FastAdmin — complete runnable examples for Tortoise ORM, Django ORM, SQLAlchemy, Pony ORM and Yara ORM.
---

# Registering models
Expand All @@ -14,6 +14,7 @@ The admin base class must match your ORM:
| Django ORM | `DjangoModelAdmin` | `DjangoInlineModelAdmin` |
| SQLAlchemy | `SqlAlchemyModelAdmin` | `SqlAlchemyInlineModelAdmin` |
| Pony ORM | `PonyORMModelAdmin` | `PonyORMInlineModelAdmin` |
| [Yara ORM](https://github.com/vsdudakov/yara-orm) | `YaraOrmModelAdmin` | `YaraOrmInlineModelAdmin` |

```python
from fastadmin import TortoiseModelAdmin, register
Expand Down Expand Up @@ -70,3 +71,9 @@ fields, uploads and dashboard widgets), authentication and app mounting.
```python
--8<-- "examples/fastapi_ponyorm/example.py"
```

=== "Yara ORM"

```python
--8<-- "examples/fastapi_yaraorm/example.py"
```
8 changes: 5 additions & 3 deletions docs/index.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: FastAdmin — Admin dashboard for FastAPI, Flask and Django
description: FastAdmin is an easy-to-use admin dashboard for FastAPI, Flask and Django, inspired by Django Admin. Works with Tortoise ORM, Django ORM, SQLAlchemy and Pony ORM.
description: FastAdmin is an easy-to-use admin dashboard for FastAPI, Flask and Django, inspired by Django Admin. Works with Tortoise ORM, Django ORM, SQLAlchemy, Pony ORM and Yara ORM.
---

# FastAdmin
Expand All @@ -16,7 +16,8 @@ description: FastAdmin is an easy-to-use admin dashboard for FastAPI, Flask and
FastAdmin is built with relationships in mind and admiration for Django Admin.
Its design focuses on making it as easy as possible to configure an admin
dashboard for **FastAPI**, **Flask** or **Django** on top of **Tortoise ORM**,
**Django ORM**, **SQLAlchemy** or **Pony ORM**. It aims to be minimal,
**Django ORM**, **SQLAlchemy**, **Pony ORM** or
**[Yara ORM](https://github.com/vsdudakov/yara-orm)**. It aims to be minimal,
functional and familiar.

![FastAdmin demo](assets/images/demo.gif)
Expand Down Expand Up @@ -56,7 +57,8 @@ app.mount("/admin", admin_app)
- :jigsaw: **Framework-agnostic** — mount it into FastAPI, register it as a
Flask blueprint or include it in Django urlpatterns.
- :file_cabinet: **ORM-agnostic** — first-class admins for Tortoise ORM,
Django ORM, SQLAlchemy (async) and Pony ORM.
Django ORM, SQLAlchemy (async), Pony ORM and
[Yara ORM](https://github.com/vsdudakov/yara-orm) (a fast, Rust-engine async ORM).
- :bar_chart: **Dashboard widgets** — declarative chart and action widgets
(line, area, column, bar, pie) with filters, powered by antd charts.
- :outbox_tray: **Uploads & exports** — file/image upload widgets with
Expand Down
10 changes: 10 additions & 0 deletions examples/fastapi_yaraorm/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
all: install run

.PHONY: fastapi
run:
uv run uvicorn example:app --reload --host=0.0.0.0 --port=8090

.PHONY: install
install:
uv pip install fastadmin[fastapi] yara-orm uvicorn
uv pip install -e ../../
21 changes: 21 additions & 0 deletions examples/fastapi_yaraorm/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# FastAdmin — FastAPI + Yara ORM example

A minimal admin dashboard for a FastAPI app backed by
[Yara ORM](https://github.com/vsdudakov/yara-orm) (a fast, async, Tortoise-style
Python ORM with a Rust engine).

## Run

```bash
make install
make run
```

Then open <http://localhost:8090/admin> and sign in with `admin` / `admin`.

The example registers `User`, `Tournament`, `BaseEvent` and `Event` admins,
demonstrating foreign keys, one-to-one and many-to-many relations, an inline,
bulk actions, display fields, a dashboard chart widget and file uploads.

See the [FastAdmin documentation](https://vsdudakov.github.io/fastadmin/) for
details.
Empty file.
156 changes: 156 additions & 0 deletions examples/fastapi_yaraorm/example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import os
from collections.abc import AsyncGenerator
from contextlib import asynccontextmanager

os.environ["ADMIN_USER_MODEL"] = "User"
os.environ["ADMIN_USER_MODEL_USERNAME_FIELD"] = "username"
os.environ["ADMIN_SECRET_KEY"] = "secret"

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from models import BaseEvent, Event, Tournament, User
from yara_orm import Model, YaraOrm

from fastadmin import (
WidgetActionChartProps,
WidgetActionFilter,
WidgetActionInputSchema,
WidgetActionResponseSchema,
WidgetActionType,
WidgetType,
YaraOrmInlineModelAdmin,
YaraOrmModelAdmin,
action,
display,
register,
widget_action,
)
from fastadmin import fastapi_app as admin_app


@register(User)
class UserModelAdmin(YaraOrmModelAdmin):
menu_section = "Users"
list_display = ("id", "username", "is_superuser")
list_display_links = ("id", "username")
list_filter = ("id", "username", "is_superuser")
search_fields = ("username",)
formfield_overrides = { # noqa: RUF012
"username": (WidgetType.SlugInput, {"required": True}),
"password": (WidgetType.PasswordInput, {"passwordModalForm": True}),
"avatar_url": (WidgetType.UploadImage, {"required": False}),
}
widget_actions = ("sales_chart",)

async def authenticate(self, username: str, password: str) -> int | None:
obj = await self.model_cls.filter(username=username, password=password, is_superuser=True).first()
if not obj:
return None
return obj.id

async def change_password(self, id: int, password: str) -> None:
user = await self.model_cls.filter(id=id).first()
if not user:
return
# direct saving password is only for the example - hash it in production
user.password = password
await user.save()

async def upload_file(
self,
field_name: str,
file_name: str,
file_content: bytes,
obj: Model | None = None,
) -> str:
# save file to a media directory or to s3/filestorage here
return f"/media/{file_name}"

@widget_action(
widget_action_type=WidgetActionType.ChartLine,
widget_action_props=WidgetActionChartProps(x_field="x", y_field="y", series_field="series"),
widget_action_filters=[
WidgetActionFilter(field_name="x", widget_type=WidgetType.DatePicker),
],
tab="Analytics",
title="Sales over time",
description="Line chart of sales",
width=24,
)
async def sales_chart(self, payload: WidgetActionInputSchema) -> WidgetActionResponseSchema:
return WidgetActionResponseSchema(
data=[
{"x": "2026-01-01", "y": 100, "series": "Sales"},
{"x": "2026-01-02", "y": 200, "series": "Sales"},
],
)


class EventInlineModelAdmin(YaraOrmInlineModelAdmin):
model = Event


@register(Tournament)
class TournamentModelAdmin(YaraOrmModelAdmin):
list_display = ("id", "name")
list_display_links = ("id", "name")
search_fields = ("name",)
inlines = (EventInlineModelAdmin,)


@register(BaseEvent)
class BaseEventModelAdmin(YaraOrmModelAdmin):
list_display = ("id", "name")


@register(Event)
class EventModelAdmin(YaraOrmModelAdmin):
list_display = ("id", "name", "tournament", "is_active", "started")
list_display_links = ("id", "name")
list_filter = ("id", "name", "event_type", "is_active", "tournament")
list_select_related = ("tournament",)
search_fields = ("name",)
actions = ("make_is_active", "make_is_not_active")

@action(description="Make selected events active")
async def make_is_active(self, ids):
await self.model_cls.filter(id__in=ids).update(is_active=True)

@action(description="Make selected events inactive")
async def make_is_not_active(self, ids):
await self.model_cls.filter(id__in=ids).update(is_active=False)

@display
async def started(self, obj):
return bool(obj.start_time)

@display()
async def name_with_price(self, obj):
return f"{obj.name} - {obj.price}"


async def create_superuser() -> None:
if not await User.filter(username="admin").first():
await User.create(username="admin", password="admin", is_superuser=True)


@asynccontextmanager
async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
await YaraOrm.init("sqlite://:memory:")
await YaraOrm.generate_schemas()
await create_superuser()
yield
await YaraOrm.close()


app = FastAPI(lifespan=lifespan)

app.mount("/admin", admin_app)

app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:3030", "http://localhost:8090"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
Loading
Loading