WEBTODESIGN

#4 VSCでDjango開発入門【ページ制御・テンプレート編】

前回 #3 VSCでDjango開発入門【データベースの設定・管理画面編】の、次のステップです。

公式チュートリアルのはじめての Django アプリ作成、その3に沿って、ページの追加や見た目の成形をしていきます。

このステップでは、URLの管理制御と、テンプレートの追加ができるようになります。

前提

  • コマンドはVisual Studio Code内のターミナルで実行します。
  • 基本的にコマンドはWindowsの場合となっています。

ビューの追加

今回は投票アプリに必要なページを追加していきます。

ページを追加するにはビューを編集します。ビューはどこからどのようにデータを持ってくるか指示するものでしたね。(#2 VSCでDjango開発入門【文字を表示してみる編】

まず必要と思われるのは、以下のようなページです。

  • 投票アプリのトップページ(/polls/)【作成済み】
  • 各質問の詳細ページ(/polls/質問ID/)
  • 各質問の投票結果ページ(/polls/質問ID/results/)
  • 各質問の投票ページ(/polls/質問ID/vote/)

内容の用意

すでに投票アプリのトップページ(index)は作ってあるので、他の3つを追記します。

まずは、polls/views.pyでページの内容を用意します。

from django.shortcuts import render
from django.http import HttpResponse

# 投票アプリのトップページ
def index(request):
    return HttpResponse("Hello World!")

# 各質問の詳細ページ
def detail(request, question_id):
    return HttpResponse("You're looking at question %s." % question_id)

# 各質問の投票結果ページ
def results(request, question_id):
    response = "You're looking at the results of question %s."
    return HttpResponse(response % question_id)

# 各質問の投票ページ
def vote(request, question_id):
    return HttpResponse("You're voting on question %s." % question_id)

表示の指示

次に、URLと結びつけます。prepolls/urls.pyを編集します。

from django.urls import path

from . import views

urlpatterns = [
    # ex: /polls/
    path('', views.index, name='index'),
    # ex: /polls/質問ID/
    path('<int:question_id>/', views.detail, name='detail'),
    # ex: /polls/質問ID/results/
    path('<int:question_id>/results/', views.results, name='results'),
    # ex: /polls/質問ID/vote/
    path('<int:question_id>/vote/', views.vote, name='vote'),
]

この状態で任意の質問ID(数字)を入れて各質問の詳細ページ(/polls/質問ID/)を見てみると、以下のように出力されます。

/polls/10/の出力

URLに打ち込んだ任意の質問IDをDjangoが読み取り、prepolls/urls.pypath()で指定したviews.detail(つまりprepolls/views.pyのdetail()関数)を表示しています。任意の質問IDがページ内にも反映されていますね。

質問一覧を表示する

投票のトップページ(/polls/)には現在指定した文字列のみ表示されていますが、ここに最新の質問5件を表示するようにしてみます。

polls/views.pyindex()関数の中身を編集します。polls/views.pyは以下のようになります。

from django.shortcuts import render
from django.http import HttpResponse
from .models import Question

def index(request):
    latest_question_list = Question.objects.order_by('-pub_date')[:5]
    output = ', '.join([q.question_text for q in latest_question_list])
    return HttpResponse(output)

# ...

記述内容はひとまずおいておいて、これで一度ページを見てみます。

サーバーを立ち上げた状態でhttp://127.0.0.1:8000/polls/ にアクセスすると…

最新の質問5件が表示されているようす

最新の質問5件が表示されています!

管理画面( http://127.0.0.1:8000/admin/ )で質問を増やしたり減らしたりして試してみてください。内容が反映されるはずです。

 

では、具体的になにを記述したのかみていきます。

Questionモデルの読み込み

from .models import Question

.modelsモジュール(pyファイル)のQuestionモデルをインポートしています。インポートすることで中身を使うことができます。

index()関数を定義

def index(request):

index()関数を定義します。

モデルの内容を取り出す

latest_question_list = Question.objects.order_by('-pub_date')[:5]

Questionモデルの内容を、日付が新しい順に5件取り出す指示をlatest_question_listに代入します。

.order_by()で並び順を変えることができます。今回は日付を格納しているpub_dateをもとに並べます。デフォルトは昇順で、ハイフン(-)をつけることで降順となります。日付の降順は新しいものが上に表示されるということです。

[:5]は配列の5個目まで(配列の0~4番目)という指示です。

取り出したデータをリストにする

output = ', '.join([q.question_text for q in latest_question_list])

取り出したデータから、質問内容をコンマで区切って並べる指示をoutputに代入します。

.join()for文を使って、先程取り出したデータ(latest_question_list)から質問内容(question_text)を抜き出して、,で区切ってリストにしています。

結果を返す

return HttpResponse(output)

最後に、HttpResponseで先程代入したoutputをページに引き渡します。

 

以上の流れで、最新の質問5件を表示しています。

テンプレート機能

さて、無事にページに質問を表示できるようになりました。

しかし、コンマで区切っただけではすごく見づらいですよね…?しかもソースコードをみてみると、<body></body>内にベタ打ちされています。

  • ちゃんとしたHTML構造にする
  • デザインの変更をしやすくする
  • デザインの使い回しをできるようにする

そのために、テンプレートを作成します!

テンプレートの作成

まずは、polls(アプリケーション名)ディレクトリにtemplatesディレクトリを作成します。Djangoはここからテンプレートを探します。更にその中にpolls(アプリケーション名)のディレクトリを作成し、index.htmlファイルを設置します。

つまりこのような構造になります。

src
└── polls
    └── templates
        └── polls
            └── index.html

pollsが2回でてくるのがスッキリしませんが、Djangoが正しくテンプレートを認識するために必要な構造なのでこの通りにします。

テンプレートの中身

polls/templates/polls/index.htmlに以下のように記述します。

{% if latest_question_list %}
    <ul>
    {% for question in latest_question_list %}
    <li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>
    {% endfor %}
    </ul>
{% else %}
    <p>No polls are available.</p>
{% endif %}

Djangoのテンプレート内では、テンプレートタグ({% %})と変数({{ }}を使うことができます。

WordPressのテンプレートを作ったことがある方はピンと来ましたよね。テンプレートタグでは動作の制御や分岐、ループをしたり様々なことができます。変数はデータベースの内容を表示したり、予め作ったコードを実行するなどができます。

テンプレートタグ一覧は公式ドキュメントの以下のページにあります。
Built-in template tags and filters | Django documentation | Django

テンプレートの読み込み

テンプレートを作っただけではどことも紐づいていないので、ビューでテンプレートを読み込む必要があります。

polls/views.pyの内容を更新します。

from django.shortcuts import render
from django.http import HttpResponse
from django.template import loader
from .models import Question

def index(request):
    latest_question_list = Question.objects.order_by('-pub_date')[:5]
    template = loader.get_template('polls/index.html')
    context = {'latest_question_list': latest_question_list}
    return HttpResponse(template.render(context, request))

# ...

内容はひとまずおいておいて、どのように表示されるか確認してみます。

サーバーを立てた状態で http://127.0.0.1:8000/polls/ を再読込してみると…

リスト上に並んだ質問

質問がリスト状に並んでいます!

では具体的に内容をみていきましょう。

ローダーの読み込み

from django.template import loader

まずはテンプレートローダーをインポートします。これでテンプレートが読み込める状態になりました。

テンプレートの指定

template = loader.get_template('polls/index.html')

次に、get_template()でテンプレートの場所を指定し、読み込みます。

 テンプレートで使える変数を登録

context = {'latest_question_list': latest_question_list}

Questionモデルの内容を、日付が新しい順に5件取り出す指示をlatest_question_listに代入していますよね。それを、テンプレート内で使えるようにcontextに辞書登録します。

こうすることで、テンプレート内でlatest_question_listを使うことができ、ページにlatest_question_listの内容を引き渡すことができます。(左側のキーがテンプレート内で使える変数となります。)

結果を返す

return HttpResponse(template.render(context, request))

最後に、render()メソッドを利用してHttpResponseをページに返します。

ショートカットでの記述

テンプレートをロードしてコンテキストに値を入れてんプレートをレンダリングした結果をHttpResponseで返すという流れは、非常によく使われるので、Djangoはショートカットを用意してくれています。

polls/views.pyindex()に関係する部分を書き換えてみます。

from django.shortcuts import render
from .models import Question

def index(request):
    latest_question_list = Question.objects.order_by('-pub_date')[:5]
    context = {'latest_question_list': latest_question_list}
    return render(request, 'polls/index.html', context)

かなり短くなりました!

templateの定義が不要になり、render()でまとめることができます。第二引数でテンプレートの場所を指定します。

何より、loaderHttpResponseのインポートが不要になりました。

(ただし、現状detail()result()vote()HttpResposeを使用しているので、そちらでも引き続き使用する場合は、全体としてはインポートが必要です。)

404エラーを設定する

さて、これでトップページに質問をリスト状に並べることができました。

リストからはそれぞれの質問詳細ページに飛ぶことができます。もちろんリンクからは質問詳細ページが表示されますが、アドレスバーから存在しない質問IDをリクエストしても質問詳細ページが表示されてしまいます。(試してみてください!)

Djangoは、内容からページを生成するのではなく、リクエストされたURLから処理を辿ってページを生成するのです。基本的には存在しないページは404になるようになっていますが、チュートリアルで新しく作ったページにはそのような処理が含まれていません。

なので、存在しない質問IDのページは404エラーを返すようにし、テンプレートも使用するようにビューを修正します。

polls/views.pydetail()(質問詳細ページ)に関係する部分は以下です。

from django.shortcuts import render
from django.http import Http404
# ...
from .models import Question

# ...

def detail(request, question_id):
    try:
        question = Question.objects.get(pk=question_id)
    except Question.DoesNotExist:
        raise Http404("Question does not exist")
    return render(request, 'polls/detail.html', {'question': question})

# ...

また、テンプレートも作成します。polls/detail.htmlを作り、以下の1文を記述して保存します。

{{ question }}

これで、存在しない質問IDをリクエストしてみると…

404が帰ってきた様子

404エラーが返ってきました!

内容を詳しくみてみます。

404エラーの雛形をインポート

from django.http import Http404

Djangoが404エラーページの内容を用意してくれているので、インポートします。

例外処理

try:
    question = Question.objects.get(pk=question_id)
except Question.DoesNotExist:
    raise Http404("Question does not exist")

try-excep文を使って例外処理を行っています。tryを実行して、Question.DoesNotExistというエラーが出た場合はexceptを実行する(Htto404()を実行)という処理です。

get()question_idを持ってきます。

pkprimary keyというデータベースの用語です。これについては、以下のページがわかりやすかったです。
主キー(primary key)とは – ITを分かりやすく解説

結果を返す

return render(request, 'polls/detail.html', {'question': question})

render()で結果を返して完了です。

ショートカットでの記述

404を返す処理も頻繁に使うので、テンプレート同様、Djangoがショートカットを提供してくれています。

ショートカットを使用したpolls/views.pyは以下。

from django.shortcuts import get_object_or_404, render
# ...
from .models import Question
# ...
def detail(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    return render(request, 'polls/detail.html', {'question': question})
# ...

短くなりましたね!

コンテキストにget_object_or_404()関数を使って、オブジェクトがない場合は404を返すという処理になります。

テンプレートシステムを使う

polls/detail.htmlを以下のようにします。

<h1>{{ question.question_text }}</h1>
<ul>
{% for choice in question.choice_set.all %}
    <li>{{ choice.choice_text }}</li>
{% endfor %}
</ul>

これで質問詳細ページを表示してみると…

先程登録した選択肢がリストで表示されていますね!

テンプレートの詳しい動作は下記の公式ドキュメントをご確認ください。
テンプレート | Django ドキュメント | Django

テンプレート内のURLを柔軟にする

現在index.htmlのテンプレートではURLは以下のようになっています。

href="/polls/{{ question.id }}/"

URLを変えたくなったときに、このようなURLが多いと変更が困難です。

なので、テンプレートタグを使用して、変更を容易にしましょう!

URLのテンプレートタグ

{%url%} タグを使用します。先程のURLが、こうなります。

href="{% url 'detail' question.id %}"

これは、以下のようにpolls/urls.pyで定義されているURLから探されます。

path('<int:question_id>/', views.detail, name='detail'),

path()nameの部分です!この部分を{%url%}は目印にします。

{% url 'detail' question.id %}question.id部分はパラメータです。このパラメータをurls.py<int:question_id>に渡しています。

このようにURLのテンプレートタグを使用しておけば、URLを変えたいときはurls.pyの一箇所だけの変更で済みます。

名前空間の追加

現在このプロジェクトには「polls」アプリケーションしか存在しません。

URLの指定で、'index''detail'nameを指定します。しかし、複数のアプリがあって、他のアプリにもindexdetailがある場合、Djangoはどうやって識別すればいいでしょうか?

それは、名前空間とと呼ばれる機能を設定すれば解決します。

名前空間はpolls/urls.pyで設定します。

# ...

app_name = 'polls'
urlpatterns = [
    # ...
]

urlpatternsの上にapp_name = 'アプリケーション名'を追加します。

そして、URLテンプレートも修正します。

href="{% url 'polls:detail' question.id %}"

urlで指定するnameを、'名前空間:name'というように記述します。

こうすれば、Djangoがpollsのdetailであることを識別できます。

 

以上で、このステップは終了です。

今回は少し長くて内容も濃かったですね。引き続き頑張りましょう。

シリーズ一覧

#1 VSCでDjango開発入門【環境構築・プロジェクト生成編】
#2 VSCでDjango開発入門【文字を表示してみる編】
#3 VSCでDjango開発入門【データベースの設定・管理画面編】
#4 VSCでDjango開発入門【ページ制御・テンプレート編】(今ココ)
#5 製作中…

参考元

Djangoを始めよう! 〜チュートリアル①〜 – Qiita
【図解Python初心者向け】Django基本的な使い方 | CodeCampus
Pythonのモジュールとimportとfrom入門 – Qiita
Djangoでorder byの降順のやり方がわからんから調べた – Qiita
Pythonのスライス – Qiita
【Python】スライス操作についてまとめ – Qiita
【Python】joinの正しい使い方 – Qiita
主キー(primary key)とは – ITを分かりやすく解説
Pythonで例外を発生させる:raise | UX MILK
Djangoのテンプレートにおいてaタグのurlを指定する方法:実は3パターンで網羅できる | 日本語で学ぶDjango | Django Baby
【Django】名前空間を使ったURLの指定 | プログラマーになった 「中卒」 男のブログ