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

# File path: models.py - add association table and modify Post model
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

# File path: app/blueprints/user.py (new file)
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

from app.blueprints.user import user_bp
app.register_blueprint(user_bp)

Add favorite buttons on the detail page and list page

Example

<!-- Add in the article operations area of post_detail.html and 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 %}
{# 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

# File path: app.py or create a new admin_setup.py
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

FeaturesFlask-AdminDjango Admin
Integration methodThird-party extension, requires pip installBuilt into django.contrib.admin
Register modelsadmin.add_view(ModelView(Model, session))admin.site.register(Model)
List fieldscolumn_listlist_display
Searchcolumn_searchable_listsearch_fields
Filtercolumn_filterslist_filter
Authentication requiredNot 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