Djangoで簡単にいいね機能を実装する

Django
DjangoPython
0

本記事ではDjangoで簡単にいいね機能を実装する方法を紹介する。いいね機能に必要なAjaxを実装するために使うjQueryのコードの書き方も詳しく説明する。

スポンサーリンク

はじめに

いいね機能を実装するにあたり、Ajaxを利用する。Ajaxは非同期通信、すなわち画面遷移なく画面操作をできるようにするものである。(いいねを押して別の画面に遷移してほしくないはず。)
Ajaxについても詳しくこの記事で説明する。

また「いいね」はFontAwesomeのハートを利用する。FontAwesomeの使い方については説明しない。
いいねを1回押したらピンクになって、もう1度押すと元の画像に戻るという仕様である。

また、この記事はDjangoの初めてのアプリ作成(pollsのチュートリアル)くらいの知識は前提としているので、ファイル名の説明とかは省略している。

環境

  • Python 3.7.9
  • Django 3.1.3
スポンサーリンク

データベースの定義

テーブルの作成

models.pyに以下のように記事といいねのテーブルをそれぞれ作る。

from django.db import models
from django.utils import timezone
from django.contrib.auth.models import User

class Article(models.Model):
    title = models.CharField(max_length=100, verbose_name='タイトル')
    text = models.TextField(verbose_name='本文')
    timestamp = models.DateTimeField(default=timezone.now)

class Like(models.Model):
    article = models.ForeignKey(Article, on_delete=models.CASCADE)
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    timestamp = models.DateTimeField(default=timezone.now)

Likeは後々の拡張のために別テーブルで作った。
誰が、どの記事に、いつ、いいねしたのかを記録する。

次にmigrateする。

python manage.py makemigrations
python manage.py migrate

Adminページで編集できるようにする

Admin.pyを次のように編集する。

from django.contrib import admin
from .models import Article, Like

admin.site.register(Article)
admin.site.register(Like)

保存して、superuserを作成し、サーバーを立ち上げる。

python manage.py createsuperuser
...
python manage.py runserver

http://127.0.0.1:8000/adminに行き、作成したsuperuserでログインすると記事やいいねを操作するUIがあるので適当に記事を増やしておく。

記事一覧のページを作成

mysite/{自分のapp名}/templatesを作成する。

  • templates
    • articles.html
    • like.html

という2つのhtmlファイルを作成する。
articles.htmlは記事一覧を表示し、like.htmlはいいねボタンを表示する。

urls.pyの編集

htmlを表示するためにmysite/{自分のapp名}/urls.pymysite/mysite/urls.pyを編集する。

mysite/{自分のapp名}/urls.py

from django.urls import path

from . import views

urlpatterns = [
    path('', views.ArticlesView, name='articles'),
    path('like', views.LikeView, name='like'),
]

mysite/mysite/urls.py

from django.contrib import admin
from django.urls import include, path

urlpatterns = [
    path('', include('mural.urls')),
    path('admin', admin.site.urls),
]

views.pyの編集

htmlを表示するためにmysite/{自分のapp名}/views.pyを編集する。

from django.shortcuts import get_object_or_404, render
from .models import Article, Like
from django.http import JsonResponse

def ArticlesView(request):
    articles = Article.objects.all()
    liked_list = []
    for article in articles:
        liked = article.like_set.filter(user=request.user)
        if liked.exists():
            liked_list.append(article.id)

    context = {
        'articles': articles,
        'liked_list': liked_list,
    }

    return render(request, '{自分のapp名}/articles.html', context)

def LikeView(request):
    if request.method =="POST":
        article = get_object_or_404(Article, pk=request.POST.get('article_id'))
        user = request.user
        liked = False
        like = Like.objects.filter(article=article, user=user)
        if like.exists():
            like.delete()
        else:
            like.create(article=article, user=user)
            liked = True
    
        context={
            'article_id': article.id,
            'liked': liked,
            'count': article.like_set.count(),
        }

    if request.is_ajax():
        return JsonResponse(context)

ArticlesViewについて

urls.pyで定義したようにarticles.htmlが呼ばれたときに実行される。

liked_listというものを用意し、閲覧しているユーザー(request.userで取得)が過去にどの記事をいいねしたかを格納しておく。

liked = article.like_set.filter(user=request.user)

この部分はlike_setでarticleに紐づく全てのいいねを取得し、閲覧しているユーザーでフィルターをかけている。

LikeViewについて

urls.pyで定義したようにlike.htmlが呼ばれたときに実行される。
最初にif request.method =="POST":としているように、POSTつまり「いいねの押下」で呼ばれなければ何もしない。
また、最後にif request.is_ajax():で返しているようにAjax通信でなければ何も返さない。

「いいねの押下」によって呼ばれると、ユーザーがすでにその記事をいいねしているかどうかを調べ、

  • いいねしていたら、いいねテーブルから削除する。
  • いいねしていなければ、いいねテーブルに追加する。

