【Webアプリ】Djangoでコメント機能の実装

必要な前提知識

コメント機能を実装するにあたって、いくつか必要な基本知識があります。

GETメソッド・POSTメソッド

WebアプリやWebサイトにおいて、クライアントとサーバーは互いに通信する必要があります。

この通信について、クライアントからサーバーへの通信をリクエスト、サーバーからクライアントへの通信をレスポンスといいます。

何かページを表示する際は

【リクエスト】クライアントが「○○.htmlというページをください。」と要求

【レスポンス】サーバーが「はい。これ(○○.html)です。」と応答

これがやり取りの1セットです。

このやり取りは「HTTP」というプロトコル、にすなわちクライアントとサーバーが通信をする際のルールとして決められています。

リクエストの中身は以下のような構成になっています。

・リクエストライン・・・「何のメソッドで」「サーバーのどこに送るか」を格納する部分です。ブラウザでサイトにアクセスする際にURLバーのところに書く情報が大部分を占めています。

・リクエストヘッダ・・・ユーザーエージェントやリファラなど、クライアントの情報が格納されます。Djangoにおいて自力でこの部分を実装することほぼないので、以降の説明では無視しています。

・メッセージボディ・・・主にPOSTメソッドでパラメータを格納する部分です。

※細かい説明は色々と端折っています。

さらに、リクエストにはいくつか「メソッド(HTTPメソッド)」が存在します。その中でも特に使われるのが、「GETメソッド」と「POSTメソッド」です。(他にもPUTやDELETEなどがありますが、GETやPOSTで代替可能なので、まずはこの2つを覚えましょう)

GETメソッド

サーバーに存在するリソース(ページなど)の取得だけを行いたい際に使用します。

例1:

例2:

上の例1のように「○○のリソース(=ページ)が欲しい」だったり、例2のようにリソースの要求と同時にクエリと呼ばれるサーバーに渡したい情報リクエストラインで記述することもできます。

クエリについては、例えば複数言語に対応したWebサイトにおいて、日本語のページをレスポンスとしてほしい場合に、「?lang=ja」のように指定したりといった使い方があります。他には、検索サイトの検索ワードをクエリ部分にくっつける場合もあります。ポイントは、?以降に書くということです。

GETメソッドの特徴をまとめると、

・リソースの要求だけを行う際に使う

・リソースの要求と同時に何か情報を送ることもできるが、その情報はリクエストラインというユーザーからすぐに見える部分に格納する。

POSTメソッド

サーバーに対して、リソースの取得以外にも何らかの処理をして欲しいときに使用します。

GETメソッドとは違い、メッセージボディに色々なパラメータが格納されています。

ここでは、上の図の例では、サーバーの/send-commentという場所にメッセージボディの情報を送信しています。(送信後はDBに保管するといった具合です)

POSTメソッドの主な用途は、フォームの送信があげられます。

POSTメソッドの特徴をまとめると、

・サーバーに処理をしてほしい際に使う

・情報を送信する際に、ユーザーがすぐには見ることができない場所に格納されます。

データベース

送信されたコメントを蓄積しておく機能を作るのに必要な知識となります。

以下の記事が参考になります。

【Django入門】Databaseの使い方 | 侍エンジニアブログ
この記事では「 【Django入門】Databaseの使い方 」といった内容について、誰でも理解できるように解説します。この記事を読めば、あなたの悩みが解決するだけじゃなく、新たな気付きも発見できることでしょう。お悩みの方はぜひご一読くださ...

ページ表示までの実装

まずはページが表示できるところまでを作成します。

プロジェクト立ち上げ・アプリ作成・urls.pyの作成・言語とタイムゾーンの設定

新たにDjangoのプロジェクトを立ち上げて実装します。

プロジェクトの立ち上げからアプリの立ち上げまで一気に行います。3行ともコピーして、プロジェクトを立ち上げたい場所で開いたターミナルにペースト、最後にエンターすれば3つのコマンドが実行されます。

python -m django startproject project
cd project
python manage.py startapp app

次に、アプリ側のurls.pyを新規作成します。

VSCodeの場合、左側のエクスプローラーの「app」をクリックして選択した状態で、右クリック>「新しいファイル」で「urls.py」というファイルを作成してください。

appフォルダ内が以下のようになればOKです。

続いて、settings.pyにアプリを追加&言語とタイムゾーンの設定を行います。

