[Django] いいね機能の追加

前回までにブログの基本的な機能を作成しました。
今回は、いいね機能を実装してみたいと思います。

シリーズ一覧

  1. プロジェクトの作成とアプリケーションの作成
  2. view関数の書き方
  3. URLの指定の仕方(URLディスパッチャ)
  4. データベース(モデル)の設定
  5. 記事の投稿ページの作成
  6. 記事の一覧ページと詳細ページの追加
  7. 記事の編集ページと記事の削除機能の追加
  8. いいね機能の追加 <- 今回
  9. コメント機能の追加
  10. Bootstrapを利用したwebデザイン

データベースの確認

データベースの記事のテーブルに新しく「いいね」の数を保存するカラムが必要になりました。
なので、models.pyを編集して「いいね」数を保存できるように変更します。

まず、編集する前のモデル(データベース)を確認します。
models.py

from django.db import models

class Article(models.Model):
    title = models.CharField(max_length=128)
    text = models.TextField()
    posted_at = models.DateTimeField(auto_now_add=True)
    last_modify = models.DateTimeField(auto_now=True)
    def __str__(self):
        return self.title

おそらくこうなっていると思います。

これにlike = models.IntegerField(default=0)を足して、いいねの数を保存できるようにします。
models.py

from django.db import models

class Article(models.Model):
    title = models.CharField(max_length=128)
    text = models.TextField()
    posted_at = models.DateTimeField(auto_now_add=True)
    last_modify = models.DateTimeField(auto_now=True)
    like = models.IntegerField(default=0) # 追記
    def __str__(self):
        return self.title

models.IntegerFieldは数値を保存します。
default=XXを指定するとデフォルト値を設定できます。他のフィールドでも同様に使えます。

変更が完了したら

python manage.py makemigrations

を実行してDjangoにモデルが変更されてことを教えてあげて、
出力

Migrations for 'blog':
  blog\migrations\0002_article_like.py
    - Add field like to article

データベースを更新します。

python manage.py migrate

出力

Operations to perform:
  Apply all migrations: admin, auth, blog, contenttypes, sessions
Running migrations:
  Applying blog.0002_article_like... OK

これで、データベースに新たにいいね数を保存するためのカラムが追加されました。

ちなみに、既存のモデル(テーブル)に新しく追加するフィールド(カラム)にdefaultを指定しない場合、

# default = XX  をつけない場合
like = models.IntegerField()

Djangoにモデルが変更されてことを教える(makemigrations)際に
「デフォルト値がないけど既に登録済みのデータには追加されたカラムに何セットするの?」
と聞かれます。

You are trying to add a non-nullable field 'like' to article without a default; we can't do that (the database needs something to populate existing rows).
Please select a fix:
 1) Provide a one-off default now (will be set on all existing rows with a null value for this column)
 2) Quit, and let me add a default in models.py
Select an option:

ここで「1」を選択すれば、コンソールにデフォルト値にしたいものを入力すればいいのですが、面倒なのであらかじめモデルにフィールドを足すときに、defaultを設定しておきましょう。

URL

http://localhost:8000/article/数字/likeにアクセスするといいねの数が増えるようにします。
urls.py

from django.urls import path
from . import views

urlpatterns = [
    path("", views.index, name="index"),
    # 略
    path("article/<int:pk>/delete/", views.delete, name="delete"),
    path("article/<int:pk>/like/", views.like, name="like"), # 追記
]

view

今度は、views.pyにいいねボタンを押されたときの動作を記述します。

def like(request, pk):
    try:
        article = models.Article.objects.get(pk=pk)
    except models.Article.DoesNotExist:
        raise Http404
    article.like += 1 # ここでいいねの数を増やす
    article.save() # 保存をする
    return redirect(view_article, pk)

何度も言いますが、saveメソッドを実行しないとデータが保存されないので注意

いいねボタンが押されたら詳細ページにリダイレクトするようにしています。

テンプレート

詳細ページのテンプレートにいいねボタンを追加します。
view_article.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>{{ article.title }}</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
    <p><a href="{% url 'index' %}">トップページへ</a></p>
    <p><a href="{% url 'article_all' %}">一覧へ戻る</a></p>
    <h3>{{ article.title }}</h3>
    <small>投稿日時 : {{ article.posted_at }}</small>
    <small>最終更新 : {{ article.last_modify }}</small>
    <p>{{ article.text }}</p>
    
    <p><a href="{% url 'like' article.pk %}"><span id="like">{{ article.like }}</span>いいね!</a></p>
    <!-- いいねボタンを追記 -->
</body>
</html>

確認

さあ、これでいいね機能の追加ができたはずなので確認してみましょう。
Djangoサーバーを起動して

