介绍
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 eventfrom sqlalchemy.orm import declarative_base
from enum import Enum
from sqlalchemy.ext.hybrid import hybrid_propertyBase = 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 Requestapp = 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 sessionif username == "admin" and password == "123456":request.session.update({"sqladmin_token": "..."})return Trueelse:return Falsereturn Trueasync def logout(self, request: Request) -> bool:# Usually you'd want to just clear the sessionrequest.session.clear()return Trueasync def authenticate(self, request: Request) -> bool:token = request.session.get("sqladmin_token")if not token:return False# Check the token in depthreturn Trueauthentication_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 不能使用单引号的jsonadmin.add_view(UserAdmin)
admin.add_view(JsonConfigAdmin)if __name__ == "__main__":import uvicornuvicorn.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