project>project>settings.pyのINSTALLED_APPSの部分に以下の記述を追加。

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'app', #追加
]

次に、Djangoの言語とタイムゾーンの設定。

LANGUAGE_CODE = 'ja' #変更

TIME_ZONE = 'Asia/Tokyo' #変更

記述したら、「Ctrl」キー+「S」で保存します。

テンプレート(html)用のフォルダを作成・設定に追加

2つ目のprojectフォルダーやappフォルダーの並びにtemplatesフォルダーを作成します。

VSCodeの場合、左側のエクスプローラーで1つ目のprojectフォルダをクリックして選択した状態で右クリック>「新しいフォルダー」から「templates」フォルダーを作成します。

以下のような構成になればOKです。

続いて、templatesフォルダをDjangoに読み込んでもらえるように、settings.pyに設定します。

project>project>settings.py

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [BASE_DIR, 'templates'], #追記
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

静的ファイル用のフォルダを作成・設定に追加

htmlファイルはtemplatesフォルダに格納しますが、css・js・画像などは静的ファイル用のフォルダに格納します。

templatesフォルダの時と同様に、appフォルダや2つ目のprojectフォルダ、templatesフォルダの並びに「static」フォルダを作成します。

以下のような構成になればOKです。

続いて、staticフォルダをDjangoに読み込んでもらえるように、settings.pyに設定します。

project>project>settings.py

STATIC_URL = 'static/'
#追記
STATICFILES_DIRS = [
    BASE_DIR / 'static',
]

htmlやcssをtemplatesに追加

以下、デモページです。

templatesフォルダの中に「page1.html」として保存してください。

{% load static %}
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <link rel="stylesheet" href="{% static 'css/page1.css' %}">
    <title>ページ1</title>
</head>
<body>
    <header>
        <h1>ページ1です</h1>
    </header>
    <div class="button">
        <a href="{% url 'page2' %}">ページ2へ</a>
    </div>
    <div class="main">
        <p>こんにちは!</p>
    </div>
</body>
</html>

static>cssフォルダの中に「page1.css」として保存してください。

body {
    background-color: skyblue;
    color: black;
    font-family: Arial, sans-serif;
}

header {
    text-align: center;
    padding: 1em;
    background-color: #00bfff;
}

.button {
    text-align: center;
    margin: 1em 0;
}

.button a {
    color: black;
    text-decoration: none;
    padding: 0.5em 1em;
    background-color: #00bfff;
    border-radius: 5px;
}

.button a:hover {
    background-color: #87cefa;
}

.main {
    text-align: center;
    padding: 2em;
}

templatesフォルダの中に「page2.html」として保存してください。

{% load static %}
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <link rel="stylesheet" href="{% static 'css/page2.css' %}">
    <title>ページ2</title>
</head>
<body>
    <header>
        <h1>ページ2です</h1>
    </header>
    <div class="button">
        <a href="{% url 'page1' %}">ページ1へ</a>
    </div>
    <div class="main">
        <p>こんばんは!</p>
    </div>
</body>
</html>

static>cssフォルダの中に「page2.css」として保存してください。

body {
    background-color: skyblue;
    color: black;
    font-family: Arial, sans-serif;
}

header {
    text-align: center;
    padding: 1em;
    background-color: #00bfff;
}

.button {
    text-align: center;
    margin: 1em 0;
}

.button a {
    color: black;
    text-decoration: none;
    padding: 0.5em 1em;
    background-color: #00bfff;
    border-radius: 5px;
}

.button a:hover {
    background-color: #87cefa;
}

.main {
    text-align: center;
    padding: 2em;
}

htmlファイルについてはtemplatesフォルダに

cssファイルはstaticフォルダの中にさらに「css」フォルダを作成してその中に格納します。

以下のような構成になればOKです。

解説

page1.htmlを参照しながら解説します。

まず1行目ですが、{% load static %}とあります。これはDjangoテンプレートの「組み込みタグ」と呼ばれるもののひとつで、そのあとのcssの読み込み部分(=staticファイル読み込み部分)の<link rel=”stylesheet” href=”{% static ‘css/page1.css’ %}”>のためのおまじないだと思ってください。

組み込みタグは{% ○○ %}のような書き方をし、Djangoがhtmlファイルをクライアントに返却する際に何らかの処理を加える箇所になります。これはhtmlの構文ではなく、Djangoのテンプレート特有の書き方である点に注意してください。

