前回 #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/)を見てみると、以下のように出力されます。
URLに打ち込んだ任意の質問IDをDjangoが読み取り、prepolls/urls.py
のpath()
で指定したviews.detail
(つまりprepolls/views.pyのdetail()関数
)を表示しています。任意の質問IDがページ内にも反映されていますね。
質問一覧を表示する
投票のトップページ(/polls/)には現在指定した文字列のみ表示されていますが、ここに最新の質問5件を表示するようにしてみます。
polls/views.py
のindex()
関数の中身を編集します。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件が表示されています!
管理画面( 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.py
のindex()
に関係する部分を書き換えてみます。
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()
でまとめることができます。第二引数でテンプレートの場所を指定します。
何より、loader
とHttpResponse
のインポートが不要になりました。
(ただし、現状detail()
とresult()
とvote()
でHttpRespose
を使用しているので、そちらでも引き続き使用する場合は、全体としてはインポートが必要です。)
404エラーを設定する
さて、これでトップページに質問をリスト状に並べることができました。
リストからはそれぞれの質問詳細ページに飛ぶことができます。もちろんリンクからは質問詳細ページが表示されますが、アドレスバーから存在しない質問IDをリクエストしても質問詳細ページが表示されてしまいます。(試してみてください!)
Djangoは、内容からページを生成するのではなく、リクエストされたURLから処理を辿ってページを生成するのです。基本的には存在しないページは404になるようになっていますが、チュートリアルで新しく作ったページにはそのような処理が含まれていません。
なので、存在しない質問IDのページは404エラーを返すようにし、テンプレートも使用するようにビューを修正します。
polls/views.py
のdetail()
(質問詳細ページ)に関係する部分は以下です。
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エラーの雛形をインポート
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
を持ってきます。
pk
はprimary 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
を指定します。しかし、複数のアプリがあって、他のアプリにもindex
やdetail
がある場合、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の指定 | プログラマーになった 「中卒」 男のブログ