フォームとお気に入り機能

この章では、ユーザーのPOSTリクエストの処理、多対多関係の構築、そして「記事のお気に入り登録」機能の実装を学びます。


お気に入り機能のデータモデル設計

お気に入り機能の核心は次のとおりです:1人のユーザーは複数の記事をお気に入り登録でき、1つの記事は複数のユーザーにお気に入り登録できます。。

これは典型的な多対多(ManyToMany)関係です。

新しいモデルを作成する必要はありません。DjangoのUserモデルは組み込みなので、PostモデルにUserを指すManyToManyFieldを追加するだけです。

実例

# ファイルパス: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 }} - サンプル ブログ{% 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>

なぜお気に入りボタンは a タグではなく form を使うのでしょうか?お気に入り操作はデータベースを変更するため、POSTリクエストに該当します。RESTの規範に従い、読み取り操作はGET、書き込み操作はPOSTを使用します。さらに重要なのは、POSTリクエストはCSRFトークンを携帯しているため。csrf_token、悪意のあるWebサイトによるリクエスト偽造を防止します。


お気に入り一覧ページ

ログインユーザーにお気に入り登録した記事の一覧を表示します。

実例

# ファイルパス: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': 'マイお気に入り - サンプル ブログ'
    })

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による現在のユーザー判定です。

お気に入り機能により、ユーザーは好きな記事にマークを付け、個人のお気に入り一覧で確認できます。

その他の拡張