[Django] コメント機能の追加

この連続企画も今回も含めて残り二回となりました。

今回は、記事に対するコメントができる機能を実装します。

シリーズ一覧

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

コメント機能の追加

コメント機能を実現するにはデータベースのリレーションをうまく活用できれば実現できそうです。

モデル

新たにコメントのデータを保存するテーブル(モデル)を作ります。
models.py

# 略

class Comment(models.Model):
    text = models.TextField()
    posted_at = models.DateTimeField(auto_now_add=True)
    article = models.ForeignKey(to=Article, related_name='comments', on_delete=models.CASCADE)
    def __str__(self):
        return self.text

text = models.TextField()がコメントの本文です。
Articleクラスのtextと同様TextFieldを利用しています。

posted_at = models.DateTimeField(auto_now_add=True)がコメントの投稿日時です。
こちらも、Articleクラスのposted_atと同様DateTimeFieldを利用しています。

article = models.ForeignKey(to=Article, related_name='comments', on_delete=models.CASCADE)がちょっと特殊で、コメントへのリレーションです。

ForeignKeyは一対多のリレーションを表します。

要は、データベースの外部キーです。

to="XXX"(XXXはモデルクラス名)でどのテーブル(モデル)の要素に対してリレーションを持つか指定できます。

related_name='comments'は、Articleクラスから逆参照するときにどのような名前で参照するかを指定しています。
今回の例だと、

article = models.Article.objects.get(pk=n) # 記事を取得
article.comments # <- これで、この記事に対するコメントへアクセスできる
article.comments.all() # とすることでこの記事に対するコメントを全件取得

on_delete=models.CASCADEで参照先の記事が削除されたときのふるまいを指定しています。

models.CASCADEを指定すると、参照先の記事が削除された際、その記事に対するコメントも削除します。

モデルを書き換えたので忘れずにマイグレーションをしましょう。

python manage.py makemigrations

出力

Migrations for 'blog':
  blog\migrations\0003_comment.py
    - Create model Comment
python manage.py migrate

出力

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

URL

今回は新たに設定することはありません。

テンプレート

コメントの投稿と、コメントが表示できるようにフォームなど、ちょっと書き足しました。

フォームの部分には{% csrf_token %}を忘れずに指定しておきましょう。

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>

  <form action="" method="POST">
    <p><label for="com">コメント</label></p>
    {% csrf_token %}
    <textarea name="text" id="com" cols="30" rows="10"></textarea>
    <button type="submit">投稿</button>
  </form>

  {% for comment in article.comments.all %}<!-- 記事に対するコメントを全件取得 -->
    <p>{{ comment.text }}<br/><small>{{ comment.posted_at }}</small></p>
  {% empty %}
    <p>コメントはありません</p>
  {% endfor %}
</body>

</html>

view

先ほどURLを追加しなかった代わりに、記事の閲覧ページを表示させているview_article関数に改造を加えます。

具体的にはコメントが投稿されたとき、コメントをデータベースに保存する機能を追加します。

「投稿されたとき」なのでrequest.method == "POST"で条件分岐を発生させます。

if request.method == "POST":
  コメントの保存処理

データベースにデータを保存する方法を覚えていますか?

Model.objects.create(フィールド名=値)

で出来ます。

views.py

def view_article(request, pk):
    template_name = "blog/view_article.html"
    try:
        article = models.Article.objects.get(pk=pk)
    except models.Article.DoesNotExist:
        raise Http404
    if request.method == "POST":
        # データベースに投稿されたコメントを保存
        models.Comment.objects.create(text=request.POST["text"], article=article) # 追記
    context = {"article": article}
    return render(request, template_name, context)

これで、コメントが投稿できるはずなのでやってみてください。

まとめ

他のモデルへのリレーションはmodels.ForeignKeyで表現できる。

  • toで参照先を指定
  • on_deleteで参照先が削除されたときのふるまいを指定

最後に

コメント機能を実装して、だいぶ中身も充実したと思うので機能の追加は以上にしようと思います。

次回は、テンプレートをもう少し綺麗に書き直してページのデザインを整えたいと思います。
ページデザインにはBootstrap4を利用する予定です。