<link rel=”stylesheet” href=”{% static ‘css/page1.css’ %}”>の部分ではスタイルシートの読み込みのためのパス指定(href=””の部分)で組み込みタグが使われています。このように書くことで、static>css>page1.cssを読み込んでくれます。

次に、以下の部分。

<div class="button">
    <a href="{% url 'page2' %}">ページ2へ</a>
</div>

page2へのリンクを張っていて、そのリンクをボタンとして表示しています。

aタグのhref属性で{% url ‘page2’ %}と書いていますが、これがDjangoのテンプレートにおけるリンクの張り方です。ひな形は以下の通りです。

{% url '<urls.pyのname引数に与えた名前>' %}

移動先のページとしてurls.pyのname引数に与えた名前を指定する点に注意してください。htmlファイルの名前や、views.pyの関数の名前を指定してもエラーになります。

<div class=”button”>~</div>のように、aタグ全体をdivタグで囲っているのは、本来リンクであるはずのaタグをボタンの見た目にするためのテクニックです。buttonという名前(ネーミングはなんでもいいです)をつけ、page1.cssでボタンのような見た目にしています。

views.pyの実装

まずはページ表示を行うだけの関数を実装します。

project>app>views.py

from django.shortcuts import render

#以下を追記
def page1(request):
    return render(request, 'page1.html')

def page2(request):
    return render(request, 'page2.html')

プロジェクト側のurls.pyの実装

project>project>urls.py

from django.contrib import admin
from django.urls import path, include #includeを追加

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include("app.urls")), #追記
]

アプリ側のurls.pyの実装

project>app>urls.py

from django.urls import path
from . import views

urlpatterns = [
    path('page1/', views.page1, name='page1'),
    path('page2/', views.page2, name='page2'),
]

ここのname=’page1′name=’page2′で指定している名前をテンプレート内でリンクを張る際に使うことは先ほど述べたとおりです。

ひとまず動作確認

ここまでの作業で、2つのページが表示されるようになったはずです。

python manage.py runserver

「http://127.0.0.1:8000/page1」や「http://127.0.0.1:8000/page2」が表示されるかどうか、ページ内のリンクをクリックするとページが移動できるか、CSSが適用されているかどうかなどを確認してみてください。

コメント機能【送信】の実装(途中)

いよいよコメント機能をページに追加していきます。まずは送信フォームを作りましょう。

どうやって実装?

コーディングに入る前に、実装方法を考えます。

コメント欄は各記事の下に、今回の場合は2ページともにコメント機能を設置します。

page1.htmlにコメント欄のコードを追加し、page2.htmlにもコメント欄のコードを・・・とすると冗長ですし、もしコメント欄の仕様やデザインを変更したくなった場合に記事の数だけコードの変更作業をしないといけません。

そこで、Djangoのテンプレートの機能である「継承」を使います。(後述)

また、コメント機能はhtmlの標準機能として用意されているフォーム要素(formタグ)を使います。

Djangoのテンプレートの継承とは

あるテンプレートに他のテンプレートを読み込ませることを言います。(テンプレート=htmlファイルと読み替えてもらって大丈夫です。)

複数のテンプレートで共通する部分(例えばヘッダーやフッターなど)を分離して別のテンプレートとしてまとめて置き、同じコードを何度も記述しなくて済むようになります。

テンプレートの継承には2つあります。

1つ目は、複数の子要素が1つの親要素を共有する場合で、extendsといいます。

ヘッダーやフッターの例がまさにそれで、親要素となるヘッダーやフッターは複数のページにわたり、子要素となる記事は毎回異なる内容になります。例えば以下の図のような構成になります。(base.htmlが親の想定)

上記の図はあくまで一例で、ヘッダーやフッターをさらに分割して別のテンプレートにしたい場合、ヘッダーやフッターは子要素になります。

2つ目は、1つ目の逆で、複数の親要素が1つの子要素を共有する場合で、includeといいます。

今回実装したいコメント機能はこちらの方で、コメントは共通した子要素に、記事の部分はページごとに違う親要素となります。例えば以下の図のような構成になります。(comment.htmlが子要素の想定)

親要素と子要素の判別方法は、「DOCTYPE宣言を書いているhtmlファイルの方が親」と覚えておきましょう。(裏を返すと、子要素はDOCTYPE宣言はいりません)

