※1本の記事として書き上げましたが、記事が長くなりすぎたため、前編・後編に分けています。
ブログからコメントを作成できるようにする(承前)
コメント入力画面へ遷移するように設定(承前)
AttributeError
への対応として、blog/views.py
にadd_comment_to_post
関数を用意します。
from django.shortcuts import render, get_object_or_404
from django.utils import timezone
from .models import Post
from .forms import PostForm, CommentForm
def add_comment_to_post(request, pk):
post = get_object_or_404(Post, pk=pk)
if request.method == "POST":
form = CommentForm(request.POST)
if form.is_valid():
comment = form.save(commit=False)
comment.post = post
comment.save()
return redirect('post_detail', pk=post.pk)
else:
form = CommentForm()
return render(request, 'blog/add_comment_to_post.html', {'form': form})
Djangoフォーム · Django Girls Tutorial でやったように、comment = form.save(commit=False)
で、コメント用フォームに結びつけられたデータからデータベースに保存するためのコメントのオブジェクトを生成しています。1
ForeignKeyのpostについて、comment.post = post
として、コメントが紐づく記事を設定しています。2
(記事のidを渡すのでなく、記事そのものを渡すと、Django側で処理してくれるようです。)
add_comment_to_post
関数が追加されたので、記事詳細画面が表示されるようになります。(URL例:http://localhost:8000/post/1/
)
コメント入力フォーム追加
ところが、追加した「Add comment」ボタンをクリックすると、TemplateDoesNotExist
というエラーが表示されます。
「Add comment」ボタンをクリックすると、先ほど追加したadd_comment_to_post
関数が呼び出されます。
関数の中で、render(request, 'blog/add_comment_to_post.html', {'form': form})
と、blog/add_comment_to_post.html
テンプレートを表示するように設定しているのですが、そのテンプレートが見つからずエラーとなっています。
blog/add_comment_to_post.html
テンプレートを作りましょう。
blog/templates/blog/add_comment_to_post.html
{% extends 'blog/base.html' %}
{% block content %}
<h1>New comment</h1>
<form method="POST" class="post-form">{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="save btn btn-default">Send</button>
</form>
{% endblock %}
add_comment_to_post.html
はpost_edit.html
テンプレートと非常に似ています。
テンプレートが追加されたので、「Add comment」ボタンをクリックした後のエラーが消え、コメントが書けるようになりました。
▼ログインしていなくてもコメントが書けます
▼追加されました
コメントの承認機能と削除機能をつける
コメント承認機能・コメント削除機能追加
ここまででブログの記事に誰もがコメントを書け、そのコメントは誰でも見られるようになりました。
ここでは、ブログにログインしているユーザが承認したコメントに限って、誰でも見られるように修正します。
記事詳細画面の変更内容
- ログインしているユーザは、すべてのコメントが見られる:(1)
- ログインしているユーザは、コメントの承認または削除ができる3:(2)
- ログインしていないユーザは、承認されたコメントのみを見られる:(3)
(1)〜(3)ができるように、記事詳細画面のテンプレートを編集します。
blog/templates/blog/post_detail.html
{% extends 'blog/base.html' %}
{% block content %}
<div class="post">
</div>
<hr>
<a class="btn btn-default" href="{% url 'add_comment_to_post' pk=post.pk %}">Add comment</a>
{% for comment in post.comments.all %}
{% if user.is_authenticated or comment.approved_comment %}
<div class="comment">
<div class="date">
{{ comment.created_date }}
{% if not comment.approved_comment %}
<a class="btn btn-default" href="{% url 'comment_remove' pk=comment.pk %}"><span class="glyphicon glyphicon-remove"></span></a>
<a class="btn btn-default" href="{% url 'comment_approve' pk=comment.pk %}"><span class="glyphicon glyphicon-ok"></span></a>
{% endif %}
</div>
<strong>{{ comment.author }}</strong>
<p>{{ comment.text|linebreaks }}</p>
</div>
{% endif %}
{% empty %}
<p>No comments here yet :(</p>
{% endfor %}
{% endblock %}
ログインしているユーザの場合、user.is_authenticated
はTrue
なので、すべてのコメントが表示されます。
そのうち、承認されていない各コメントには削除ボタンと承認ボタンが表示されます。
ログインしていないユーザの場合、comment.approved_comment
がTrueになる(すなわち、承認済みの)コメントが表示されます。
承認済みなので、コメントに削除ボタンと承認ボタンは表示されません。4
記事詳細画面を表示すると、NoReverseMatch
エラーです。
comment_remove
(とcomment_approve
)というURLが設定されていないために発生しているので、blog/urls.py
を編集します。
from django.urls import path
from . import views
urlpatterns = [
path('comment/<int:pk>/approve/', views.comment_approve, name='comment_approve'),
path('comment/<int:pk>/remove/', views.comment_remove, name='comment_remove'),
]
AttributeError
により、サーバが起動しなくなりました。
blog/views.py
にcomment_approve
関数(とcomment_remove
関数)が見つからないために発生しているので、これらの関数を追加します。
from django.shortcuts import render, get_object_or_404
from django.utils import timezone
from .models import Post, Comment
from .forms import PostForm, CommentForm
@login_required
def comment_approve(request, pk):
comment = get_object_or_404(Comment, pk=pk)
comment.approve()
return redirect('post_detail', pk=comment.post.pk)
@login_required
def comment_remove(request, pk):
comment = get_object_or_404(Comment, pk=pk)
comment.delete()
return redirect('post_detail', pk=comment.post.pk)
comment.post.pk
でコメントが紐づく記事のpk(ID)を取得できます。
▼Djangoシェルでの確認
>>> from blog.models import Comment
>>> comment = Comment.objects.get(id=1)
>>> comment.post
<Post: 初投稿:dockerで環境構築>
>>> comment.post.pk
1
これでエラーは解決し、ログインするとコメントの承認/削除を行うことができます。
ログインしていないユーザには、承認したコメントだけが表示されます。
記事一覧に表示されるコメント数を承認されたコメント数に変更する
最後に、記事一覧のコメント数を承認されたコメントの数に変更します。
(ログインの有無にかかわらず、承認されたコメント数の表示となります)
変更するのはテンプレートとモデルの2箇所です。
まず、blog/templates/blog/post_list.html
を変更します。
{% extends 'blog/base.html' %}
{% block content %}
{% for post in posts %}
<div class="post">
<div class="date">
<p>published: {{ post.published_date }}</p>
</div>
<h1><a href="{% url 'post_detail' pk=post.pk %}">{{ post.title }}</a></h1>
<p>{{ post.text|linebreaksbr }}</p>
<a href="{% url 'post_detail' pk=post.pk %}">Comments: {{ post.approved_comments.count }}</a>
</div>
{% endfor %}
{% endblock %}
コメントの数はPostモデルのapproved_comments
メソッドで取得した数としています。
しかし、approved_comments
はまだ実装されていないため、この時点ではコメント数には何も表示されません。
そこで、blog/models.py
を変更します。
Postモデルにapproved_comments
メソッドを追加します。
from django.db import models
from django.utils import timezone
class Post(models.Model):
def publish(self):
self.published_date = timezone.now()
self.save()
def approved_comments(self):
return self.comments.filter(approved_comment=True)
def __str__(self):
return self.title
class Comment(models.Model):
approved_comments
メソッドでは、前編冒頭のCommentモデルのForeignKey設定で、post.comments
と書けるように設定したことを利用しています。
追加したメソッドをDjangoシェルで試してみます。
>>> from blog.models import Post
>>> post = Post.objects.get(id=1)
>>> post.approved_comments()
<QuerySet [<Comment: フォームからコメントを作りました!>, <Comment: 承認のテストです>]>
>>> post.approved_comments().count()
2
記事一覧画面で承認されたコメントの数が表示されるようになりました!
終わりに
コメント機能、手を動かす部分は長かったですが、やっていることはDjango Girls Tutorialでやってきた機能追加手順と変わらないと思います。
Djangoで機能追加するときの一連の手順の練習という印象です。
- 新規ページへ遷移するリンクを追加
- 新規ページのURL設定
- 新規ページのビュー作成
- 新規ページのテンプレート作成
ExtensionsのDjango2系の動作確認はこれで完了です。
PyConJPのスプリントで協力いただいた翻訳があるので、そのレビューを進めていきます。
最後までお読みいただき、ありがとうございました。
脚注