DjangoアプリにFacebookやTwitterなどでおなじみのいいね機能を実装する方法について解説します。いいね機能自体は汎用ビューのCreateViewなどで簡単に実装できますが、こういった機能はJavaScriptのAjaxを使ってページ遷移を伴わない非同期通信で処理するのが一般的です。
※この記事では弊サイトの Django入門編 を終えている前提で進めます。また、機能の実装先もDjango入門編で制作した掲示板アプリをベースに進めていきます。
目次
完成イメージ

このように投稿詳細ページにいいねボタンを表示し、ユーザーがクリックすると
このようにハートマークの中が塗られたように見え、いいねしたユーザーの数が表示されるいいね機能を実装します。
いいねボタンは1ユーザーにつき1回までしか押せず、もう一度押した場合いいねを解除します。
今回はログイン前提の機能として実装するため、投稿詳細ページにアクセス制御をかけ、ログインしていない状態でアクセスするとログインページにリダイレクトされるようにします。
テーブル設計

いいね機能に必要な値は「誰が」「どの投稿に」いいねをしたのかの2つです。
Likesテーブルを用意し、いいねをしたユーザーをDjangoが予め用意してくれているUserモデルと紐づけ、いいねされた投稿をArticleテーブルの投稿idと紐づけます。
モデル
上記のテーブル設計をモデルに落とし込みます。
from django.db import models
from django.urls import reverse
class Article(models.Model):
author = models.ForeignKey(
'auth.User',
on_delete=models.CASCADE,
)
content = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.content
def get_absolute_url(self):
return reverse('bbs:detail', kwargs={'pk': self.pk})
class Like(models.Model):
user_id = models.ForeignKey(
'auth.User',
on_delete=models.CASCADE,
)
target = models.ForeignKey(
Article,
on_delete=models.CASCADE
)
モデルの作成が完了したらマイグレーションでデータベースに反映します。
python manage.py makemigrations bbs python manage.py migrate
これでテーブルの用意ができました。
ルーティング
bbs/urls.pyにいいね機能のURLを追記します。
from django.urls import path
from . import views
app_name = 'bbs'
urlpatterns = [
path('', views.IndexView.as_view(), name='index'),
path('<int:pk>/', views.DetailView.as_view(), name='detail'),
path('create/', views.CreateView.as_view(), name='create'),
path('<int:pk>/update/', views.UpdateView.as_view(), name='update'),
path('<int:pk>/delete/', views.DeleteView.as_view(), name='delete'),
path('like/', views.like, name='like'), # 追加
]
ビュー
from django.urls import reverse_lazy
from django.views import generic
from .models import Article, Like # Likeを追加
from django.contrib.auth.mixins import LoginRequiredMixin
from django.core.exceptions import PermissionDenied
from django.http import JsonResponse # 追加
from django.shortcuts import get_object_or_404 # 追加
# 中略
class DetailView(LoginRequiredMixin, generic.DetailView): # アクセス制御を追加
model = Article
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# 投稿に対するいいねの数
like_count = self.object.like_set.count()
context['like_count'] = like_count
if self.object.like_set.filter(user_id=self.request.user).exists():
context['is_user_liked'] = True
else:
context['is_user_liked'] = False
return context
# 中略
def like(request):
article_pk = request.POST.get('article_pk')
context = {
'user_id': f'{ request.user }',
}
article = get_object_or_404(Article, pk=article_pk)
like = Like.objects.filter(target=article, user_id=request.user)
if like.exists():
like.delete()
context['method'] = 'delete'
else:
like.create(target=article, user_id=request.user)
context['method'] = 'create'
context['like_count'] = article.like_set.count()
return JsonResponse(context)
投稿詳細ページのビュー(DetailView)にアクセス制御を追加しログインユーザーのみアクセスできるようにします。
そしてcontextの中身にその投稿がいいねされている数と、ログイン中のユーザーが既にいいねしているかどうか、という2つの情報を持たせます。
likeというビュー関数を作成します。これはいいねボタンがクリックした時に呼び出される関数です。
その中でその投稿の詳細ページを呼び出し、いいねテーブルの中から「ログイン中のユーザーかつその投稿」のものを探します。
該当するデータがあればmethodにdeleteを代入、なければmethodにcreateを格納。
like_countにその投稿に紐づいているいいねレコードの数を格納します。
テンプレート
まずは共通テンプレートです。
ハートマークはFont Awesomeを、CSSフレームワークはMDBを利用するので、base.htmlにこれらを読み込むよう記述します。
# 前略
<title>Djangoを使ってみよう!</title>
<!-- Font Awesome -->
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/css/all.min.css" rel="stylesheet" />
<!-- MDB -->
<link href="https://cdnjs.cloudflare.com/ajax/libs/mdb-ui-kit/3.10.1/mdb.min.css" rel="stylesheet" />
# 中略
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/mdb-ui-kit/4.0.0/mdb.min.js"></script>
{% block extrajs %}
{% endblock %}
</body>
</html>
また、非同期通信にAjaxを利用するのでその読み込みと、いいねボタンが押された時に動作するJavaScriptを入れるためのextrajsブロックも用意しました。
最後に詳細ページのテンプレートにいいねボタンの表示と押された時の処理を追記します。
{% block extrajs %}
<script type="text/javascript">
// いいねボタンが押された時
document.getElementById('ajax-like').addEventListener('click', e => {
e.preventDefault();
const url = '{% url "bbs:like" %}';
fetch(url, {
method: 'POST',
body: `article_pk={{ article.pk }}`,
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8',
'X-CSRFToken': '{{ csrf_token }}',
},
}).then(response => {
return response.json();
}).then(response => {
// いいね数を書き換える
const counter = document.getElementById('like-count')
counter.textContent = response.like_count
const icon = document.getElementById('like-icon')
// いいねした時はハートを塗る
if (response.method == 'create') {
icon.classList.remove('far')
icon.classList.add('fas')
icon.id = 'like-icon'
} else {
icon.classList.remove('fas')
icon.classList.add('far')
icon.id = 'like-icon'
}
}).catch(error => {
console.log(error);
});
});
</script>
{% endblock %}
{% block content %}
<h1>{{ article.id }}の投稿詳細ページ</h1>
<div class="container">
<p>{{ article.author }}:{{ article.created_at }}</p>
<p>{{ article.content }}</p>
<div class="card-header">
{% if is_user_liked %}
<button type="button" id="ajax-like" style="border:none;background:none">
<!-- すでにいいねしている時はfasクラス -->
<i class="fas fa-heart text-danger" id="like-icon"></i>
</button>
{% else %}
<button type="button" id="ajax-like" style="border:none;background:none">
<!-- いいねしていないときはfarクラス -->
<i class="far fa-heart text-danger" id="like-icon"></i>
</button>
{% endif %}
<!-- いいねの数 -->
<span id="like-count">{{ like_count }}</span>
<span>いいね</span>
</div>
<!-- ユーザー情報がその投稿のものと一致する場合 -->
{% if request.user.id == object.author_id %}
<p><a href='{% url "bbs:update" article.pk %}'>編集</a></p>
<p><a href='{% url "bbs:delete" article.pk %}'>削除</a></p>
{% endif %}
</div>
<p><a href='{% url "bbs:index" %}'>一覧ページへ戻る</a></p>
{% endblock %}
extrajsブロックではいいねボタンが押された時と外された時で、いいねアイコン部分のHTMLに付けるクラス(farとfas)を変えます。
あとはcontentブロックに、ユーザーがそのページにアクセスした時にも、既にいいねしている場合はfasクラスが付いたソースを、いいねしていない時はfarクラスが付いたソースを表示するように記述します。
動作確認

アプリにログインして投稿詳細ページにアクセスするといいねボタンが表示されています。
ボタンをクリックすると
アイコン部分に付けているクラス名が変わり、適用されるCSSが変わったことでハートマークが塗られたように見せることができています。
いいねされた数も増えました。
もう一度いいねボタンをクリックすると
またクラス名が変わり塗られていないハートが表示されます。
いいね数も0に戻っていますね。
別のユーザーを作って同じ記事にいいねしてみると、ちゃんといいねしているユーザーの数がカウントされているのが確認できます。