また、継承のパターンの判別方法は、「親要素が共通部分ならextends」「子要素が共通部分ならinclude」です。

コメント用のcomment.htmlを作成

コメント投稿機能だけを載せる用のhtmlファイル「comment.html」をtemplatesフォルダの中に新規作成し、コードを記述します。

project>templates>comment.html

<div class="comment-form">
    <form action="{% url 'send-comment' %}" method="post">
        {% csrf_token %}
        <div class="form-name">
            <label for="name">名前</label>
            <input type="text" name="name" required>
        </div>
        <div class="form-comment">
            <label for="comment">コメント</label>
            <textarea name="comment" required></textarea>
        </div>
        <button type="submit">送信</button>
    </form>
</div>

次に、親要素となるpage1.htmlやpage2.htmlにcomment.htmlをインクルードしてもらう記述を追加します。

page1.htmlとpage2.htmlそれぞれの<body>タグ内に以下を追記しましょう。例えば、</body>の直前に追加することで、記事の最後にコメント欄が出現します。(が、まだ実装途中なので実行してもエラーが出ます)

{% include 'comment.html' %}

例えば以下のような感じで追記します。

views.pyにコメント機能用の関数を追加(未完成)

フォームから送信されたコメントを受け取るためにviews.pyに処理を追加します。ただし、まだデータベースの用意を何もしていません。ひとまずはコメントがバックエンド側にちゃんと届いているかを確認するため、送信されたコメントをprintで標準出力するだけの処理をするコードを書きます。

project>app>views.py

from django.shortcuts import render, redirect #追記

def send_comment(request):
    if request.method == 'POST':
        print(request.POST)
        return redirect("/page1")
    else:
        return redirect("/page1")

実装後は以下のようになります。(関数の並び順は何でもいいです)

解説

if request.method == ‘POST’:では、受け取ったリクエストがPOSTメソッドかを調べています。POSTメソッドで届いている場合はその下のprintなどを実行します。フォームデータはcomment.htmlで「method=”post”」と指定している通り、POSTで送信しているので、普通であればこの条件文はTrueになります。Falseになる状況は、/send-commentにURL直打ちでアクセスした場合が考えられます。(必然的にGETメソッドとなるので)

request.POSTでは、POSTで送られてきたデータを取り出しています。この書き方は覚えておきましょう。

return redirect(“/page1”)では、データ受け取り後にpage1にリダイレクトする処理をしています。リダイレクト先の指定は、今回のようにアプリ側のurls.pyで設定しているパス表記のほか、views.pyの関数名で指定することもできます。

ここがポイントなのですが、Djangoのviews.pyでリクエストを処理する関数は、何らかのHttpレスポンスを返却(return)する必要があります。コメント欄の場合、コメントの送信さえできればレスポンスは必要ないとも思えますが、HTTPのプロトコル上の仕様による制約になります。

ここでは、page1にリダイレクトされるため、page1で書き込んだコメントの送信後にpage1に飛ばされる(見た目上ではリロードされる)形になります。page2でコメントした場合もpage1に飛ばされてしまう仕様なのですが、これを解決するにはURLのパラメーターを取得したり、少し仕様を複雑にする必要があるので、また別の記事で紹介します。

最後に以下の部分。

else:
    return redirect("/page1")

POSTメソッド以外でsend_commment()にリクエストが飛んできた場合、コメントの送信ではない謎のリクエストということになるので、とりあえずpage1.htmlを表示しています。

この処理を追加しないと、例えば/send-commentにURL直打ちでアクセスした際、Httpレスポンスがないとしてエラーが発生します。DjangoのWebアプリにおいて、Djangoが出力するエラーはなるべく発生させないように、こういった形で自前で例外処理を実装してあげるのが望ましいです。

アプリ側のurls.pyにsend_comment用のルーティングを追加

views.pyに処理を追加したのでルーティング処理も追加します。

from django.urls import path
from . import views

urlpatterns = [
    path('page1/', views.page1, name='page1'),
    path('page2/', views.page2, name='page2'),
    path('send-comment/', views.send_comment, name='send-comment'), #追記
]

動作確認

ここまでで、各ページにコメントフォームを載せ、送信するとDjangoの標準出力(ターミナル上)にデータが表示されところまで実装しました。

