January 25, 2021
←前回:Day 19 確認画面付きのトピック作成画面を作る
「Djangoを学びたい」とのことでありましたら[Day 1]Djangoの開発環境から読むことをおすすめします。
#はじめに
ここでは簡単なDjnagoのORM(Object Relational Mapper)の使い方を確認しておきましょう。
#データベースからのデータ取得
Djangoではクエリセットというイテレーション可能なオブジェクトを生成して、それを評価することでデータベースと情報をやり取りします。
プログラマは直接SQLを書かずともクエリセットを生成する処理を行うことで、内部的にSQLが発行されてデータが処理されます。
クエリセットに関してはQuerySet APIが用意されており、プログラマはモデルが持つマネージャを介してクエリセットを操作します。
既にトップページでトピック一覧を作っているのでデータベースからのデータの取得については扱っていますが、以前はorder_by関数で並べ替えをしただけでしたので、ここで基本的な操作を少し見てみます。
全てのAPIに関してはAPIリファレンスを見ていただくとして、ここではよく使いそうなものをピックアップします。
クエリセットを生成し返す関数
all():全てのオブジェクトが入ったクエリセットを返す。
filter(**kwargs):指定の照合パラメータに一致するオブジェクトの入った新たなクエリセット返す。
exclude(**kwargs):filterのNOT検索版
order_by(*fields):指定パラメータで昇順に並び替える。’-‘をつけると降順に並び替え
anotate(*args, **kwargs):検索結果に付帯情報をもたせる時に使用する。集計結果やサブクエリ使用時に使う。
クエリセットを生成する関数はドットでつないで記載することが出来ます。
その場合はAND条件で結合されます。
クエリセット以外を返す関数
get(**kwargs):照合パラメータに合致するオブジェクトを返す
first():クエリセットの最初のオブジェクトを返す
last():クエリセットの最後のオブジェクトを返す
count():クエリセットのオブジェクトの個数を返す
#カテゴリー毎のリスト表示テンプレート作成
前置きが長くなりました。まずはテンプレートを作成します。
{% 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を継承したクラスビューを作ります。
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'])
さて、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は以下のようになります。
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/にアクセスしてみます。
と、ここでweb_appにアクセスできるらしいのですが、エラーが出てできませんでした。
NameErrorで、thread/views.pyのline 100で'Category'は定義されていないと出てしまいました。
なので、エラー文通り'Category'を定義しました。
from . models import Topic #修正前
from . models import Topic, Category #修正後
これをやったら直りました。
今後支障が出なければいいのですが、、、
8080/threads/category/wed_appにアクセスします。
今度はトピックが存在しないカテゴリを表示してみましょう。localhost:8080/thread/category/os/にアクセスしてみます。
本題とは外れますが、トピック詳細ページのパンくず(breadcrumbs)のURLをカテゴリーページのURLに直しておきましょう。
<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>
#おわりに
今日はあまり集中力がなかったです。
睡眠の量は確保しているので、おそらく質の方が悪かったのでしょう。
そんな時僕は、体を動かしたり頭は使うが集中力のいらないゲームをします。
日々、トライアンドエラーですが改善して最良を尽くせるように頑張ります。
それではまたまた