「Djangoで始めるブログ作成講座」の第三回です。
前回はDjangoで画像を扱い、ブログのアイキャッチ画像を定義しました。
「Djangoで始めるブログ作成講座」の第二回です。 前回はブログの基本的な内容を実装するにとどまりました。 [sitecard subtitle=関連記事 url=https://freemas.stepupkaraoke.com[…]
今回は投稿の詳細画面に誰でもコメントを投稿できる機能を実装していきましょう!
完成形のイメージは下記のようになります。
この記事を読んで取り組むことで、Djangoの以下の基本的な使い方を習得することができます。
- DjangoでのModelの結合の仕方
- 結合先のデータ削除の場合の挙動
- 同一ViewでのPost時のふるまいの指定
- commit=Falseでのデータ保存
- Django管理画面でのinlineの使い方
- Django管理画面で、データ一覧画面の表示のカスタマイズ
今回はDjangoの多くのことが学べる回となっています。
その分Djangoの便利な部分も多数出てきますので、楽しく作りながら学んでいきましょう!
Djangoで始めるブログ作成講座③:コメント投稿機能を導入しよう!
今回は
- コメント投稿フォームの設置
- コメント投稿機能実装
- コメントの管理画面実装
の3段階で実装していきます。まずはコメントフォームの設置からです。
コメント投稿フォームの設置:Model作成
新たにmodels.pyに追記します。
1 2 3 4 5 6 7 8 9 |
class Comment(models.Model): post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='comments') author = models.CharField(max_length=50, blank=True, default='匿名') text = models.TextField() created_at = models.DateTimeField(auto_now_add=True) approved_comment = models.BooleanField(default=False) def __str__(self): return self.text |
- Post:どの投稿にコメントをしたかを外部結合で指定
- author:コメントをした人の名前
- text:コメントの本文
- created_at:コメントの投稿日時
- approved_comment:許可されたコメントか否か
表でユーザーに入力してもらうのはauthoerとtextのみになります。
on_detele=models.CASCADEは紐づいている別テーブルのデータが削除されたら、このデータも削除することを表します。
今回の場合は紐づいているPost、つまり投稿が削除された段階で紐づくコメントも削除するようにしています。
他にもいくつか種類があり、例えば以下はそれぞれ結合先データ削除時に異なる動きを見せます。
- on_delete=models.SET_NULL:カラムを空に
- on_delete=models.DEFAULT:Modelで指定した初期値に
データによって、どの値に戻したいのか、そもそもデータを丸ごと削除したいのかは変わってくるので、よく検討するようにしましょう。
コメント投稿フォームの設置:Views作成
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
-- 省略 -- from .forms import CommentForm class PostDetailView(DetailView): model = Post template_name = 'post_detail.html' def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) #投稿されたコメントの全権を取得 comments = self.object.comments.all() #コメントフォームを設置 form = CommentForm() context.update({ 'comments': comments, 'form': form, }) return context |
追加しているのはコメントの全件、およびコメント投稿フォームです。
コメントの全件は投稿に紐づくコメントのみを取得しています。
またこれに紐づくCommentFormクラスを持つforms.pyも作成しましょう。
1 2 3 4 5 6 7 8 9 10 11 12 |
from django import forms from .models import Comment class CommentForm(forms.ModelForm): class Meta: model = Comment fields = ('author', 'text',) widgets = { 'text': forms.Textarea(attrs={'class': 'form-control'}), 'author': forms.TextInput(attrs={'class': 'form-control'}), } |
コメント投稿フォームの設置:Template作成
続いてテンプレートの方にも修正を加えます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
-- 省略 -- <div class="row"> {{ post.content | safe }} <p>Created: {{ post.created_at }}</p> </div> <!-- 追加ここから --> <hr> <h2>Comments</h2> <ul class="list-unstyled"> {% for comment in comments %} <li class="media my-4"> <div class="media-body"> <h5 class="mt-0 mb-1">{{ comment.name }}</h5> <!-- 未承認コメントは隠す --> <p>{% if comment.approved_comment %} {{ comment.text }} {% else %} 管理人の承認待ちです {% endif %}</p> <small>Created at: {{ comment.created_at }}</small> </div> </li> {% empty %} <li>No comments yet</li> {% endfor %} </ul> <form method="post"> <!-- コメント投稿フォーム --> {% csrf_token %} {{ form.as_p }} <button type="submit" class="btn btn-primary">Post</button> </form> </div> </div> </div> {% endblock %} |
分岐により、管理者が承認していないコメントは「承認待ち」だと表示するようにしています。
データはViewが取得してくれるので、テンプレート側で捌くだけで済みます。
フォームはHTMLのp要素でマークアップしています。
csrf_tokenはフォームお決まりのセキュリティ対策ですね。
さて、実装が終ったところで開発環境にアクセスしてブログの記事詳細画面を見てみましょう。
下側に下記画像のようにコメントフォームが出ていれば成功です。
ただ、もちろんコメントを送ってもデータベースには反映されません。
反映させるための処理を、続いて記載していきましょう。
コメント投稿機能実装:Viewに追記する
先ほど修正したPostDetailViewに追記しましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
from django.shortcuts import redirect class PostDetailView(DetailView): model = Post template_name = 'post_detail.html' def get_context_data(self, **kwargs): -- 省略 -- #POSTで呼び出された場合はコメントを登録 def post(self, request, *args, **kwargs): self.object = self.get_object() form = CommentForm(data=self.request.POST or None) #フォームの入力値に問題がなければ、投稿に紐づけてから登録する if form.is_valid(): comment = form.save(commit=False) comment.post = self.object comment.save() return redirect('post_detail', pk=self.object.pk) |
CommentFormで問題がなければ、一旦commit=Falseでsave関数を発行しています。
この段階で保存の準備は整っているのですが、まだ登録はしていません。
その次のcomment.post = self.objectで紐づく投稿を取得しているので、そのために一旦保留していると思ってください。
紐づき投稿が分かった場合は保留する必要がなくなったので、comment.save()でデータベースに保存しています。
このように、保存前にすることがある場合にはcommit=Falseで保留にすることがあるのを覚えておきましょう。
コメント投稿機能実装:コメントが送れるか確認
Viewの修正が終ったところで、コメントが投稿できるかどうか確認してみましょう。
もう一度投稿詳細画面に入り、適当なコメントを打ってみてください。
上記のように、「管理人の承認待ちです」が表示されれば適切にデータベースに保存されています。
次はこのコメントを管理画面で承認してみましょう!
コメントの管理画面実装:詳細画面にコメントを追加
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
from django.contrib import admin from .models import Post, Comment #投稿詳細管理画面にコメントも追加 class CommentInline(admin.TabularInline): model = Comment extra = 0 fields = ('author', 'text', 'approved_comment') class PostAdmin(admin.ModelAdmin): list_display = ('title', 'created_at', 'updated_at','thumbnail', 'get_comment_count') inlines = (CommentInline,) #コメント数を測定して、記事一覧画面に反映 def get_comment_count(self, obj): return obj.comments.count() get_comment_count.short_description = 'Comment Count' |
一つのModel管理画面で、別のModelを含めることができるDjangoのInlineという機能を用いています。
今回はこれを用いて、投稿の詳細画面に下記のようにコメントを含めました。
コメントの承認も出来るので、実際に使うときにかなり便利ですね。
今回のCommentInlineの説明は以下の通りです。
- model:紐づけるモデルを選択します
- extras:空のカラムの数を指定します。1を入力すると、上記のコメントの下に新規で空のフォームが追加されます。
- fields:どの項目を表示するかを指定します。
基本的にはModelの指定と同じなので分かりやすいと思います。
extrasはここだけで登場するので、覚えておくと良いでしょう。
このままではどの投稿にコメントが付いているのか分からないので、コメント数を計測して表示します。
ここで定義した関数「get_comment_count」は投稿に紐づくコメント数を取得しています。
obj.Modelで定義したrelated_name.countで取得可能です。
Modelで定義したrelated_nameはもし違う文言を指定しているならばそちらに合わせてください。
COMMENT COUNTが表示されていれば成功しています。
コメントの管理画面実装:コメント一覧を追加
投稿に紐づくコメントが完成しましたが、どうせならコメントを一覧で確認したいですね。
ということで、コメント一覧の管理場面も作成してしまいましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
from django.contrib import admin from .models import Post, Comment from django.urls import reverse from django.utils.html import format_html from ckeditor_uploader.widgets import CKEditorUploadingWidget from django.db import models -- 省略 -- class CommentAdmin(admin.ModelAdmin): list_display = ('text', 'get_post_link', 'author', 'created_at', 'approved_comment') #フィルターフィールドを追加 list_filter = ('approved_comment', 'created_at',) #検索フィールドを追加 search_fields = ('text', 'post__title', 'author') #編集不可フィールドを追加 readonly_fields = ('created_at',) #コメントに紐づく投稿名と編集画面へのリンク追加 def get_post_link(self, obj): url = reverse('admin:djangoblog_post_change', args=[obj.post.id]) return format_html("<a href='{}'>{}</a>", url, obj.post.title) get_post_link.short_description = 'Post' admin.site.register(Post, PostAdmin) admin.site.register(Comment, CommentAdmin) |
かなり項目が多いですが、これを実装することで管理画面に以下のようなコメント一覧画面が表示されます。
コードと実際の管理画面を紐づけながら、見ていきましょう。
管理画面右側のフィルターに表示する項目を指定します。
今回は承認しているかどうか、と、いつ投稿されたコメントか、でコメントを絞り込むことが可能です。
管理画面上側の検索窓を追加します。
テキストボックスに入力した値を、指定したフィールド名だけで検索、絞り込みを行ってくれます。
今回はコメント本文、投稿名、投稿者の3つから検索してくれます。
投稿名ですが、コメントのように別のモデルクラスと結合している場合は「モデル名__カラム名」で値を取得できます。
真ん中が「_」2つですので、間違えないように気を付けてください。
文字通り、表示されますが編集できない項目を指定します。
今回はコメントの投稿日を指定しているので、各種コメント詳細画面では以下のように指定されます。
投稿日が編集できなくなっているのが分かりますね。
一覧画面にて、コメント側からも紐づいた先が分かるようにしています。
まずはコメントが紐づく投稿の編集画面を取得してurlに代入しています。
さらにそれを踏まえてHTMLコードを作り出しました。
format_htmlは上記のように「{}」を指定し、その後ろに「{}」の数だけ値を指定します。今回はリンク先URLと紐づいた投稿名を表示しました。
この関数「get_post_link」はそのまま「list_display」に指定可能です。
このように一覧で表示したい内容をお好みで変更することもできます。
おわりに
今回は記述量こそ多いものの、別の言語ではさらに多くのコード記述が必要になります。
比較してみると2/3程度になっていて、このように記述量が少ないのはDjangoの、ひいてはPythonの良い点ですね。
内容も分かりやすく、理解しやすい記述が多くなっていて頭に入りやすいものになっています。
さて、今回もここまでのソースをGithubに掲載しておくので、もしも見返したい場合は確認してみてください。
次の記事では、投稿一覧画面にページング機能を実装していきましょう。
「Djangoで始めるブログ作成講座」の第四回です。 前回はブログにコメント投稿機能を実装し、管理画面のカスタマイズも同時に行いましたね。 [sitecard subtitle=関連記事 url=https://freemas.st[…]