runserverをして動作確認します。

python manage.py runserver

コメント欄は以下のように表示されます。

例えば以下のように入力して送信すると・・・

ターミナルにリクエストとアクセスログが表示されます。

これで、フロントエンドからバックエンドへデータを渡すことができました!

解説

送信されてきたリクエストの中身を見てみましょう。

<QueryDict: {'csrfmiddlewaretoken': ['4EsKCuGPNsUD9X2jB9ROtjcN78APPq1GakjbVE7fxZljxVGTHv72WEDNUJaw93YH'], 'name': ['花子'], 'comment': ['こんにちは!']}

QueryDict型というDjango特有の型にデータが格納されていますが、ふ~ん程度でOKです。中身は辞書型のキー:バリュー形式なので、データ一個一個を取り出したいときはキーを指定してあげればよいことが分かります。(後ほど実装します)

1つ目のキー「csrfmiddlewaretoken」にはDjangoのセキュリティ対策用のトークンが格納されています。これは開発に当たっては無視してOKです。

2つ目、3つ目にはフォームデータが入っています。テンプレート内で以下のように記述しましたが、このname=””で指定した文字列がキーになります。

<input type="text" name="name" required>
(略)
<textarea name="comment" required></textarea>

コメントをデータベースに格納する部分の実装

Djangoのmodels.pyを実装

さて、ここからはバックエンドに届いたコメントをデータベースに格納していきます。

Djangoにはデータベースを簡単に作成したり、データベースにデータを格納・取り出しできる機能が備わっています。

Djangoでデータベースを作成するには、まずmodels.py(project>appの中)にテーブル構成を定義する必要があります。これを、「モデルの定義」といいます。

models.pyで定義したテーブル構成をもとに、その後実行するコマンドでDjangoがデータベースとテーブルの作成を自動的に行います。

世の中にはデータベースシステムはいくつも存在し、MySQLやPostgreSQLなどが有名ですが、Djangoでは標準でSQLiteを使用します。SQLiteはデータベースが1つのファイルにまとまるので、ミニマムなデータベースを作成したい場合に非常に便利です。もしWebアプリが大規模になる場合は、DjangoでもMySQLなど他のデータベースシステムを使えるので、それらを使用しましょう。

models.pyを実装

ではmodels.pyを定義・・・と行きたいところですが、テーブルを作成する前にテーブルの構成を考えましょう。「テーブル構成なら頭の中にある!」でも良いんですが、エクセルなどに表として書き出すと整理しやすいですし、特にテーブルがたくさん必要な場合は書き出さないと意味不明になってくるので、簡単にでも書き出してみることをお勧めします。

今回のコメント機能のテーブル構成は以下のようになります。(commentsテーブルとします)

カラム名id*namecommentcreated_at
整数文字列文字列日時
備考主キー・自動採番名前コメントコメントが投稿された日時

これは一般的な最小構成ですが、nameとcomment以外にもカラムがある点に注目してください。

まず、idカラムはテーブル内で一意性を担保するため、ないしはテーブルには主キーが必要なため用意します。自動採番とすることで、レコードが追加されると勝手に数字が入ります。Djangoの場合は1レコード目から順に1,2,3・・・のように付与されます。

created_atカラムはコメントが投稿された日時を格納します。ユーザーの投稿などの、運用していくとどんどん追加される系のテーブルにはレコードの作成日時のカラムを用意するのが一般的です。レコードが更新された日時を格納するupdated_atカラムを用意することも多いですが、今回のコメント投稿機能には投稿後の更新機能がないので省いています。

テーブル構成が決まったので、models.pyを実装します。

project>app>models.py

from django.db import models
from datetime import datetime

class Comment(models.Model):
    id = models.AutoField(primary_key=True, verbose_name="ID")
    name = models.CharField(max_length=255, verbose_name="名前")
    comment = models.CharField(max_length=255, verbose_name="コメント")
    created_at = models.DateTimeField(default=datetime.now, verbose_name="投稿日")

    class Meta:
        verbose_name = "comment"
        verbose_name_plural = "comments"

    def __str__(self):
        return self.name

マイグレーションとマイグレートする

models.pyを実装したらマイグレーションとマイグレートという2つの作業を行ってDjangoにテーブルの自動生成をしてもらいます。(データベースは最初のテーブル作成と同時に作成されます)

