3.ビューとテンプレート#
ビューを追加#
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)
path()
コールを追加して、新しい view を polls.urls
モジュールと結びつける。
from django.urls import path
from . import views
urlpatterns = [
# ex: /polls/
path('', views.index, name='index'),
# ex: /polls/5/
path('<int:question_id>/', views.detail, name='detail'),
# ex: /polls/5/results/
path('<int:question_id>/results/', views.results, name='results'),
# ex: /polls/5/vote/
path('<int:question_id>/vote/', views.vote, name='vote'),
]
ブラウザで、 "/polls/34/" を見てみる。
detail()
メソッドが実行され、URLで提供したIDが表示される。
"/polls/34/results/" と "/polls/34/vote/" も試すと、結果と投票ページのプレースホルダがそれぞれ表示される。
誰かがWebサイトの 「/polls/34/」 をリクエストすると、 Django は ROOT_URLCONF に指定されている Python モジュール mysite.urls をロードする。
そのモジュール内の urlpatterns という変数を探し、順番にパターンを検査していく。
polls/ にマッチした箇所を見つけた後、一致した文字列 ("polls/") を取り除き、残りの文字列である "34/" を次の処理のために 『polls.urls』 の URLconf に渡す。これは <int:question_id>/
に一致し、結果として下記のようにdetail()
が呼び出される。
実際に動作するビューを書く#
各ビューには二つの役割がある 1. リクエストされたページのコンテンツを含むHttpResponseオブジェクトを返すこと 2. Http404 のような例外の送出
Django にとって必要なのは HttpResponse か、あるいは例外。 試しに次のような index() ビューを作る。 これは、システム上にある最新の 5 件の質問項目をカンマで区切り、日付順に表示するビュー:
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)
# Leave the rest of the views (detail, results, vote) unchanged
- このコードには問題があり、ビューの中で、ページのデザインがハードコードされている。
- ページの見栄えを変更するたびに、 PythonコードをDjango のテンプレートシステムを使って、ビューから使用できるテンプレートを作成し、Python からデザインを分離する
-
最初に、 polls ディレクトリの中に、 templates ディレクトリを作成します。 Django はそこからテンプレートを探す
-
プロジェクトの TEMPLATES には、Django がどのようにテンプレートをロードしレンダリングするかが書かれている。
- デフォルトの設定ファイルでは、 DjangoTemplates バックエンドが設定されており、その APP_DIRS のオプションが True になっている。
- 規約により、 DjangoTemplates は INSTALLED_APPS のそれぞれの "templates" サブディレクトリを検索する
- 先ほど作成した templates ディレクトリ内で polls というディレクトリを作成し、さらにその中に index.html というファイルを作成する。(テンプレートは polls/templates/polls/index.html に書く必要がある。)
- app_directories テンプレートローダは前述のように動くため、Django 内でこのテンプレートを単に polls/index.html のように参照できる。
テンプレートの名前空間
- 作ったテンプレートを (polls という別のサブディレクトリを作らずに) 直接 polls/templates の中に置いてもいいのではないか?しかし、それは実際には悪い考え。
- Django は、名前がマッチした最初のテンプレートを使用するため、もし 異なる アプリケーションの中に同じ名前のテンプレートがあった場合、Django はそれらを区別することができない。
- そのため、Django に正しいテンプレートを教えてあげる必要があるが、一番簡単な方法は、それらに 名前空間を与えること。アプリケーションと同じ名前をつけた もう一つの ディレクトリの中にテンプレートを置いた理由はここにある
- テンプレートには次のコードを記述
Info
チュートリアルを短くするために、すべてのテンプレートの例では不完全なHTMLを使用しています。独自のプロジェクトでは 完全な HTML ドキュメント を使用する必要があります。
このテンプレートを使用するために polls/views.py の index ビューを更新する
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))
- このコードは、 polls/index.html というテンプレートをロードし、そこにコンテキストを渡す。
- コンテキストは、テンプレート変数名を Python オブジェクトにマッピングする辞書。
http://localhost:8000/polls/
を開くと「Whtat's up?」が箇条書きで表示される
ショートカット: render()#
テンプレートをロードしてコンテキストに値を入れ、テンプレートをレンダリングした結果を HttpResponse
オブジェクトで返す、というイディオムは非常によく使われます。
Django はこのためのショートカットを提供する。これを使って 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)
全部の view をこのように書き換えてしまえば、 loader や HttpResponse を import する必要はなくなる (detail 、 results 、 vote を引き続きスタブメソッドにするなら、 HttpResponse はそのままにしておいたほうがいい)。 render() 関数は、第1引数として request オブジェクトを、第2引数としてテンプレート名を、第3引数(任意)として辞書を受け取ります。この関数はテンプレートを指定のコンテキストでレンダリングし、その HttpResponse オブジェクトを返します。
404 エラーの送出#
このビューは、リクエストした ID を持つ質問が存在しないときに Http404 を送出する。
from django.http import Http404
from django.shortcuts import render
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
テンプレートには以下のように記述する
get_object_or_404()#
get_object_or_404()
はget()を実行し、
オブジェクトが存在しない場合にはHttp404
を送出することは非常によく使われるイディオム
Django はこのためのショートカットを提供しています。ショートカットを使って、 detail() ビューを書き換える:
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() 関数は、Django モデルを第一引数に、任意の数のキーワード引数を取り、モデルのマネージャの get() 関数に渡します。オブジェクトが存在しない場合は Http404 を発生させます。
Tip
なぜ ObjectDoesNotExist 例外を高水準で自動的にキャッチせず、ヘルパー関数 get_object_or_404() を使うのでしょうか、また、なぜモデル API に ObjectDoesNotExist ではなく、 Http404 を送出させるのでしょうか? 答えは、モデルレイヤとビューレイヤをカップリングしてしまうからです。 Django の最も大きな目標の一つは、ルーズカップリングの維持にあります。いくつかの制御カップリングは、 django.shortcuts モジュールの中にあります。
get_list_or_404()という関数もある。 この関数は get_object_or_404() と同じように動きますが、 get() ではなく、 filter() を使う。リストが空の場合は Http404 を送出する。
テンプレートシステムを使う#
コンテキスト変数を question とすると、 polls/detail.html テンプレートは次のようになる:
<h1>{{ question.question_text }}</h1>
<ul>
{% for choice in question.choice_set.all %}
<li>{{ choice.choice_text }}</li>
{% endfor %}
</ul>
- テンプレートシステムは、変数の属性にアクセスするためにドット検索の構文を使用
{{ question.question_text }}
を例にすると、はじめに Django は question オブジェクトに辞書検索を行う。- それに失敗したら、今度は属性として検索を行う。
- もし属性の検索に失敗すると、リストインデックスでの検索を行う。
- メソッドの呼び出しは {% for %} ループの中で行う。
question.choice_set.all
は、 Python コードのquestion.choice_set.all()
と解釈される。- その結果、Choice オブジェクトからなるイテレーション可能オブジェクトを返し、
{% for %}
タグで使えるようになる。 - テンプレートの詳しい動作はテンプレートガイドを参照。
テンプレート内のハードコードされたURLを削除#
polls/index.html テンプレートで質問へのリンクを書いたとき、リンクの一部は次のようにハードコードされていた
このハードコードされた、密結合のアプローチの問題は、プロジェクトにテンプレートが多数ある場合、URLの変更が困難になってしまう。 しかし、 polls.urls モジュール の path() 関数で name 引数を定義したので、テンプレートタグの {%url%} を使用して、URL 設定で定義されている特定の URL パスへの依存をなくすことができる:
これが機能するのは、 polls.urls モジュールに指定されたURLの定義を検索するから。 'detail' のURL名は以下の箇所で定義されている:
投票の詳細ビューの URL を何か他のものに変更したい場合、たとえば polls/specifics/12/ のように変更したいとき、対象となるテンプレートを変更する代わりに、 polls/urls.py を変更する:
URL 名の名前空間#
このチュートリアルプロジェクトが持つアプリは polls アプリ1つだけ。
実際の Django プロジェクトでは、5個、10個、20個、あるいはそれ以上のアプリがあるかもしれない。
Django はどうやってこれらの間の URL 名を区別するのか? 例えば、 polls アプリは detail ビューを含むが、同じプロジェクトにブログのためのアプリがあり、そのアプリも同名のビューを含むかもしれない。
{% url %}
テンプレートタグを使ったとき、 Django はどのアプリのビューに対して url を作成すればいいか? これを Django にどう知らせればいいか。
答えは、 URLconf に名前空間を追加すること。どうぞ polls/urls.py
ファイル内で app_name を追加し、アプリケーションの名前空間を設定してください。
from django.urls import path
from . import views
app_name = 'polls'
urlpatterns = [
path('', views.index, name='index'),
path('<int:question_id>/', views.detail, name='detail'),
path('<int:question_id>/results/', views.results, name='results'),
path('<int:question_id>/vote/', views.vote, name='vote'),
]
polls/index.html テンプレートを以下の形から、
以下の形にし、名前空間つきの詳細ビューを指すようにする: