Django 記事詳細ページにコメント機能をつける方法

Djangoで記事詳細ページ内にコメントフォームをつける方法について紹介します。

モデル


ポストモデルとコメントモデルを用意します。

from django.db import models


class Post(models.Model):
    title = models.CharField('タイトル', max_length=32)
    text = models.TextField('本文')


class Comment(models.Model):
    """記事に紐づくコメント"""

    text = models.TextField('本文')
    target = models.ForeignKey(Post, on_delete=models.CASCADE, verbose_name='対象記事')


今回は必要最小限の実装です。
コメントモデルのtargetにどの記事に対するコメントか、という情報を保存します。

URLパターン

アプリ内のurls.pyです。

from django.urls import path
from . import views

app_name = 'blog'

urlpatterns = [
    path('', views.PostList.as_view(), name='post'),
    path('detail/<int:pk>/', views.PostDetail.as_view(),
         name='post_detail'), 
]


とりあえず記事一覧ページと、記事詳細ページのURLを定義します。

ビュー


まずは記事一覧ページと詳細ページを作ります。

from django.views import generic
from .models import Post, Comment

class PostList(generic.ListView):
    model = Post
    template_name = 'blog/index.html'


class PostDetail(generic.DetailView):
    model = Post
    template_name = 'blog/post_detail.html'


記事詳細テンプレート

一覧ページは省略します。
記事詳細ページのHTMLは下記のような形です。

{% extends "blog/base.html" %}
{% block content %}
<h2>記事詳細ページです</h2>
<div class="title">
  {{ post.title }}
</div>
<div class="text">
  {{ post.text }}
</div>
<div class="comment">
  <h2>コメント</h2>
  {% for comment in post.comment_set.all %}
  <div class="comment-text">
    <p>{{ comment.text }}</p>
  </div>
  {% endfor %}
</div>

{% endblock %}
{% block extrahead %}
<style media="screen">
  .title {
    font-size: 2rem;
  }

  .text {
    margin-top: 30px;
  }

  .comment {
    margin-top: 30px;
  }

  .comment-text {
    margin-top: 20px;
  }

</style>
{% endblock %}


管理サイトから記事を一つとコメントを一つ追加するとこのような見た目になります。



雑ですがこんな感じで記事とコメントが表示されます。
ここのページ内でコメント機能をつけていきたいと思います。

詳細ページにコメント機能をつける

通常、このような構成の場合、コメント用の作成ビューと表示テンプレートを用意して行います。
ただし、コメントをする際に、ページ遷移をするのはちょっとかっこわるい感じがします。

ユーザー的にも記事ページ内からコメントができたほうが良いでしょう。

まずはurls.pyにコメント作成用のurlを追加します。

from django.urls import path
from . import views

app_name = 'blog'

urlpatterns = [
    path('', views.PostList.as_view(), name='post'),
    path('detail/<int:pk>/', views.PostDetail.as_view(),
         name='post_detail'),  
    # 追加
    path('comment/create/<int:pk>/', views.CommentCreate.as_view(), name='comment_create')
] 


次にアプリ内にforms.pyを作成して、コメント作成用のフォームを定義しましょう。

from django import forms
from .models import Comment


class CommentCreateForm(forms.ModelForm):
    """コメント投稿フォーム"""

    class Meta:
        model = Comment
        fields = ('text',)


次にビューの編集をします。

from django.views import generic
from .models import Post, Comment
from .forms import CommentCreateForm  # 追加
from django.shortcuts import redirect, get_object_or_404 # 追加


class PostList(generic.ListView):
    model = Post
    template_name = 'blog/index.html'


class PostDetail(generic.DetailView):
    model = Post
    template_name = 'blog/post_detail.html'

    # 追加
    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        # テンプレートにコメント作成フォームを渡す
        context['comment_form'] = CommentCreateForm
        # ポストのpkを取得
        pk = self.kwargs.get('pk')
        # コメント投稿のurlを渡す
        context['comment_url'] = f'/comment/create/{pk}/'

        return context


# 追加
class CommentCreate(generic.CreateView):
    """
    記事へのコメント作成ビュー
    ページは表示されないが、コメントを作成するために使用
    """
    model = Comment
    form_class = CommentCreateForm

    def form_valid(self, form):
        post_pk = self.kwargs.get('pk')
        post = get_object_or_404(Post, pk=post_pk)

        comment = form.save(commit=False)
        comment.target = post
        comment.save()

        return redirect('blog:post_detail', pk=post_pk)


ポイントは下記のような感じかと思います。

  • 詳細ページにコメント作成フォームを渡す
  • コメント作成フォームの送信先urlを渡す
  • ページは表示しないがコメント作成のビューを定義する


次にpost_detail.htmlを編集します。
先ほど定義したフォームと作成用のurlを渡してあげます。

{% extends "blog/base.html" %}
{% block content %}
<h2>記事詳細ページです</h2>
<div class="title">
  {{ post.title }}
</div>
<div class="text">
  {{ post.text }}
</div>
<div class="comment">
  <h2>コメント</h2>
  {% for comment in post.comment_set.all %}
  <div class="comment-text">
    <p>{{ comment.text }}</p>
  </div>
  {% endfor %}
</div>
<!-- 追加 -->
<div class="comment-form">
  <h2>コメント投稿</h2>
  <!-- コメント作成用のURLを渡す -->
  <form action="{{ comment_url }}" method="post">
    {% csrf_token %}
    {{ comment_form}}
    <div>
      <button type="submit">送信</button>
    </div>
  </form>
</div>
{% endblock %}
{% block extrahead %}
<style media="screen">
  .title {
    font-size: 2rem;
  }

  .text {
    margin-top: 30px;
  }

  .comment {
    margin-top: 30px;
  }

  .comment-text {
    margin-top: 20px;
  }

  /*追加*/
  label {
    display: block;
  }
</style>
{% endblock %}


<form action="{{ comment_url }}" method="post">とすることで、フォームの送信が成功すると、コメント作成用のビューが呼ばれる、ということになります。



このようにして送信をしますと…



ページ遷移なしでコメントが追加されました。

TOPページ