流れとしては、まずマイグレーションしてマイグレーションファイルを自動作成し、次にマイグレートすることでマイグレーションファイルをもとにデータベース構成を自動変更するのですが、この2つの違いは分からなくてもOKです。この順番だけ覚えておきましょう。

まずマイグレーションコマンドを実行します。

python manage.py makemigrations

以下のような出力があればOKです。

次に、マイグレートコマンドを実行します。

python manage.py migrate

以下のようにずらずらっとOK出力されればデータベースへ反映されています。

管理サイトでテーブルを管理できるようにする(admin.py)

先ほどの作業で作成したテーブルは、Django標準機能の「管理サイト」でグラフィカルに管理することができます。

が、テーブルを作成するだけではなく、admin.pyというファイルでその登録をする必要があります。

project>app>admin.py

from django.contrib import admin
from . models import *

admin.site.register(Comment)

admin.site.register(Comment)でmodels.pyのクラス名を指定することで、管理サイトそのテーブルが表示されるようになります。

管理サイトを使えるようにする

初めてマイグレーション・マイグレートした際は、管理サイトを使えるようにするためにDjangoの管理者ユーザーを追加する必要があります。

以下のコマンドを実行します。

python manage.py createsuperuser

ユーザー名は「admin」、パスワードは「password」とします。以下のように順に入力するよう促されます。(実運用ではこのような単純なユーザー名・パスワードを設定してはいけません)

project> python manage.py createsuperuser
    ユーザー名 (leave blank to use 'hoge'): admin
    メールアドレス: (入力せずエンター)
    Password: password(画面には表示されません)
    Password (again): password(画面には表示されません)
    このパスワードは一般的すぎます。
    Bypass password validation and create user anyway? [y/N]: y
    Superuser created successfully.

管理サイトに入る

まずサーバーを起動

python manage.py runserver

次に、ブラウザで「http://127.0.0.1:8000/admin」にアクセスします。すると、以下のように管理サイトのログイン画面になるので、先ほど設定したユーザー名とパスワードを入力します。

以下が管理サイトです。

コメントをテーブルに格納する機能を追加

テーブルが作成できたので、コメントを格納する機能をviews.pyに追加します。

project>app>views.py

from .models import * #追記

(略)

#変更
def send_comment(request):
    if request.method == 'POST':
        try:
            name = request.POST.get('name')
            comment = request.POST.get('comment')
            
            Comment.objects.create(name=name, comment=comment)
            return redirect("page1")
        except:
            return redirect("page1")
    else:
        return redirect("/page1")

解説

from .models import *でviews.py内でmodels.pyのクラスを参照するためのインポートをしています。

次に、

name = request.POST.get('name')
comment = request.POST.get('comment')

の部分でリクエストから、キーを指定する形でフォームデータを取り出して変数に格納しています。

Comment.objects.create(name=name, comment=comment)でテーブルにデータを追加しています。カッコ内のname=namecomment=commentは同じ名前同士で代入しているので分かりにくいですが、「<テーブルのカラム名>=<リクエストから取り出したデータが入っている変数名>」です。

try:~とexcept:~の部分はPythonの例外処理の構文です。try内で実行したいプログラムを記述し、もしそのプログラムの実行中にエラーが発生した場合はexcept内に処理が移ります。フォームデータの受け取り処理の章でも述べたように、views.pyの関数内では特に例外処理が重要です。しなくても動作はしますが、万が一エラーが発生した場合にDjangoがエラーを吐き出し、デプロイ後の場合はInternal Server Errorが発生してしまうので、「views.pyでは特にtry~exceptで例外処理をする」と覚えておいてください。

動作確認

ここまでの実装で、記事からコメントを送信するとテーブルに格納されるところまで完成しています。

サーバーを起動してコメントを投稿してみてください。

python manage.py runserver

投稿後に管理サイトのcommentsを見てみましょう。(表示されない場合は管理サイトをリロードしてください)

以下のようにレコードが追加されたことが確認できます!

追加されたレコード(ここでは「花子」)をクリックすると、テーブルに格納されたデータを確認できます。

名前・コメント・投稿日が閲覧でき、編集やレコードの削除ができるようになっています。ただし、自動採番のidだけはシステム内で自動的に付与されているため、デフォルトで管理サイトには表示されないようになっています。

コメントをページに表示する部分の実装

