概要
前回の続きです。
今回は公開用のインタフェース、ビューの作成を焦点に解説していきたいと思います。
作成するビュー
今回の投票アプリでは、以下の4ビューが必要です。
- 質問のインデックスページ
- 最新の質問がいくつか表示されている。
- 質問の詳細ページ
- 結果を表示せず、質問テキストと投稿フォームを表示する。
- 質問の結果ページ
- 特定の質問の結果を表示する。
- 投票ページ
- 特定の質問を投票として受付する。
Djangoでは、各ページのコンテンツはビューで提供されます。
各ビューは単純なPythonの関数です。
Djangoはビューを、リクエストされたURLから決定します。
DjangoはURLconfという考え方で、URLパターンとビューを紐付けます。
ビューを作成する
実際に、ビューを作成していきましょう。
from django.shortcuts import render
from django.http import HttpResponse
def index(request):
return HttpResponse('Hello World from Polls')
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)
単純な文字列のHTTPレスポンスを返すだけのビューです。
実際にルーティング(URLconf)の設定を行います。
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^$', views.index, name='index'),
url(r'^(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
url(r'^(?P<question_id>[0-9]+)/results/$', views.results, name='results'),
url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'),
]
今回も、正規表現でURLconfを設定します。
今回利用している正規表現の内容を簡単に解説します。
まず、^は「〜で始まる。」、$は「〜で終わる。」を表します。
正規表現内を()で囲むと、そのパターンにマッチしたテキストをキャプチャし、ビュー関数の引数として送信されます。
Pの部分はマッチしたパターンを識別する為の名前を定義しています。
(ビュー関数のパラメータ名に当たる。)
[0-9]+は1桁以上の数字にマッチする正規表現です。
実際に動作するビューを書く
各ビューには2つの役割があります。
1つはリクエストされたページのコンテンツを含むHttpResponseオブジェクトを返す事。
もう1つは、Http404の様な例外を検出する事です。
ビューはデータベースからレコードを読みだしても、読み出さなくてもどちらでも構いません。
Djangoのテンプレートシステム、あるいはサードパーティのPythonテンプレートシステムを利用しても構いません。
また、PDFファイルの生成、XML出力、ZIPファイルの生成などPythonライブラリを使ってやりたい事を何でも実現できます。
Djangoにとって必要なのはHttpResponseか、あるいは例外かのどちらかです。
indexビューを利用して、前回作成したデータベースを操作してみましょう。
以下の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])
print(output)
return HttpResponse(output)
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)
修正が終わって、ブラウザで確認すると以下の文字列が表示されるはずです。
データベースの前回作成したquestionsテーブル(Questionモデル)からデータを引っ張ってきています。
しかし、このままでは、毎回デザインもビューごとに設定しなければなりません。
そこで、テンプレートファイルを作成してデザインを統一します。
まずは、pollsディレクトリ以下にtemplatesディレクトリを作成します。
Djangoはこのtemplates以下をテンプレートと見なしてテンプレートを探しにいきます。
ここから少しややこしい話なのですが、Djangoがどの様にテンプレートをロードし、レンダリングするかについて説明します。
デフォルトのDjangoTemplatesの設定ファイルは、APP_DIRSのオプションがTrueに設定されています。
設定により、DjangoTemplatesはINSTALLED_APPSのそれぞれのサブディレクトリのtemplatesを検索します。
先ほど作成したtemplatesディレクトリ内にpollsというディレクトリを作成し、その中にhtmlファイルを作成します。
(ローダーはpolls/index.htmlと認識できる様になる。)
要は、mysite/polls/templates/polls/index.html
というパスになります。
こうする事で、名前空間の汚染を防ぎ、polls/index.htmlというテンプレートとして、Django内で識別する事が可能になります。
一種の決まり文句的な構造なので、少し違和感がありますが慣れましょう。
では、実際にテンプレートを制作します。
{% 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 %}
ビューから渡ってきた変数(後述)の存在確認をし、存在すれば、ループで出力しています。
続いて、ビューの修正をします。
やることは、
- どのテンプレートを利用するか
- テンプレートに渡す変数の受け渡し
です。
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))
修正箇所は非常にシンプルです。
① loaderをインポート
② loader.get_templateでtemplateファイルの選定し、変数を返す
③ templateに渡す辞書型の変数を定義
④ ②で作成したテンプレート変数のrenderメソッドを利用し、HttpResponseを返す。
テンプレートをロードしてコンテキストに値を入れ、テンプレートをレンダリングした結果をHttpResponseで返す、というイディオムは非常によく使われる為、ショートカットも存在します。
以下がショートカットを使った例です。
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)
非常にすっきりしました。
この作業で、loaderやHttpResponseの読み込みも不要になります。
render()は第1引数にrequestオブジェクト、第2引数にテンプレート名、第3引数にその他のオプションを辞書型で受けます。
404エラーページの送出
指定された投票の質問文を表示するページの詳細ビュー(detail)を作成します。
ビューは以下の様になります。
from django.shortcuts import render
from django.http import Http404
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)
def detail(request, question_id):
try:
question = Question.objects.get(pk=question_id)
context = {
'question': question
}
except Question.DoesNotExist:
raise Http404('Question does not exist')
return render(request, 'polls/detail.html', context)
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)
リクエストIDがない場合は、404ページに飛ぶ様になっています。
get()を実行し、オブジェクトが存在しない場合にはHttp404を送出することは非常によく使われるイディオムの為、こちらもDjangoがショートカットを用意してくれています。
以下の様になります。
404のテストができたので、detailページを作り込んでいきます。
<h1>{{ question.question.text }}</h1>
<ul>
{% for choice in question.choice_set.all %}
<li>{{ choice.choice_text }}</li>
{% endfor %}
</ul>
ループの中のquestion.choice_set_all
はquestionに紐付くchoiceを取得する為のメソッドです。
上記だけで、questionに紐付く、choiseを取得する事ができました。
テンプレート内のハードコーディングされたURLを削除
urls.pyでURL名(name)を指定していれば、html内でurl関数を用いてハードコーディング部分を削除できます。
{% if latest_question_list %}
<ul>
{% for question in latest_question_list %}
<li><a href="{% url 'detail' question.id %}">{{question.question_text}}</a></li>
{% endfor %}
</ul>
{% else %}
<p>No polls are available.</p>
{% endif %}
URL名の名前空間
今はアプリがpollsのみですが、普通のプロジェクトでは、多くのアプリケーションで1つのプロジェクトを整形します。
その際にURL名がindexやdetailだと名前衝突を起こす可能性が高いです。
Djangoでは名前衝突を回避する為に、以下の様な変数を定義します。
from django.conf.urls import url
from . import views
app_name = 'polls' # アプリ名を追加
urlpatterns = [
url(r'^$', views.index, name='index'),
url(r'^(?P<question_id>[0-9]+)/$', views.detail, name='detail'),
url(r'^(?P<question_id>[0-9]+)/results/$', views.results, name='results'),
url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'),
]
併せて、index.htmlも修正します。
{% if latest_question_list %}
<ul>
{% for question in latest_question_list %}
<li><a href="{% url 'polls:detail' question.id %}">{{question.question_text}}</a></li>
{% endfor %}
</ul>
{% else %}
<p>No polls are available.</p>
{% endif %}
これで名前衝突も防ぎながら、より直感的にミニマムな名前をつける事ができる様になりました。
まとめ
ここまででかなりアプリケーションの作成が出来る様になったと思います。
今回は、
- ビューの作成
- テンプレートの作成
- テンプレートタグの使い方
- Djangoが提供するショートカットの利用
- 名前衝突回避方法
をやってきました。
次回は、フォーム関連の処理とコードの縮小化を解説したいと思います。