Favorites Function and Backend Management
In this chapter, you will learn to implement a bookmark feature using many-to-many relationships and quickly build a backend admin with Flask-Admin.
Many-to-many relationship design
Requirements for the favorites function:A user can favorite multiple posts, and a post can be favorited by multiple users.。
In SQLAlchemy, many-to-many relationships are handled throughAssociation TableImplementation.
Example
from datetime import datetime
# Association table: no need to define it as a model class, create it directly with 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'
# ... existing fields remain unchanged ...
Then run the migration:
(venv) $ flask db migrate -m "新增收藏关联表" (venv) $ flask db upgrade
Favorite and Unfavorite Views
Example
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):
"""Toggle favorite status"""
post = Post.query.get_or_404(post_id)
# Check whether already favorited: query the association table for this user-post combination
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:
# Unfavorite
db.session.execute(
favorites.delete().where(
(favorites.c.user_id == current_user.id) &
(favorites.c.post_id == post.id)
)
)
db.session.commit()
flash(f'Unfavorited "{post.title}"', 'info')
else:
# Add favorite
db.session.execute(
favorites.insert().values(user_id=current_user.id, post_id=post.id)
)
db.session.commit()
flash(f'Favorited "{post.title}"', 'success')
return redirect(request.referrer or url_for('main.index'))
@user_bp.route("/favorites")
@login_required
def favorites_list():
"""Current user's favorites list"""
# Query articles favorited by users through the association table
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)
Register Blueprint in app.py
Example
app.register_blueprint(user_bp)
Add favorite buttons on the detail page and list page
Example
{% 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 %}
{# Check whether the current user is in the list of users who favorited the article #}
♡ Favorite
</button>
</form>
{% endif %}
Bookmark operations use POST requests (form submission), not GET links. GET requests should not produce side effects (modifying the database); this is a fundamental principle of web development.
Flask-Admin — Backend Management
Flask-AdminIt is the extension in the Flask ecosystem that is equivalent to Django Admin.
After configuration, you can manage articles and categories in the browser.
(venv) $ pip install flask-admin
Example
from flask_admin import Admin
from flask_admin.contrib.sqla import ModelView
from app.models import Post, Category, User
# Custom ModelView: control which fields are displayed in the admin backend
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']
# Create Admin instance
admin = Admin(app, name='EXAMPLE Blog Admin', template_mode='bootstrap4')
admin.add_view(PostAdmin(Post, db.session, name='Articles'))
admin.add_view(CategoryAdmin(Category, db.session, name='Categories'))
admin.add_view(ModelView(User, db.session, name='Users'))
Start the server and visit/admin, and you can enter the admin interface.
Flask-Admin vs Django Admin
| Features | Flask-Admin | Django Admin |
|---|---|---|
| Integration method | Third-party extension, requires pip install | Built into django.contrib.admin |
| Register models | admin.add_view(ModelView(Model, session)) | admin.site.register(Model) |
| List fields | column_list | list_display |
| Search | column_searchable_list | search_fields |
| Filter | column_filters | list_filter |
| Authentication required | Not built-in (requires manually adding @login_required) | Built-in administrator permissions |
Flask-Admin does not require login to access by default; it needs to be combined with Flask-Login for permission control. A simple way is to add
@login_required, or override ModelView'sis_accessiblea method to check administrator permissions.
Chapter summary
In this chapter, you implemented two important features: using the db.Table association table + db.session.execute operations to implement the many-to-many relationship for bookmarks; and using Flask-Admin to quickly build a backend management system.
Now the blog's interactive features (users, bookmarks) and management features (backend) are all complete.
other extensions