2
0

More than 1 year has passed since last update.

【Django】自作ブログサイトの記事にコメント機能を実装してみた

Last updated at Posted at 2021-09-24

今回はコメント機能の実装について備忘録を残していきます。
当方初心者エンジニアですが、自分のため、そしてこれを見てくれた方のお役に立てればと思います。

これから一部コードを提示しながら説明していきますが、全体のコードをご覧になりたい場合はGithubにありますので、そちらを参照して頂けますと幸いです。

完成イメージ

実装結果はこちらになります。一般的なコメント欄となります。
トップの記事リスト(list.html)から選択した記事詳細(detail.html)の下に付けてみました。
無題①.png

コメントする場所(comment_form.html)は記事詳細(detail.html)に『コメントする』というボタンを追加しましたので、そこから遷移できるようにしました。
名前(デフォルトは名無し)、本文を書いて『投稿する』をポチっと押せば投稿完了です。
『キャンセル』を押せば記事詳細(detail.html)に戻ります。
無題②.png

CSSは神フレームワーク『BootStrap』を使用しました。
いつかオシャレデザインを自分で実装できるようにしたいよ(遠い目)

……ま、まあとにかく。どう実装していったかを下記に記していきます。

参考サイト

下記サイトを放浪しながら構築していきました。いつもお世話になっております。
ほぼ参考サイトのロジックで実装させて頂きました。
Narito Blog 様
知識の枝 様

①モデル

まずはモデルを定義していきます。
当然ですが、コメント機能はブログ記事があってこそ成立します。なのでまずはブログ記事のモデルを用意します。

models.py
#ブログ記事モデル
#モデル名が『BlogModel』となっておりますが、『Blog』とか、『Article』とかにした方が良いですね(汗)
class BlogModel(models.Model):
    title = models.CharField(max_length=100, verbose_name='タイトル')
    content = MDTextField(verbose_name='内容')
    postdate = models.DateField(auto_now_add=True, verbose_name='投稿日')
    category = models.CharField(
        max_length= 50,
        choices= CATEGORY,
        verbose_name= 'ジャンル'
    )
    tag = models.ManyToManyField(Tag, blank=True)

なんか色々とフィールドを設定してますが、とりあえずtitile, content, postdateフィールドさえあれば問題ないかと。
このブログ記事モデルが出来ましたら、次はお待ちかねのコメントモデルを定義していきます。

models.py
#コメントモデル
class Comment(models.Model):
    user_name = models.CharField('名前', max_length=255, default='名無し')
    message = models.TextField('本文')
    target = models.ForeignKey(BlogModel, on_delete=models.CASCADE, verbose_name='対象記事')
    created_at = models.DateTimeField('作成日', default=timezone.now)

    def __str__(self):
        return self.message[:20]

さて、色々とフィールドが設定されておりますね。
user_nameは名前、messageはコメント本文,created_atはコメント投稿日時となります。
そして大事なのはtargetフィールドです。
該当するブログ記事は一つだけですが、それに対するコメントはもちろん複数ありますよね。
それを実現するにはForeignKeyを使用して、「1対多」の関係を構築する必要があります。
なので引数には先ほど定義したBlogModelを設定します。

これでモデルの定義は完了です。あとは忘れないよう、マイグレーションです。

python manage.py makemigrations
python manage.py migrate

②フォーム

ふう、ようやくモデルが完成しましたね。
というわけで、お次はフォームの設定をしていきます。

モデルの定義はOKですが、これだけでなくフォームも定義する必要があります。
まずはアプリフォルダ直下(models.pyやviews.pyと同じ階層)にforms.pyファイルを作成します。
作成後、以下のように記述していきます。

forms.py
from django import forms
from .models import Comment

#コメント投稿フォーム
class CommentCreateForm(forms.ModelForm):
    class Meta:
        model = Comment
        exclude = ('target', 'created_at')

まずは必要なライブラリをインポートしていきます。
コメントフォームを作成しますので、先ほど定義したコメントモデルもインポートします。

メタデータに exclude = ('target', 'created_at')を記述していますが、これはフォーム画面に表示させないよう除外しております。
どの記事に紐づくかのtarget、および作成日であるcreated_atです。

