Skip to main content

FastAdmin is an easy-to-use Admin Dashboard App for FastAPI/Flask/Django inspired by Django Admin.

Project description

Admin Dashboard for FastAPI / Flask / Django

codecov License PyPi Python 3.12 Python 3.13

Screenshots

Sign-in view List view Change view

tweet

Introduction

FastAdmin is an easy-to-use admin dashboard for FastAPI, Django, and Flask, inspired by Django Admin.

FastAdmin is built with relationships in mind and admiration for Django Admin. Its design focuses on making it as easy as possible to configure your admin dashboard for FastAPI, Django, or Flask.

FastAdmin aims to be minimal, functional, and familiar.

Getting Started

If you have questions beyond this documentation, feel free to email us.

Installation

Follow the steps below to set up FastAdmin:

Install the package with pip:

On zsh and macOS, use quotes: pip install 'fastadmin[fastapi,django]'

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[django]                # Django with Django ORM
pip install fastadmin[django,pony]           # Django with Pony ORM
pip install fastadmin[flask,sqlalchemy]      # Flask with SQLAlchemy (includes greenlet)

Or install with Poetry:

poetry add 'fastadmin[fastapi,django]'
poetry add 'fastadmin[fastapi,tortoise-orm]'
poetry add 'fastadmin[fastapi,pony]'
poetry add 'fastadmin[fastapi,sqlalchemy]'
poetry add 'fastadmin[django]'
poetry add 'fastadmin[django,pony]'
poetry add 'fastadmin[flask,sqlalchemy]'

When using SQLAlchemy, the greenlet package is required (included in the fastadmin[sqlalchemy] extra).

Configure the required settings with environment variables:

You can add these variables to a .env file and load them with python-dotenv. See all settings in the full documentation.

export ADMIN_USER_MODEL=User
export ADMIN_USER_MODEL_USERNAME_FIELD=username
export ADMIN_SECRET_KEY=secret_key

Quick Tutorial

Set up FastAdmin for your framework

FastAPI

from fastapi import FastAPI

from fastadmin import fastapi_app as admin_app

app = FastAPI()

app.mount("/admin", admin_app)

Django

from django.urls import path

from fastadmin import get_django_admin_urls as get_admin_urls
from fastadmin.settings import settings

urlpatterns = [
    path(f"{settings.ADMIN_PREFIX}/", get_admin_urls()),
]

Flask

from flask import Flask

from fastadmin import flask_app as admin_app

app = Flask(__name__)

app.register_blueprint(admin_app, url_prefix="/admin")

Register ORM models

Tortoise ORM

import typing as tp
from uuid import UUID

import bcrypt
from tortoise import fields
from tortoise.models import Model

from fastadmin import TortoiseModelAdmin, WidgetType, register


class User(Model):
    username = fields.CharField(max_length=255, unique=True)
    hash_password = fields.CharField(max_length=255)
    is_superuser = fields.BooleanField(default=False)
    is_active = fields.BooleanField(default=False)
    avatar_url = fields.TextField(null=True)

    def __str__(self):
        return self.username


@register(User)
class UserAdmin(TortoiseModelAdmin):
    exclude = ("hash_password",)
    list_display = ("id", "username", "is_superuser", "is_active")
    list_display_links = ("id", "username")
    list_filter = ("id", "username", "is_superuser", "is_active")
    search_fields = ("username",)
    formfield_overrides: tp.Any = {  # noqa: RUF012
        "username": (WidgetType.SlugInput, {"required": True}),
        "password": (WidgetType.PasswordInput, {"passwordModalForm": True}),
        "avatar_url": (
            WidgetType.UploadImage,
            {
                "required": False,
                # "disableCropImage": True,  # optional: disable image cropping
            },
        ),
    }

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

    async def change_password(self, id: UUID | int | str, password: str) -> None:
        user = await self.model_cls.filter(id=id).first()
        if not user:
            return
        user.hash_password = bcrypt.hashpw(password.encode(), bcrypt.gensalt()).decode()
        await user.save(update_fields=("hash_password",))

    async def upload_file(self, field_name: str, file_name: str, file_content: bytes) -> str:
        # save file to media directory or s3/filestorage, then return the file url
        return f"/media/{file_name}"

Django ORM

from django.db import models

from fastadmin import DjangoModelAdmin, register


class User(models.Model):
    username = models.CharField(max_length=255, unique=True)
    hash_password = models.CharField(max_length=255)
    is_superuser = models.BooleanField(default=False)
    is_active = models.BooleanField(default=False)
    avatar_url = models.ImageField(null=True)

    def __str__(self):
        return self.username


@register(User)
class UserAdmin(DjangoModelAdmin):
    exclude = ("hash_password",)
    list_display = ("id", "username", "is_superuser", "is_active")
    list_display_links = ("id", "username")
    list_filter = ("id", "username", "is_superuser", "is_active")
    search_fields = ("username",)

    def authenticate(self, username, password):
        obj = User.objects.filter(username=username, is_superuser=True).first()
        if not obj:
            return None
        if not obj.check_password(password):
            return None
        return obj.id

    def upload_file(self, field_name: str, file_name: str, file_content: bytes) -> str:  # type: ignore[override]
        # save file to media directory or s3/filestorage, then return the file url
        return f"/media/{file_name}"

SQLAlchemy

import uuid

import bcrypt
from sqlalchemy import Boolean, Integer, String, Text, select, update
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column

from fastadmin import SqlAlchemyModelAdmin, WidgetType, register

sqlalchemy_engine = create_async_engine(
    "sqlite+aiosqlite:///:memory:",
    echo=True,
)
sqlalchemy_sessionmaker = async_sessionmaker(sqlalchemy_engine, expire_on_commit=False)


class Base(DeclarativeBase):
    pass


