收藏功能与后台管理
本章你将学会用多对多关系实现收藏功能,并用 Flask-Admin 快速搭建后台管理。
多对多关系设计
收藏功能的需求:一个用户可以收藏多篇文章,一篇文章可以被多个用户收藏。
在 SQLAlchemy 中,多对多关系通过关联表(Association Table)实现。
实例
# 文件路径:models.py 新增关联表和修改 Post 模型
from datetime import datetime
# 关联表:不需要定义为模型类,用 db.Table 直接创建
favorites = db.Table('favorites',
db.Column('user_id', db.Integer, db.ForeignKey('users.id'), primary_key=True),
db.Column('post_id', db.Integer, db.ForeignKey('posts.id'), primary_key=True),
db.Column('created_at', db.DateTime, default=datetime.utcnow)
)
class Post(db.Model):
__tablename__ = 'posts'
# ... 已有字段不变 ...
from datetime import datetime
# 关联表:不需要定义为模型类,用 db.Table 直接创建
favorites = db.Table('favorites',
db.Column('user_id', db.Integer, db.ForeignKey('users.id'), primary_key=True),
db.Column('post_id', db.Integer, db.ForeignKey('posts.id'), primary_key=True),
db.Column('created_at', db.DateTime, default=datetime.utcnow)
)
class Post(db.Model):
__tablename__ = 'posts'
# ... 已有字段不变 ...
然后执行迁移:
(venv) $ flask db migrate -m "新增收藏关联表" (venv) $ flask db upgrade
收藏与取消收藏视图
实例
# 文件路径:app/blueprints/user.py(新建文件)
from flask import Blueprint, render_template, redirect, url_for, request, flash
from flask_login import login_required, current_user
from app.models import Post, db, favorites
user_bp = Blueprint('user', __name__)
@user_bp.route("/favorite/<int:post_id>", methods=['POST'])
@login_required
def toggle_favorite(post_id):
"""切换收藏状态"""
post = Post.query.get_or_404(post_id)
# 检查是否已收藏:查询关联表中是否有此用户-文章组合
is_faved = db.session.query(favorites).filter(
favorites.c.user_id == current_user.id,
favorites.c.post_id == post.id
).first() is not None
if is_faved:
# 取消收藏
db.session.execute(
favorites.delete().where(
(favorites.c.user_id == current_user.id) &
(favorites.c.post_id == post.id)
)
)
db.session.commit()
flash(f'已取消收藏「{post.title}」', 'info')
else:
# 添加收藏
db.session.execute(
favorites.insert().values(user_id=current_user.id, post_id=post.id)
)
db.session.commit()
flash(f'已收藏「{post.title}」', 'success')
return redirect(request.referrer or url_for('main.index'))
@user_bp.route("/favorites")
@login_required
def favorites_list():
"""当前用户的收藏列表"""
# 通过关联表查询用户收藏的文章
faved_posts = Post.query.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 render_template('favorites.html', posts=faved_posts)
from flask import Blueprint, render_template, redirect, url_for, request, flash
from flask_login import login_required, current_user
from app.models import Post, db, favorites
user_bp = Blueprint('user', __name__)
@user_bp.route("/favorite/<int:post_id>", methods=['POST'])
@login_required
def toggle_favorite(post_id):
"""切换收藏状态"""
post = Post.query.get_or_404(post_id)
# 检查是否已收藏:查询关联表中是否有此用户-文章组合
is_faved = db.session.query(favorites).filter(
favorites.c.user_id == current_user.id,
favorites.c.post_id == post.id
).first() is not None
if is_faved:
# 取消收藏
db.session.execute(
favorites.delete().where(
(favorites.c.user_id == current_user.id) &
(favorites.c.post_id == post.id)
)
)
db.session.commit()
flash(f'已取消收藏「{post.title}」', 'info')
else:
# 添加收藏
db.session.execute(
favorites.insert().values(user_id=current_user.id, post_id=post.id)
)
db.session.commit()
flash(f'已收藏「{post.title}」', 'success')
return redirect(request.referrer or url_for('main.index'))
@user_bp.route("/favorites")
@login_required
def favorites_list():
"""当前用户的收藏列表"""
# 通过关联表查询用户收藏的文章
faved_posts = Post.query.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 render_template('favorites.html', posts=faved_posts)
在 app.py 中注册 Blueprint
实例
from app.blueprints.user import user_bp
app.register_blueprint(user_bp)
app.register_blueprint(user_bp)
在详情页和列表页添加收藏按钮
实例
<!-- 在 post_detail.html 和 index.html 的文章操作区加入 -->
{% if current_user.is_authenticated %}
<form method="post" action="{{ url_for('user.toggle_favorite', post_id=post.id) }}">
<button type="submit" class="fav-btn">
{% set is_faved = false %}
{# 检查当前用户是否在文章的收藏者列表中 #}
♡ 收藏
</button>
</form>
{% endif %}
{% if current_user.is_authenticated %}
<form method="post" action="{{ url_for('user.toggle_favorite', post_id=post.id) }}">
<button type="submit" class="fav-btn">
{% set is_faved = false %}
{# 检查当前用户是否在文章的收藏者列表中 #}
♡ 收藏
</button>
</form>
{% endif %}
收藏操作用 POST 请求(form 提交),而非 GET 链接。GET 请求不应该产生副作用(修改数据库),这是 Web 开发的基本原则。
Flask-Admin — 后台管理
Flask-Admin 是 Flask 生态中对标 Django Admin 的扩展。
配置后,你可以在浏览器中管理文章和分类数据。
(venv) $ pip install flask-admin
实例
# 文件路径:app.py 或新建 admin_setup.py
from flask_admin import Admin
from flask_admin.contrib.sqla import ModelView
from app.models import Post, Category, User
# 自定义 ModelView:控制后台显示哪些字段
class PostAdmin(ModelView):
column_list = ['title', 'category', 'created_at', 'updated_at']
column_searchable_list = ['title', 'summary']
column_filters = ['category', 'created_at']
form_columns = ['title', 'slug', 'summary', 'content', 'category']
class CategoryAdmin(ModelView):
column_list = ['name', 'slug']
form_columns = ['name', 'slug']
# 创建 Admin 实例
admin = Admin(app, name='RUNOOB 博客后台', template_mode='bootstrap4')
admin.add_view(PostAdmin(Post, db.session, name='文章'))
admin.add_view(CategoryAdmin(Category, db.session, name='分类'))
admin.add_view(ModelView(User, db.session, name='用户'))
from flask_admin import Admin
from flask_admin.contrib.sqla import ModelView
from app.models import Post, Category, User
# 自定义 ModelView:控制后台显示哪些字段
class PostAdmin(ModelView):
column_list = ['title', 'category', 'created_at', 'updated_at']
column_searchable_list = ['title', 'summary']
column_filters = ['category', 'created_at']
form_columns = ['title', 'slug', 'summary', 'content', 'category']
class CategoryAdmin(ModelView):
column_list = ['name', 'slug']
form_columns = ['name', 'slug']
# 创建 Admin 实例
admin = Admin(app, name='RUNOOB 博客后台', template_mode='bootstrap4')
admin.add_view(PostAdmin(Post, db.session, name='文章'))
admin.add_view(CategoryAdmin(Category, db.session, name='分类'))
admin.add_view(ModelView(User, db.session, name='用户'))
启动服务器,访问 /admin,即可进入后台管理界面。
Flask-Admin vs Django Admin
| 特性 | Flask-Admin | Django Admin |
|---|---|---|
| 集成方式 | 第三方扩展,需 pip install | 内置在 django.contrib.admin |
| 注册模型 | admin.add_view(ModelView(Model, session)) | admin.site.register(Model) |
| 列表字段 | column_list | list_display |
| 搜索 | column_searchable_list | search_fields |
| 过滤 | column_filters | list_filter |
| 需要认证 | 不内置(需手动加 @login_required) | 内置管理员权限 |
Flask-Admin 默认不需要登录就能访问,需要结合 Flask-Login 加权限控制。一个简单的方式是在视图函数前加
@login_required,或者覆盖 ModelView 的is_accessible方法检查管理员权限。
本章小结
本章你实现了两个重要功能:用 db.Table 关联表 + db.session.execute 操作实现收藏的多对多关系;用 Flask-Admin 快速搭建后台管理系统。
现在博客的交互功能(用户、收藏)和管理功能(后台)都已完备。
