Favorites Feature and Admin Management
In this chapter, you will learn how to implement a favorites feature using a many-to-many relationship, and use sqladmin to set up an admin backend.
Many-to-Many Relationship Design
Example
from sqlalchemy import Table
# Association table: many-to-many intermediate table between User and Post
favorites = Table(
"favorites",
Base.metadata,
Column("user_id", Integer, ForeignKey("users.id"), primary_key=True),
Column("post_id", Integer, ForeignKey("posts.id"), primary_key=True),
Column("created_at", DateTime, default=datetime.utcnow)
)
Execute migration:
(venv) $ alembic revision --autogenerate -m "新增收藏关联表" (venv) $ alembic upgrade head
Favorite and Unfavorite Routes
Example
from fastapi import APIRouter, Depends, HTTPException, Request
from sqlalchemy.orm import Session
from database import get_db
from models import Post, favorites
from routers.users import get_current_user
from fastapi.templating import Jinja2Templates
router = APIRouter(prefix="/favorites", tags=["Favorites"])
templates = Jinja2Templates(directory="templates")
@router.post("/toggle/{post_id}")
def toggle_favorite(
post_id: int,
request: Request,
db: Session = Depends(get_db),
current_user = Depends(get_current_user) # Login Required
):
"""Toggle favorite status"""
post = db.query(Post).filter(Post.id == post_id).first()
if not post:
raise HTTPException(status_code=404, detail=“Article does not exist”)
# Check if already favorited
is_faved = db.query(favorites).filter(
favorites.c.user_id == current_user.id,
favorites.c.post_id == post.id
).first() is not None
if is_faved:
# Unfavorite
db.execute(
favorites.delete().where(
(favorites.c.user_id == current_user.id) &
(favorites.c.post_id == post.id)
)
)
db.commit()
else:
# Add favorite
db.execute(
favorites.insert().values(user_id=current_user.id, post_id=post.id)
)
db.commit()
from fastapi.responses import RedirectResponse
return RedirectResponse(url=request.headers.get("referer", "/"), status_code=303)
@router.get("/", name="favorites_list")
def favorites_list(
request: Request,
db: Session = Depends(get_db),
current_user = Depends(get_current_user)
):
"""My favorites list"""
faved_posts = db.query(Post).join(
favorites, (favorites.c.post_id == Post.id)
).filter(
favorites.c.user_id == current_user.id
).order_by(favorites.c.created_at.desc()).all()
return templates.TemplateResponse("favorites.html", {
"request": request,
"posts": faved_posts
})
Add a Favorite Button on the Detail Page
Example
<div class="post-header">
<h1>{{ post.title }}</h1>
<form method="post" action="/favorites/toggle/{{ post.id }}">
<button type="submit" class="fav-btn">♡ Favorite</button>
</form>
</div>
sqladmin — Admin Management
sqladminIt is the admin backend extension in the FastAPI ecosystem that parallels Django Admin.
It is designed specifically for SQLAlchemy and integrates seamlessly with FastAPI.
(venv) $ pip install sqladmin
Example
from sqladmin import Admin, ModelView
from database import engine
from models import Post, Category, User
class PostAdmin(ModelView, model=Post):
column_list = [Post.id, Post.title, Post.category, Post.created_at]
column_searchable_list = [Post.title, Post.summary]
column_sortable_list = [Post.created_at, Post.title]
form_columns = [Post.title, Post.slug, Post.summary, Post.content, Post.category_id]
class CategoryAdmin(ModelView, model=Category):
column_list = [Category.id, Category.name, Category.slug]
form_columns = [Category.name, Category.slug]
class UserAdmin(ModelView, model=User):
column_list = [User.id, User.username, User.email]
column_searchable_list = [User.username, User.email]
# Do not display the password field (for security)
form_excluded_columns = [User.hashed_password]
# Create an Admin instance and bind it to the FastAPI app
admin = Admin(app, engine, title=EXAMPLE Blog Admin)
admin.add_view(PostAdmin)
admin.add_view(CategoryAdmin)
admin.add_view(UserAdmin)
Visit /admin to enter the admin interface.
sqladmin vs Flask-Admin vs Django Admin
| Features | Django Admin | Flask-Admin | sqladmin |
|---|---|---|---|
| Registration method | admin.site.register() | admin.add_view(ModelView()) | Admin + add_view |
| ORM Support | Django ORM | SQLAlchemy / MongoEngine | SQLAlchemy (only) |
| Authentication support | Built-in | Requires manual integration | Built-in authentication_backend |
| Async support | Not supported | Not supported | native async |
sqladmin is accessible without login by default. It needs to be configured for production environments.
authentication_backendTo add authentication protection, see the official sqladmin documentation.
Chapter summary
In this chapter, you implemented two important features: using a Table association table to implement the many-to-many relationship for favorites; and using sqladmin to quickly set up an admin backend management system.
The blog now has complete user interaction features (registration, login, favorites) and management functions (Admin backend).
other extensions