介绍
Github 开源地址:
https://github.com/aminalaee/sqladmin
网站说明链接地址:
https://aminalaee.dev/sqladmin/
一个现代、优雅的 SQLAlchemy 管理后台工具,非常适合用在 FastAPI 项目中
SQLAdmin 是一个基于 FastAPI + SQLAlchemy 构建的管理后台框架,灵感来自于 Django Admin,目标是为 Python 项目提供:
• 🚀 简洁易用的后台界面
• 📋 基于 SQLAlchemy 的模型自动管理
• ⚙️ 快速 CRUD(增删改查)
• ✨ Jinja2 渲染漂亮的 Bootstrap 风格后台
功能 | 描述 |
---|---|
🎨 UI | 基于 Tabler(Bootstrap 风格)构建 |
🔁 CRUD 自动化 | 自动根据模型生成增删改查 |
🔍 搜索 / 过滤 | 表字段搜索、过滤支持 |
🧩 多数据库支持 | 支持 SQLite、PostgreSQL、MySQL 等 |
🔐 自定义认证 | 可扩展自定义登录(如你用的 AdminAuth) |
🧠 异步支持 | 与 FastAPI 完美配合,支持异步视图 |
🔧 高度可配置 | 支持列隐藏、格式化、标签等配置项 |
支持models 的类型:
✅ 支持 SQLAlchemy 吗? | 是,必须基于 SQLAlchemy ORM 模型 |
---|---|
✅ 支持 SQLAlchemy 1.x 吗? | 是 |
✅ 支持 SQLAlchemy 2.x 吗? | 是,从 sqladmin 0.15.0+ 开始全面支持 |
❌ 支持 Tortoise ORM 吗? | 不支持 |
❌ 支持 Django ORM 吗? | 不支持 |
为什么使用?
很多项目都是异步,而异步里面ORM 性能最好的就是 SQLAlchemy 2.0
特性 | 优势 | 是否推荐 |
---|---|---|
✅ 新的 2.0 风格语法 | 更清晰、类型安全,统一 async / sync 用法 | 强烈推荐 |
✅ 原生异步支持(async_engine) | 适配 FastAPI、aiohttp 等异步框架 | ✔ |
✅ Declarative Mapping 更加清晰 | 不再混用 Base = declarative_base() 和 mapper() | ✔ |
✅ 更强的类型提示支持 | IDE 友好,调试更舒服 | ✔ |
✅ Session 语义改进 | 显式事务控制、更加安全 | ✔ |
✅ 官方长期维护方向 | 所有新功能只在 2.x 上更新 | ✔ |
特性 / 框架 | SQLAlchemy 2.0 (async) | Tortoise ORM | asyncpg (原生驱动) |
---|---|---|---|
⭐ 异步支持 | ✅ 原生 async API(2.0 起) | ✅ 原生 async | ✅ 原生 async |
🎯 ORM 支持 | ✅ 完整 ORM(经典 + 声明式) | ✅ 类 Django 的 ORM | ❌ 没有 ORM,需手写 SQL |
⚙️ 事务处理 | ✅ async with session.begin() | ✅ transaction() 上下文 | ✅ 需手动写 SQL + 事务处理 |
🔧 灵活性 | ✅ 高(支持 ORM + Core) | 中等(ORM风格较固定) | 极高(最贴近 SQL) |
📚 学习曲线 | 稍高(但文档全) | 较低,适合 Django 用户 | 中等(需掌握 SQL) |
🚀 性能 | 优(合理用 async) | 优(轻量) | 最优(直接操作 protocol) |
✅ 类型支持 | ✅ Pydantic 配合良好 | ✅ 也能配合 Pydantic 使用 | ❌ 无自动转换,手动处理 |
🧩 支持的数据库 | 多(PostgreSQL, MySQL, SQLite等) | PostgreSQL、MySQL、SQLite | PostgreSQL 专用 |
🔍 社区 & 文档 | 非常成熟(官方长期维护) | 活跃,偏小众 | 官方是 PostgreSQL 的推荐驱动 |
你的需求 | 推荐方案 |
---|---|
需要强大的 ORM + 类型检查 + 可扩展性 | ✅ SQLAlchemy 2.0 + Async |
想快速上手、有 Django ORM 背景 | ✅ Tortoise ORM |
要极致性能,手写 SQL 不怕麻烦 | ✅ asyncpg |
结合应用
from sqlalchemy import Column, Integer, String, create_engine,Enum as SAEnum,JSON
from sqladmin.authentication import AuthenticationBackend
import json
import hashlib
from sqlalchemy import event
from sqlalchemy.orm import declarative_base
from enum import Enum
from sqlalchemy.ext.hybrid import hybrid_property
Base = declarative_base()
engine = create_engine(
"sqlite:///example.db",
connect_args={"check_same_thread": False},
)
# 定义枚举类型
class GenderEnum(str, Enum):
MALE = "male"
FEMALE = "female"
OTHER = "other"
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True)
name = Column(String)
gender = Column(SAEnum(GenderEnum), nullable=False, comment="性别")
password = Column("password", String, nullable=False) # 真实存储加密后的密码
class JsonConfig(Base):
__tablename__ = "json_config"
id = Column(Integer, primary_key=True)
name = Column(String)
config = Column(JSON)
Base.metadata.create_all(engine) # Create tables,后面改了字段需要删除
from fastapi import FastAPI
from sqladmin import Admin, ModelView
from fastapi import Request
app = FastAPI()
class AdminAuth(AuthenticationBackend):
async def login(self, request: Request) -> bool:
form = await request.form()
username, password = form["username"], form["password"]
# Validate username/password credentials
# And update session
if username == "admin" and password == "123456":
request.session.update({"sqladmin_token": "..."})
return True
else:
return False
return True
async def logout(self, request: Request) -> bool:
# Usually you'd want to just clear the session
request.session.clear()
return True
async def authenticate(self, request: Request) -> bool:
token = request.session.get("sqladmin_token")
if not token:
return False
# Check the token in depth
return True
authentication_backend = AdminAuth(secret_key="secret_key")
admin = Admin(app, engine,base_url="/super",authentication_backend=authentication_backend)
class UserAdmin(ModelView, model=User):
self_md5_salt = "123456"
column_list = [User.id, User.name,User.gender,User.password]
# 自定义列标签(让 SQLAdmin UI 显示中文)
column_labels = {
"id": "用户 ID",
"name": "姓名",
"gender": "性别",
"password": "密码",
}
async def insert_model(self, request: Request, data: dict):
"""拦截插入逻辑,确保 `password` 经过 MD5 加密"""
raw_password = data.pop("password", None)
if raw_password:
data["password"] = hashlib.md5((raw_password+self.self_md5_salt).encode()).hexdigest()
return await super().insert_model(request, data)
async def update_model(self, request: Request, pk: str, data: dict):
"""拦截更新逻辑,确保 `password` 经过 MD5 加密"""
raw_password = data.pop("password", None)
if raw_password:
data["password"] = hashlib.md5((raw_password+self.self_md5_salt).encode()).hexdigest()
return await super().update_model(request, pk, data)
class JsonConfigAdmin(ModelView, model=JsonConfig):
column_list = [JsonConfig.id, JsonConfig.name,JsonConfig.config]
# 前端页面插入的时候 只能使用双引号的json 不能使用单引号的json
admin.add_view(UserAdmin)
admin.add_view(JsonConfigAdmin)
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="127.0.0.1", port=8000)
# uvicorn test_sqladmin:app --host 127.0.0.1 --port 8000 --reload
# 后台地址: http://127.0.0.1:8000/super
# pip install itsdangerous sqladmin