いよいよコメント機能の最後の部分、ページに投稿されたコメントを表示する機能を実装します。

views.pyの実装

project>app>views.py

#追加
def get_comment():
    return Comment.objects.all().order_by('-created_at')

#変更
def page1(request):
    comments = get_comment()
    return render(request, 'page1.html', {'comments': comments})

def page2(request):
    comments = get_comment()
    return render(request, 'page2.html', {'comments': comments})

解説

以下の部分

def get_comment():
    return Comment.objects.all().order_by('-created_at')

ここではテーブルからコメントを取得する処理を関数として用意しています。この関数はリクエストを受け取るわけではなく、page1・page2関数から呼び出される関数です。プログラム中では同じコードを何回も記述することは基本的に望ましくないので、再利用可能な形で関数化しています。

関数呼び出しはの前に関数の定義をする必要があるので、page1・page2関数よりも前に記述してください。

Comment.objects.all().order_by(‘-created_at’)でテーブルからデータを取得し、created_atの日時の降順に並べています。

テーブルから全データを取得するひな形は以下です。

<models.pyで定義したクラス名>.objects.all()

comments = get_comment()ではget_comment関数を呼び出し、返却されるテーブル内の全件のコメントデータを受け取っています。データは辞書型の形式で格納されます。(正確にはQuerySet型)

return render(request, ‘page2.html’, {‘comments’: comments})の部分、レスポンスとして何らかのデータを返却する際はrender関数の第3引数に辞書型で格納します。

page1.htmlとpage2.htmlでの実装

バックエンドはすべて完成したので、フロントエンドでコメントの表示部分を追加します。page1.htmlとpage2.htmlそれぞれに以下を追記してください。記事通りに進めている場合、{% include ‘comment.html’ %}の下に追記してください。

<div class="comments-list">
        <h2>コメント一覧</h2>
        {% for comment in comments %}
            <div class="comment">
                <p><strong>{{ comment.name }}</strong></p>
                <p>{{ comment.comment }}</p>
                <p><em>{{ comment.created_at }}</em></p>
            </div>
        {% empty %}
            <p>コメントはまだありません。</p>
        {% endfor %}
    </div>

解説

{% for comment in comments %}{% endfor %}はDjangoのテンプレートの組み込みタグ形式で、Pythonのfor文の構文で処理を書くことができます。ここではレスポンスデータのcommentsの中から1つずつコメントデータを取り出し、comment変数に格納しています。

for文の最後は{% endfor %}でタグを閉じます。

(余談ですが、組み込みタグとしてPythonのif文なども記述できます。)

次に、以下の部分

<p><strong>{{ comment.name }}</strong></p>
<p>{{ comment.comment }}</p>
<p><em>{{ comment.created_at }}</em></p>

ここはfor文の中で、1つ1つのコメントデータが格納されたcomment変数(辞書型)の中の各要素nameやcomment・created_atを取り出しています。このように記述することでデータを取り出せることを覚えておいてください。

{% empty %}では、for文で繰り返し取り出したい配列や辞書型が空の場合に処理が移動場所になります。ここではレスポンスのcomments(辞書型)の中身が空の場合に「コメントはまだありません。」と表示します。

最後の動作確認

お疲れ様です!以上ですべての実装が終わりました。

最後にサーバーを起動して動作確認しましょう。

python manage.py runserver

以下のように表示されていれば成功です!!

コメントを追加すると、最新のコメントが上に来るように表示されます。

最後に

今回の記事ではDjangoでフォームデータの送信・データベース機能にフォーカスを当てて紹介するために、なるべくミニマムな記述でプログラムを紹介しています。

それゆえに、以下のようなスマートじゃない挙動・実装になっています。

1.コメントを送信すると、どのページにいてもpage1に飛ばされる。

2.views.pyで各ページの関数内で毎回get_comment関数を呼び出している。また、コメント表示部分をページごとにhtmlに実装している。

これらは以下のような実装にすることで解決できます。

1.→記事を/page1や/page2ではなく/page/1・/page/2のようなルーティングで表示し、/1や/2をパラメータとしてviews.py関数で受け取り、リダイレクト先を動的に変更する。

2.→comment.htmlにコメント表示機能も集約すればOKなのですが、そのために非同期通信を導入する必要があります。非同期通信はフロントエンドの実装でAjax等を使用することで実現できます。