今回のテーマは「カテゴリー毎のトピック一覧画面を作る」です。ここでは簡単なDjnagoのORM(Object Relational Mapper)の使い方を確認しておきましょう。
※本ページは確認画面付きのトピック作成画面を作るまで読まれた方を対象としています。そのためサンプルソースコードが省略されている場合があります。
データベースからのデータ取得
Djangoではクエリセットというイテレーション可能なオブジェクトを生成して、それを評価することでデータベースと情報をやり取りします。プログラマは直接SQLを書かずともクエリセットを生成する処理を行うことで、内部的にSQLが発行されてデータが処理されます。クエリセットに関してはQuerySet APIが用意されており、プログラマはモデルが持つマネージャを介してクエリセットを操作します。
参考:Django 1.0のAPIリファレンス:情報が古いのですが日本語なので理解しやすいかも知れませんので、記載しておきます。
既にトップページでトピック一覧を作っているのでデータベースからのデータの取得については扱っていますが、以前はorder_by関数で並べ替えをしただけでしたので、ここで基本的な操作を少し見てみます。全てのAPIに関してはAPIリファレンスを見ていただくとして、ここではよく使いそうなものをピックアップします。
クエリセットを生成し返す関数
filter(**kwargs):指定の照合パラメータに一致するオブジェクトの入った新たなクエリセット返す。
exclude(**kwargs):filterのNOT検索版
order_by(*fields):指定パラメータで昇順に並び替える。’-‘をつけると降順に並び替え
anotate(*args, **kwargs):検索結果に付帯情報をもたせる時に使用する。集計結果やサブクエリ使用時に使う。
クエリセットを生成する関数はドットでつないで記載することが出来ます。その場合はAND条件で結合されます。
クエリセット以外を返す関数
first():クエリセットの最初のオブジェクトを返す
last():クエリセットの最後のオブジェクトを返す
count():クエリセットのオブジェクトの個数を返す
カテゴリー毎のリスト表示テンプレート作成
前置きが長くなりました。まずはテンプレートを作成します。
templates/thread/category.html
{% extends 'base/base.html' %}
{% block title %}{{category.name}} - {{ block.super }}{% endblock %}
{% block content %}
<div class="ui grid stackable">
<div class="eleven wide column">
<div class="ui breadcrumb">
<a class="section">TOP</a>
<a class="active section">{{category.name}}</a>
</div>
<div class="ui segment">
<div class="content">
<div class="header"><h3>{{category.name}}</h3></div>
<div class="ui divided items">
{% if topic_list %}
{% for topic in topic_list %}
<div class="item">
<div class="content">
<div class="header">
<a href="{% url 'thread:topic' pk=topic.id %}"><h4>{{topic.title}}</h4></a>
</div>
<div class="meta">
<span class="name">{{topic.user_name}}</span>
<span class="date">{{topic.created}}</span>
</div>
</div>
</div>
{% endfor %}
{% else %}
<div class="ui warning message">トピックが存在しません</div>
{% endif %}
</div>
</div>
</div>
</div>
{% include 'base/sidebar.html' %}
</div>
{% endblock %}
もう特別解説は必要ないと思います。賢明な読者はすでに渡すパラメータがどのようなものか想像ついていると思います。次にビューを作ります。今回はリスト表示ということでListViewを継承したクラスビューを作ります。
thread/views.py(一部抜粋)
from django.views.generic import (
CreateView, FormView, DetailView, TemplateView, ListView)
class CategoryView(ListView):
template_name = 'thread/category.html'
context_object_name = 'topic_list'
def get_queryset(self):
return Topic.objects.filter(category__url_code = self.kwargs['url_code'])
def get_context_data(self, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx['category'] = get_object_or_404(Category, url_code=self.kwargs['url_code'])
return ctx
さて、ListViewについては良いと思います。今回はmodelパラメータがないですね。ListViewはmodelもしくはquerysetが必要です。今回はURLパラメータとして’url_code’を受ける必要があったのでget_queryset関数ないでquerysetを規定しました。肝心の中身ですが、前述のfilter関数が使われていますね。ここでトピックが持つカテゴリーの’url_code’を検索条件にしているところがキーポイントです。トピックのプロパティなら話が簡単です。id=3やtitle=’hogehoge’などで指定すれば良いのです。しかし今回は一度Categoryオブジェクトを取得するサブクエリを使いたくなるような場面ですよね。Djangoではモデルを跨ぐ検索も’__’でつなぐことで照合条件に指定できます。初心者が意外に躓くポイントなので解説しておきます。
また、get_context_data関数内ではget_object_or_404を使っています。以前に使っているので詳細は省きますがこれは存在しないurl_codeが指定された場合の対策です。
最後にURLを規定しましょう。thread/urls.pyは以下のようになります。
thread/urls.py(一部抜粋)
urlpatterns = [
path('create_topic/', views.TopicCreateView.as_view(), name='create_topic'),
path('<int:pk>/', views.TopicTemplateView.as_view(), name='topic'),
path('category/<str:url_code>/', views.CategoryView.as_view(), name='category'),
]
URLのパラメータとして今回は文字列を受けますのでstrで型を指定しています。では確認してみましょう。localhost:8080/thread/category/web_app/にアクセスしてみます。
今度はトピックが存在しないカテゴリを表示してみましょう。localhost:8080/thread/category/os/にアクセスしてみます。
本題とは外れますが、トピック詳細ページのパンくず(breadcrumbs)のURLをカテゴリーページのURLに直しておきましょう。
templates/thread/detail_topic.html(一部抜粋)
<div class="ui breadcrumb">
<a href="{% url 'base:top' %}" class="section">TOP</a>
<i class="right angle icon divider"></i>
- <a class="section">{{topic.category.name}}</a>
+ <a href="{% url 'thread:category' url_code=topic.category.url_code %}" class="section">{{topic.category.name}}</a>
<i class="right angle icon divider"></i>
<a class="active section">{{topic.title}}</a>
</div>
最後に
クエリセットの作成は慣れが必要だと思います。今回の例だけでは練習不足だと思いますので、出会う度にAPIリファレンスを読み想定する挙動をするクエリセットが作成できるように練習しましょう。次回で一章は終了予定です。トピック詳細画面にコメント表示と登録機能を付与していきます。
Sponsored Link
path(‘category//’, views.CategoryView.as_view(), name=’topic’),
name=’topic’ -> ‘category’ですね
ご指摘ありがとうございます。修正しておきます。
ctx = super().get_context_data(kwargs)
⇒ ctx = super().get_context_data(**kwargs)
ではないでしょうか?
ご指摘ありがとうございます。
おっしゃるとおり、誤記です。修正しておきます。
はじめまして。
こちらのサイトで勉強させて頂けて大変有難くおもいます。
しかし出所のわからないエラーに遭遇してしまい、よろしければご教示頂きたくコメントしました。
エラーコードは以下です。
NoReverseMatch at /
Reverse for ‘topic’ with keyword arguments ‘{‘pk’: 10}’ not found. 1 pattern(s) tried: [‘thread//$’]
エラーの最後の箇所はここのようですが何を改善すればよいかわかりません。
C:\Users\kwansei\Anaconda3\envs\django py3.7\lib\site-packages\django\urls\resolvers.py in _reverse_with_prefix
raise NoReverseMatch(msg)
ちなみにAnacondaのpython3.7仮想環境で動かしています。
どうかよろしくお願いいたします。
コメント失礼します。
先に質問させていただいた内容を解決することができましたので連絡します。
url.pyにおいて
path(‘/’, views.TopicTemplateView.as_view(), name=’topic’)
⇒path(‘/’, views.TopicTemplateView.as_view(), name=’topic’)
としたところ解決できました。
また、「1-20.コメント投稿機能を付与する」でも同様のエラーが発生し、同じ方法で対処することができました。
path(‘<int:pk>/’, views.TopicTemplateView.as_view(), name=’topic’)
と書きたかったのですが(小文字だと?)消えてしまいました。
みやびさん
コメントありがとうございます。”<int:pk>”部分につきHTMLタグとみなされてしまい表示出来ていませんでした。
気付かず失礼しました。ご迷惑をおかけしました。修正いたしました。