Djangoメモ(36) : 掲示板アプリの最終調整
A Complete Beginner's Guide to Djangoのチュートリアルを参考に実装してきた掲示板アプリの最終調整をする。
更新日時を保存する
Post(返信)があったときにTopicのlast_updated
が更新されるようにboards/views.py
を修正する。
@login_required def reply_topic(request, pk, topic_pk): topic = get_object_or_404(Topic, board__pk=pk, pk=topic_pk) if request.method == 'POST': form = PostForm(request.POST) if form.is_valid(): post = form.save(commit=False) post.topic = topic post.created_by = request.user post.save() topic.last_updated = timezone.now() # <- here topic.save() # <- and here return redirect('topic_posts', pk=pk, topic_pk=topic_pk) else: form = PostForm() return render(request, 'reply_topic.html', {'topic': topic, 'form': form})
同じユーザーの場合は閲覧数を何回もカウントしない
閲覧数をカウントするようにしていたがリロードすると同じユーザーで複数回カウントされてしまうという問題があった。
セッション情報を使って何回もカウントしないようにするためにboards/views.py
を以下のように編集。
class PostListView(ListView): model = Post context_object_name = 'posts' template_name = 'topic_posts.html' paginate_by = 20 def get_context_data(self, **kwargs): session_key = 'viewed_topic_{}'.format(self.topic.pk) # <-- here if not self.request.session.get(session_key, False): self.topic.views += 1 self.topic.save() self.request.session[session_key] = True # <-- until here kwargs['topic'] = self.topic return super().get_context_data(**kwargs) def get_queryset(self): self.topic = get_object_or_404(Topic, board__pk=self.kwargs.get('pk'), pk=self.kwargs.get('topic_pk')) queryset = self.topic.posts.order_by('created_at') return queryset
各Topicに対するページネーション
ページを遷移しやすいように各Topicに対するページネーションを追加する。
boards/models.py
を編集。
import math from django.db import models class Topic(models.Model): # ... def __str__(self): return self.subject def get_page_count(self): count = self.posts.count() pages = count / 20 return math.ceil(pages) def has_many_pages(self, count=None): if count is None: count = self.get_page_count() return count > 6 def get_page_range(self): count = self.get_page_count() if self.has_many_pages(count): return range(1, 5) return range(1, count + 1)
templates/topics.html
も編集する。
<table class="table table-striped mb-4"> <thead class="thead-dark"> <tr> <th>Topic</th> <th>Starter</th> <th>Replies</th> <th>Views</th> <th>Last Update</th> </tr> </thead> <tbody> {% for topic in topics %} {% url 'topic_posts' board.pk topic.pk as topic_url %} <tr> <td> <p class="mb-0"> <a href="{{ topic_url }}">{{ topic.subject }}</a> </p> <small class="text-muted"> Pages: {% for i in topic.get_page_range %} <a href="{{ topic_url }}?page={{ i }}">{{ i }}</a> {% endfor %} {% if topic.has_many_pages %} ... <a href="{{ topic_url }}?page={{ topic.get_page_count }}">Last Page</a> {% endif %} </small> </td> <td class="align-middle">{{ topic.starter.username }}</td> <td class="align-middle">{{ topic.replies }}</td> <td class="align-middle">{{ topic.views }}</td> <td class="align-middle">{{ topic.last_updated|naturaltime }}</td> </tr> {% endfor %} </tbody> </table>
これで以下のように各Topicに対してページネーションが表示されるようになる。
返信ページで表示する返信数を制限
返信ページで表示される返信を10個に制限する。
boards/models.py
class Topic(models.Model): # ... def get_last_ten_posts(self): return self.posts.order_by('-created_at')[:10]
templates/reply_topic.html
{% block content %} <form method="post" class="mb-4" novalidate> {% csrf_token %} {% include 'includes/form.html' %} <button type="submit" class="btn btn-success">Post a reply</button> </form> {% for post in topic.get_last_ten_posts %} <!-- here! --> <div class="card mb-2"> <!-- code suppressed --> </div> {% endfor %} {% endblock %}
返信投稿後に最後のページへ遷移するようにする
返信投稿後に最初のページへ遷移していたため、投稿した返信が表示されるように最後のページへ遷移するようにする。
templates/topic_posts.html
{% block content %} <div class="mb-4"> <a href="{% url 'reply_topic' topic.board.pk topic.pk %}" class="btn btn-primary" role="button">Reply</a> </div> {% for post in posts %} <div id="{{ post.pk }}" class="card {% if forloop.last %}mb-4{% else %}mb-2{% endif %} {% if forloop.first %}border-dark{% endif %}"> <!-- code suppressed --> </div> {% endfor %} {% include 'includes/pagination.html' %} {% endblock %}
boards/views.py
from django.urls import reverse @login_required def reply_topic(request, pk, topic_pk): topic = get_object_or_404(Topic, board__pk=pk, pk=topic_pk) if request.method == 'POST': form = PostForm(request.POST) if form.is_valid(): post = form.save(commit=False) post.topic = topic post.created_by = request.user post.save() topic.last_updated = timezone.now() topic.save() topic_url = reverse('topic_posts', kwargs={'pk': pk, 'topic_pk': topic_pk}) topic_post_url = '{url}?page={page}#{id}'.format( url=topic_url, id=post.pk, page=topic.get_page_count() ) return redirect(topic_post_url) else: form = PostForm() return render(request, 'reply_topic.html', {'topic': topic, 'form': form})
これだと最後のページへ遷移はするが、ページネーションの表示が適切でない。
さらに以下のように編集すると適切な表示になる。
templates/includes/pagination.html
(クリックで展開){% if is_paginated %}
<nav aria-label="Topics pagination" class="mb-4">
<ul class="pagination">
{% if page_obj.number > 1 %}
<li class="page-item">
<a class="page-link" href="?page=1">First</a>
</li>
{% else %}
<li class="page-item disabled">
<span class="page-link">First</span>
</li>
{% endif %}
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.previous_page_number }}">Previous</a>
</li>
{% else %}
<li class="page-item disabled">
<span class="page-link">Previous</span>
</li>
{% endif %}
{% for page_num in paginator.page_range %}
{% if page_obj.number == page_num %}
<li class="page-item active">
<span class="page-link">
{{ page_num }}
<span class="sr-only">(current)</span>
</span>
</li>
{% elif page_num > page_obj.number|add:'-3' and page_num < page_obj.number|add:'3' %}
<li class="page-item">
<a class="page-link" href="?page={{ page_num }}">{{ page_num }}</a>
</li>
{% endif %}
{% endfor %}
{% if page_obj.has_next %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.next_page_number }}">Next</a>
</li>
{% else %}
<li class="page-item disabled">
<span class="page-link">Next</span>
</li>
{% endif %}
{% if page_obj.number != paginator.num_pages %}
<li class="page-item">
<a class="page-link" href="?page={{ paginator.num_pages }}">Last</a>
</li>
{% else %}
<li class="page-item disabled">
<span class="page-link">Last</span>
</li>
{% endif %}
</ul>
</nav>
{% endif %}
まとめ
以上でチュートリアルで紹介されている掲示板アプリの実装は完了。
チュートリアルはDjango 1.11だがDjango 2.0でもURLConfやimport文を修正することで動作した。
完成形は下記リンクで確認できる(チュートリアルの作者が運用しているアプリ)。
次回以降はデプロイ方法について試していく。