return JsonResponse(context)はAjax通信でJson形式で必要な次の値を返している。

  • 記事のID
  • いいねしたのか(True)、取り消したのか(False)
  • 記事のいいね総数

この辺りはまた下で詳しく解説する。

HTMLを書く

完全なHTMLを書くと長くなってしまうので必要な部分だけ抽出する。

like.html

<form action="{% url 'like' %}" method="POST">
    {% csrf_token %}
    {% if article.id in liked_list %}
        <button id="like" name="{{article.id}}"><i class="fas fa-lg fa-heart like-red"></i></button>
    {% else %}
        <button id="like" name="{{article.id}}"><i class="far fa-lg fa-heart"></i></button>      
    {% endif %}
</form>
<p name="{{article.id}}-count" class="count"> {{ article.like_set.count }} </p>

まず、このlike.htmlarticles.html内に埋め込まれているので、最初にarticles.htmlが開かれてlike.htmlが呼ばれたときは前述した通りLikeViewは「何もしない」。したがって、ArticlesViewで定義した動作になる。

したがって、ArticlesViewで定義しているliked_listに対象の記事が入っていれば塗りつぶされたハートを、入っていなければ塗りつぶされていないハートを表示する。
namevalueなどはAjax通信で使う。詳しくは後述する。

<p name="{{article.id}}-count" class="count"> {{ article.like_set.count }} </p>

最後のこの部分で記事の総いいね数を書く。

articles.html

<h2>記事一覧</h2>

<div calss="articles-outer">
    {% for article in articles %}
    <div class="article">
        <h2>{{ article.title }}</h2>
        <div class="like-outer">
            {% include '{自分のapp名}/like.html' %}
        </div>
        <hr>
    </div>
    {% endfor %}
</div>

<script type="text/javascript">
    $(document).ready(function(event){
        $(document).on('click', '#like', function(event){
            event.preventDefault();
            $.ajax({
                type: 'POST',
                url: "{% url 'like' %}",
                data: {
                    'article_id': $(this).attr('name'),
                    'csrfmiddlewaretoken': '{{ csrf_token }}'},
                dataType: 'json',
                success: function(response){
                    selector = document.getElementsByName(response.article_id);
                    if(response.liked){
                        $(selector).html("<i class='fas fa-lg fa-heart'></i>");
                    }
                    else {
                        $(selector).html("<i class='far fa-lg fa-heart'></i>");
                    }
                    selector2 = document.getElementsByName(response.article_id + "-count");
                    $(selector2).text(response.count);
                }
            });
        });
    });
</script>

前半部分は記事の数だけforループでタイトルといいねのハートと数を表示する。後半部分の<script>...</script>がAjax用のコードである。
詳細はセクションを分けて説明する。

Ajaxの説明

まず、Ajax通信をするためにjQueryを使う必要がある。jQueryはJavaScriptで行うことを簡単に記述できるようにするJavaScriptライブラリである。
以下のコードを<head>...</head>に追加する。

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>

articles.htmlの後半に書かれている以下の部分がAjax通信を行うためのコードである。

<script type="text/javascript">
    $(document).ready(function(event){
        $(document).on('click', '#like', function(event){
            event.preventDefault();
            $.ajax({
                type: 'POST',
                url: "{% url 'like' %}",
                data: {
                    'article_id': $(this).attr('name'),
                    'csrfmiddlewaretoken': '{{ csrf_token }}'},
                dataType: 'json',
                success: function(response){
                    selector = document.getElementsByName(response.article_id);
                    if(response.liked){
                        $(selector).html("<i class='fas fa-lg fa-heart'></i>");
                    }
                    else {
                        $(selector).html("<i class='far fa-lg fa-heart'></i>");
                    }
                    selector2 = document.getElementsByName(response.article_id + "-count");
                    $(selector2).text(response.count);
                }
            });
        });
    });
</script>

id=likeとして定義されたいいねbuttonをクリック(POST)されたら、Ajaxはlike.htmlを呼び出し、必要なデータ(button内のname属性の値)をjson形式で送る。
'csrfmiddlewaretoken': '{{ csrf_token }}'はDjangoでPOST送信する場合に必要なトークンである。

通信に成功すると、success: function(response){...}を実行する。
responseにはLikeViewで定義した3つの返り値が含まれている。

selector = document.getElementsByName(response.article_id);でいいねされた記事の<button>...</button>部分を選択する。
response.likedでハートを塗りつぶすかどうかを変える。

selector2 = document.getElementsByName(response.article_id + "-count");でいいねされた記事のいいね総数を表示する部分を選択し、$(selector2).text(response.count);で更新する。

おわりに

これでいいねの実装は完了で、あとは適当にCSSをいじれば良い。

しかし、一般に公開すると誰もいいねすることができないし、そもそも記事一覧ページを開くこともできない。
これはいいねをユーザーのみで管理しているからであり、この記事ではsuperuserに登録した本人しかいいねできない。

そこでSessionを使って管理すると良いのだが、記事が長くなってしまったので別記事で紹介する。

何かミスや誤植があればCommentください。

スポンサーリンク
H-MEMO

コメント