class User(Base):
    __tablename__ = "user"

    id: Mapped[int] = mapped_column(Integer, primary_key=True, nullable=False)
    username: Mapped[str] = mapped_column(String(length=255), nullable=False)
    hash_password: Mapped[str] = mapped_column(String(length=255), nullable=False)
    is_superuser: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
    is_active: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
    avatar_url: Mapped[str | None] = mapped_column(Text, nullable=True)

    def __str__(self):
        return self.username


@register(User, sqlalchemy_sessionmaker=sqlalchemy_sessionmaker)
class UserAdmin(SqlAlchemyModelAdmin):
    exclude = ("hash_password",)
    list_display = ("id", "username", "is_superuser", "is_active")
    list_display_links = ("id", "username")
    list_filter = ("id", "username", "is_superuser", "is_active")
    search_fields = ("username",)
    formfield_overrides = {  # noqa: RUF012
        "avatar_url": (WidgetType.UploadImage, {"required": False}),
    }

    async def authenticate(self, username: str, password: str) -> uuid.UUID | int | None:
        sessionmaker = self.get_sessionmaker()
        async with sessionmaker() as session:
            query = select(self.model_cls).filter_by(username=username, password=password, is_superuser=True)
            result = await session.scalars(query)
            obj = result.first()
            if not obj:
                return None
            if not bcrypt.checkpw(password.encode(), obj.hash_password.encode()):
                return None
            return obj.id

    async def change_password(self, id: uuid.UUID | int | str, password: str) -> None:
        sessionmaker = self.get_sessionmaker()
        async with sessionmaker() as session:
            hash_password = bcrypt.hashpw(password.encode(), bcrypt.gensalt()).decode()
            query = update(self.model_cls).where(User.id.in_([id])).values(hash_password=hash_password)
            await session.execute(query)
            await session.commit()

    async def upload_file(self, field_name: str, file_name: str, file_content: bytes) -> str:
        # save file to media directory or s3/filestorage, then return the file url
        return f"/media/{file_name}"

Pony ORM

import uuid

import bcrypt
from pony.orm import Database, LongStr, Optional, PrimaryKey, Required, commit, db_session

from fastadmin import PonyORMModelAdmin, WidgetType, register

db = Database()
db.bind(provider="sqlite", filename=":memory:", create_db=True)


class User(db.Entity):  # type: ignore [name-defined]
    _table_ = "user"
    id = PrimaryKey(int, auto=True)
    username = Required(str)
    hash_password = Required(str)
    is_superuser = Required(bool, default=False)
    is_active = Required(bool, default=False)
    avatar_url = Optional(LongStr, nullable=True)

    def __str__(self):
        return self.username


@register(User)
class UserAdmin(PonyORMModelAdmin):
    exclude = ("hash_password",)
    list_display = ("id", "username", "is_superuser", "is_active")
    list_display_links = ("id", "username")
    list_filter = ("id", "username", "is_superuser", "is_active")
    search_fields = ("username",)
    formfield_overrides = {  # noqa: RUF012
        "avatar_url": (WidgetType.UploadImage, {"required": False}),
    }

    @db_session
    def authenticate(self, username: str, password: str) -> uuid.UUID | int | None:
        obj = next((f for f in User.select(username=username, password=password, is_superuser=True)), None)  # fmt: skip
        if not obj:
            return None
        if not bcrypt.checkpw(password.encode(), obj.hash_password.encode()):
            return None
        return obj.id

    @db_session
    def change_password(self, id: uuid.UUID | int, password: str) -> None:
        obj = next((f for f in self.model_cls.select(id=id)), None)
        if not obj:
            return
        hash_password = bcrypt.hashpw(password.encode(), bcrypt.gensalt()).decode()
        obj.hash_password = hash_password
        commit()

    def upload_file(self, field_name: str, file_name: str, file_content: bytes) -> str:  # type: ignore[override]
        # save file to media directory or s3/filestorage, then return the file url
        return f"/media/{file_name}"

Documentation

Full documentation is available at vsdudakov.github.io/fastadmin.

License

This project is licensed under the MIT License — see the LICENSE file for details.

Project details


Release history Release notifications | RSS feed

This version

0.4.1

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

fastadmin-0.4.1.tar.gz (1.7 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

fastadmin-0.4.1-py3-none-any.whl (1.7 MB view details)

Uploaded Python 3

File details

Details for the file fastadmin-0.4.1.tar.gz.

File metadata

  • Download URL: fastadmin-0.4.1.tar.gz
  • Upload date:
  • Size: 1.7 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.3.2 CPython/3.12.3 Linux/6.14.0-1017-azure

File hashes

Hashes for fastadmin-0.4.1.tar.gz
Algorithm Hash digest
SHA256 94390c07035b134218afa35c75dba25250bafaede63e8efbbc2439b9f57cba60
MD5 41f72208241fc091a4faaa7ad42d3fd1
BLAKE2b-256 dfa7c6477e091a1bcaa508554a1bb8a78d8b6cd9b0d6bdc87e479a265cbf66c1

See more details on using hashes here.

File details

Details for the file fastadmin-0.4.1-py3-none-any.whl.

File metadata

  • Download URL: fastadmin-0.4.1-py3-none-any.whl
  • Upload date:
  • Size: 1.7 MB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.3.2 CPython/3.12.3 Linux/6.14.0-1017-azure

File hashes

Hashes for fastadmin-0.4.1-py3-none-any.whl
Algorithm Hash digest
SHA256 04a67f255d9f71612a99f0d82ec6836523751cede247641bd2ee5a83ae43d9d9
MD5 e993bde114b086d2776cf489f09d313c
BLAKE2b-256 b583ed329d0986b487eb13d9ed681c4f1d89261a0ac0470aa1eb58640e97831d

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page