③ビュー

ここまでモデルとフォームの定義が完了しました。
次はビューを作っていきます。

views.py
from django.shortcuts import redirect, get_object_or_404
from django.views.generic import DetailView
from django.views.generic.edit import CreateView
from .models import BlogModel, Comment
from .forms import CommentCreateForm
from django import forms

#ブログ記事詳細ページのビュー
class BlogDetail(DetailView):
    template_name = 'detail.html'
    model = BlogModel

#コメント投稿ページのビュー
class CommentView(generic.CreateView):
    template_name = 'comment_form.html'
    model = Comment
    form_class = CommentCreateForm

   #フォームに入力された情報が正しい場合の処理
    def form_valid(self, form):
        post_pk = self.kwargs['pk']
        post = get_object_or_404(BlogModel, pk=post_pk)
        comment = form.save(commit=False)
        comment.target = post
        comment.save()
        return redirect('blogapp:detail', pk=post_pk)

   #htmlテンプレートに渡すデータを定義
    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['post'] = get_object_or_404(BlogModel, pk=self.kwargs['pk'])
        return context

クラスベースビューとなります。こちらはほぼ参考サイトと同じですが、一部書き換えている部分もあります。
CommentViewのtemplate_name = 'comment_form.html'ですが、こちらのテンプレートは後ほど作成します。それとビューの作成に必要なライブラリのインポートも忘れないようにします。

④ルーティング

urls.pyの設定も欠かさず行っていきます。アプリ直下にurls.pyを作成し、以下の通り記述します。

urls.py
from django.urls import path
from .views import BlogDetail, CommentView

app_name = 'blogapp'

urlpatterns = [
    path('detail/<int:pk>', BlogDetail.as_view(), name='detail'),
    path('comment/create/<int:pk>/', views.CommentView.as_view(), name='comment_create'),
]

⑤テンプレート

では、いよいよ表示するためのテンプレートを作っていきます。
今回はブログ記事詳細ページであるdetail.htmlの一番下部分にコメント欄を配置するため、以下のように記述していきます。

detail.html
<h3 class="comment-title">コメント</h3>

<!--コメント-->
{% for comment in object.comment_set.all %}
<div class="comment-list">
    <div class='border-bottom'>{{ comment.user_name }} {{ comment.created_at }}</div>
    <!--改行しないようlinebreaksを設定,URL要素に<a>要素を設定-->
    <div class='mt-2'>{{ comment.message | linebreaks | urlize }}</div>
</div>
{% empty %}
<p>コメントはありません</p>
{% endfor %}

上記を記述することにより、冒頭の完成イメージで見せたコメント欄が表示されます。
次は肝心なコメント投稿ページを作成します。

comment_form.html
<div class="container col-lg-6 offset-lg-3">

    <!---記事タイトルを表示-->
    <h2 class="titleline"><span>{{ post.title }}</span></h2>
    <form action="" method="POST" id="comment-form">

     <!--特定のフィールドに紐づかないエラーを表示する-->
        {{ form.non_field_errors }}

        <!--フォームの各フィールドを取り出す-->
        {% for field in form %}
        <div class="field">
            {{ field.label_tag }}
            {% render_field field class="form-control" %}

            {{ field.errors }}
        </div>
        {% endfor %}
        {% csrf_token %}
        <div class="row my-3">
            <button type="submit" class="btn btn-success col-3 offset-2">投稿する</button>
            <a class="btn btn-danger col-3 offset-2" href="{% url 'blogapp:detail' post.pk %}">キャンセル</a>
        </div>
    </form>
</div>

{% for field in form %}はフォームの各フィールドを取り出します。今回の場合はuser_name(名前)、message(本文)を入力するフォームが表示されるようになります。

さいごに

こんな感じでコメント機能を実装しました。
本当は返信機能も実装したかったところですが、それはまた別の機会に。。。今はブログ記事詳細のページに目次機能を実装するため目下努力中であります。
結構ざっくばらんに書き綴りましたが、何か不備がございましたらご連絡ください。
それでは、また何かありましたら備忘録を書こうと思います。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0