现在位置: 首页 > Django 教程 > 正文

表单与收藏功能

本章你将学会处理用户 POST 请求,建立多对多关系,并实现「收藏文章」功能。


收藏功能的数据模型设计

收藏功能的核心是:一个用户可以收藏多篇文章,一篇文章可以被多个用户收藏

这是典型的多对多(ManyToMany)关系。

我们不需要新建模型——Django 的 User 模型是内置的,只需要在 Post 模型中添加一个 ManyToManyField 指向 User。

实例

# 文件路径:blog/models.py 的 Post 类中添加
from django.contrib.auth.models import User

class Post(models.Model):
    # ... 已有字段 ...
    # 收藏者:ManyToManyField 自动创建中间表
    favorites = models.ManyToManyField(
        User,
        related_name='favorite_posts',  # 反向查询:user.favorite_posts.all()
        verbose_name='收藏者',
        blank=True
    )

修改模型后,生成并执行迁移:

(venv) $ python manage.py makemigrations
(venv) $ python manage.py migrate

收藏与取消收藏的视图逻辑

实例

# 文件路径:blog/views.py 新增
from django.shortcuts import render, redirect, get_object_or_404
from django.contrib.auth.decorators import login_required
from django.contrib import messages
from .models import Post

# login_required 装饰器:未登录用户访问此视图时,自动跳转到登录页
@login_required
def toggle_favorite(request, pk):
    """切换收藏状态:收藏 → 取消,未收藏 → 收藏"""
    post = get_object_or_404(Post, pk=pk)

    # 检查当前用户是否已收藏
    if post.favorites.filter(id=request.user.id).exists():
        # 已收藏 → 取消收藏
        post.favorites.remove(request.user)
        messages.info(request, f'已取消收藏「{post.title}」')
    else:
        # 未收藏 → 添加收藏
        post.favorites.add(request.user)
        messages.success(request, f'已收藏「{post.title}」')

    # 重定向回当前页面(HTTP_REFERER 是来源页面的 URL)
    referer = request.META.get('HTTP_REFERER', '/')
    return redirect(referer)

ManyToMany 操作方法速查

方法作用示例
add(obj)添加关联post.favorites.add(user)
remove(obj)移除关联post.favorites.remove(user)
all()查询所有关联对象post.favorites.all()
filter(...)带条件过滤post.favorites.filter(id=1)
exists()判断是否有匹配记录post.favorites.filter(id=1).exists()
clear()清除所有关联post.favorites.clear()

配置收藏路由

实例

# 文件路径:blog/urls.py 追加
urlpatterns = [
    # ... 已有路由 ...
    path('post/<int:pk>/favorite/', views.toggle_favorite, name='toggle_favorite'),
]

在详情页添加收藏按钮

实例

<!-- 文件路径:blog/templates/blog/post_detail.html(修改) -->
{% extends 'blog/base.html' %}

{% block title %}{{ post.title }} - RUNOOB 博客{% endblock %}

{% block content %}
<article class="post-view">
    <span class="category-tag">{{ post.category.name }}</span>
    <div class="post-header">
        <h1>{{ post.title }}</h1>
        <!-- 收藏按钮:仅登录用户可见 -->
        {% if user.is_authenticated %}
        <form method="post" action="{% url 'toggle_favorite' post.pk %}">
            {% csrf_token %}
            <button type="submit" class="fav-btn">
                {% if user in post.favorites.all %}
                    &#x2665; 已收藏
                {% else %}
                    ♡ 收藏
                {% endif %}
            </button>
        </form>
        {% endif %}
    </div>
    <time>{{ post.created_at|date:"Y-m-d" }}</time>
    <div class="content">{{ post.content|safe }}</div>
    <a href="{% url 'index' %}" class="back-link">← 返回首页</a>
</article>
{% endblock %}

在首页文章卡片中也加入收藏按钮:

实例

<!-- 在 index.html 的文章卡片中加入收藏按钮(footer 区域) -->
<div class="article-card">
    <div class="card-content">
        <span class="card-category">{{ post.category.name }}</span>
        <h3>{{ post.title }}</h3>
        <p>{{ post.summary|truncatechars:80 }}</p>
        <div class="card-footer">
            <span class="card-date">{{ post.created_at|date:"Y-m-d" }}</span>
            {% if user.is_authenticated %}
            <form method="post" action="{% url 'toggle_favorite' post.pk %}" class="fav-form">
                {% csrf_token %}
                <button type="submit" class="fav-btn-small">
                    {% if user in post.favorites.all %}&#x2665;{% else %}♡{% endif %}
                </button>
            </form>
            {% endif %}
        </div>
    </div>
</div>

为什么收藏按钮用 form 而不是 a 标签?因为收藏操作修改了数据库,属于 POST 请求。按照 REST 规范,读操作用 GET,写操作用 POST。更重要的是,POST 请求携带 csrf_token,防止恶意网站伪造请求。


收藏列表页面

为登录用户展示已收藏的文章列表。

实例

# 文件路径:blog/views.py 新增
@login_required
def favorites(request):
    """当前用户的收藏列表"""
    # 通过 related_name 反向查询用户的收藏文章
    posts = request.user.favorite_posts.all().order_by('-created_at')
    return render(request, 'blog/favorites.html', {
        'posts': posts,
        'title': '我的收藏 - RUNOOB 博客'
    })

在 urls.py 中添加路由,在导航栏添加「我的收藏」链接。


收藏按钮样式

实例

/* 在 base.html style 中追加 */
.post-header {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
}

.fav-btn, .fav-btn-small {
    background: none;
    border: none;
    font-size: 18px;
    cursor: pointer;
    color: #e74c3c;
    padding: 4px 8px;
    transition: transform 0.2s;
}

.fav-btn:hover, .fav-btn-small:hover {
    transform: scale(1.2);
}

.fav-btn-small {
    font-size: 16px;
}

.fav-form {
    display: inline;
}

.card-footer {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-top: 10px;
}

本章小结

本章你掌握了 Django 中的用户交互处理:ManyToManyField 建立多对多关系、POST 请求处理表单、csrf_token 安全防护、login_required 装饰器保护视图、request.user 判断当前用户身份。

收藏功能让用户可以标记喜欢的文章,并在个人收藏列表中查看。