python manage.py runserver

http://localhost:8000/article/all/

にアクセス

詳細ページに移動して、いいねボタンをクリック!

クリックしたらいいねの数が増えることを確認しましょう

web API

いいね機能の追加は完了しました。

がしかし、

今の状態だといいねボタンをクリックするたびにページがロードされます。
これでは、表示に時間もかかるし、データ通信量も爆増です。

なので、いいねボタンを押しても、ページ全体をロードしなくてもいいように改造します。

ページの要素を部分的に書き換えるにはやっぱりjavascriptですよね。

しかし、javascriptだけでは直接、データベースにアクセスできません。

なので、データーベースの「いいね」の数を増やすことができる機能をweb APIとして、提供してあげます。

URLの設定

web APIを提供するにはアクセスさせるための入り口URLをつくってあげる必要があります。
http://localhost:8000/api/like/数字/にアクセスするといいねの数が増えるようにします。

urls.py

from django.urls import path
from . import views

urlpatterns = [
    path("", views.index, name="index"),

    # 略

    path("article/<int:pk>/like/", views.like, name="like"),
    path("api/like/<int:pk>/", views.api_like, name="api_like"),
]

viewの設定

web API用のviewapi_likeを定義します。
views.py

from django.shortcuts import render, redirect
from django.http import Http404
from django.http.response import JsonResponse # 追記
from . import models

# 略

def api_like(request, pk):
    try:
        article = models.Article.objects.get(pk=pk)
    except models.Article.DoesNotExist:
        raise Http404
    article.like += 1  # ここでいいねの数を増やす
    article.save()  # 保存をする

    # ここまでは like関数と同じ

    return JsonResponse({"like":article.like}) # <- ここが特別

基本的にやっていることはlike関数と同じですが最後にrender関数ではないJsonResponseという関数を使っています。

これは、HTMLではなくjson形式のデータを送るための関数です。
引数には辞書型をとります。

json

jsonはjavascriptで効率的にデータを表現できる汎用データフォーマットです。
様々なプログラミング言語でパーサが提供されているので、web APIでのデータ通信の標準フォーマットになりつつあります。

最後にjson形式のデータでarticle.likeを送信したのは、この値でウェブページ上の「いいね」の数を書き換えるためです。

テンプレート(javascript)

URLとviewを設定したのでいよいよjavascriptでいいね数を増やせるようにします。

javascriptweb APIを実行するには、XMLHttpRequestを利用します。

function api_like() {
    var api_url = web APIのURL;
    var btn_txt = document.getElementById("like");
    var request = new XMLHttpRequest();
    request.onreadystatechange = function () {
        if (request.readyState === 4 && request.status === 200) {
            var received_data = JSON.parse(request.responseText);
            btn_txt.innerText = received_data.like;
            }
        }
    request.open("GET", api_url);
    request.send();
}

request.onreadystatechangeで通信が完了した後に実行する動作を記述します。

通信が完了したらJSON.parse(request.responseText)でAPIの実行結果をJson形式として受け取ります。
btn_txt.innerText = received_data.likeでHTMLのいいね部分の数字を書き換えています。

これをテンプレート(HTML)のheadタグの中に加えます。
view_article.html

<!-- 略 -->
<head>
    <meta charset="utf-8" />
    <title>{{ article.title }}</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <script>
        function api_like() {
            var api_url = "{% url 'api_like' article.pk %}";
            var btn = document.getElementById("like");
            var request = new XMLHttpRequest();
            request.onreadystatechange = function () {
                if (request.readyState === 4 && request.status === 200) {
                    var received_data = JSON.parse(request.responseText);
                    btn.innerText = received_data.like;
                }
            }
            request.open("GET", api_url);
            request.send();
        }
    </script>
</head>
<!-- 略 -->

さらに、いいねボタン部分

<p><a href="{% url 'like' article.pk %}"><span id="like">{{ article.like }}<span>いいね!</a></p>

<p><a onclick="api_like()"><span id="like">{{ article.like }}</span>いいね!</a></p>

に書き換えます。

これで、設定が完了したので動作チェックしてみます。

詳細ページに移動して、いいねボタンをクリック

きちんと数は増えましたか?

まとめ

モデルを更新したら

python manage.py makemigrations

python manage.py migrate

を実行する。

JsonRespons(dictionary)

jsonのレスポンスを返せる。

最後に

実は、記事の削除機能もいいね機能と同様web APIとして機能を提供すれば、ページのリロードなしで削除できたりするんですが、それはまたの機会にでも

次回は、コメント機能を追加してみようと思います。

※2019/1/25一部コマンドが間違っていたので修正