Django1.5 tutorial Part3 個人的メモ
Part 3ではwiewについて説明している.
哲学
view - はDjnagoアプリ中の一つのWebページに相当する.これは通常指定された(HTMLを表現する)テンプレートを持ち指定された機能を提供する.このチュートリアルのWeb-Pollアプリでは以下のviewを持つ
Poll "index" ページ - 最近のPollを表示する
- Poll "detail" ページ - ひとつのPoll questionを表示.結果ではなく投票用のフォーム表示する
- Poll "result" ページ - 特定のPollの結果を表示する
- Vote action - あるPollにおける特定の選択の投票を操作する
DjangoではWebページやその他のコンテンツはviewによってもたらさせる.DjangoはリクエストのURLを基に一つのviewを選択する.URLからviewを得るためにDjangoではURLConfs
という仕組みを使う.URLConfsはURLの(文字列)パターンとviewを紐付けるもので,urls.py
ファイルに紐付け情報を記述する.
最初のviewを書く
polls/views.py
に以下を記述する.
# polls/views.py from django.http import HttpResponse def index(request): return HttpResponse("Hello, world. You're at the poll index.")
このviewを呼ぶためにはURLConfを使ってURLにこのindex()関数を紐付ける必要がある.polls/urls.py
を作成して内容を以下のようにする.
# polls/urls.py from django.conf.urls import patterns, url from polls import views urlpatterns = patterns('', url(r'^$', views.index, name='index') )
次の手順はルートのURLConf(settings.pyで指定する.デフォルトはプロジェクト名のディレクトリ配下に生成されたurls.py)内でpolls.urls
モジュールにリンクを追加することである.これはmysite/urls.py
中でinclude()
メソッドを追加することで実現する.
# mysite/urls.py from django.conf.urls import patterns, include, url from django.contrib import admin admin.autodiscover() urlpatterns = patterns('', url(r'^polls/', include('polls.urls')), url(r'^admin/', include(admin.site.urls)), )
これでhttp://localhost:XXXX/polls/
で上記で作成したpolls.views.index()
の結果が得られる.
url()について
url()
関数は二つの必須引数(regex, view)と二つのオプション引数(kwargs, name)をとる.
url() 引数: regex
urlパターンを指定する正規表現.この正規表現にマッチすればviewに指定された関数を呼び出す.この正規表現ではURL文字列中のGETやPOSTメソッドのパラメータ,ドメイン名の部分はマッチング対象外である.
例) リクエスト: http://www.example.com/myapp/ の場合 URLConfでパターンマッチング対象となるのは:myapp/ リクエスト: http://www.example.com/myapp/?page=3 の場合 URLConfでパターンマッチング対象となるのは:myapp/
url() 引数: view
regexでマッチしたURLはこのview引数で指定した関数に処理を渡す.viewを呼び出す際に,HttpRequestオブジェクトを第一引数にし,正規表現で捕捉された項目をそれ以降の引数として渡す.regexで単純に捕捉した場合はその順番での引数として渡されるが,名前付きで捕捉した場合はキーワード引数として渡される.
url() 引数: kwargs
任意のキーワード引数をview関数に渡す.この引数は辞書型で渡す.
url() 引数: name
Django中でURLを一意に識別するために用いられる
さらにviewを書く
indexとは違う(今度は引数ありの)viewをいくつか追加する.
# polls/views.py def detail(request, poll_id): return HttpResponse("You're looking at poll %s." % poll_id) def results(request, poll_id): return HttpResponse("You're looking at the results of poll %s." % poll_id) def vote(request, poll_id): return HttpResponse("You're voting on poll %s." % poll_id)
これらの新規viewを以下のようにしてpolls.urls
モジュールに接続する.
# polls/urls.py from django.conf.urls import patterns, url from polls import views urlpatterns = patterns('', # ex: /polls/ url(r'^$', views.index, name='index'), # ex: /polls/5/ url(r'^(?P<poll_id>\d+)/$', views.detail, name='detail'), # ex: /polls/5/results/ url(r'^(?P<poll_id>\d+)/results/$', views.results, name='results'), # ex: /polls/5/vote/ url(r'^(?P<poll_id>\d+)/vote/$', views.vote, name='vote'), )
include()
メソッドはマッチしたURLの文字列部分を取り除き,残りの文字列部分をインクルードされるURLConfに渡す.
実際に何か行うviewを書く
viewは以下のうちどちらを行う責務を持つ
- リクエスト内容を含んでいるHttpResponseオブジェクトを返す
- Http404等の例外を発生させる
以下の例はpolls/
にアクセスしたときに表示するindexページを最近5件のPoll questionをカンマ区切りで表示する.
# polls/views.py from django.http import HttpResponse from polls.models import Poll def index(request): latest_poll_list = Poll.objects.order_by('-pub_date')[:5] output = ', '.join([p.question for p in latest_opll_list]) return HttpResponse(output)
これはページデザインをハードコードしているので問題である.こういった問題に対処するためにDjangoではテンプレートシステムを提供している.
templateの作り方
- polls/templatesディレクトリを作る
- さらにその下にpollsディレクトリを作る
- そこにindex.htmlファイルを作る
- 以下のコードをindex.htmlに記述する
{% if latest_poll_list %} <ul> {% for poll in latest_poll_list %} <li><a href="/polls/{{ poll.id }}">{{ poll.question }}</a></li> {% endfor %} </ul> {% else %} {% endif %}
出来あがったindex.htmlは以下のパスとなる.
polls/templates/polls/index.html
何故templatesディレクトリの下に再度pollsディレクトリを作るかは,他のアプリのhtmlファイル等のリソースと名前が重複しないようにするため.
そしてpolls/views.pyのindex()を次のように変更する.
# polls/views.py from django.http import HttpResponse from django.template import Context, loader from polls.models import Poll def index(request): latest_poll_list = Poll.objects.order_by('-pub_date')[:5] template = loader.get_template('polls/index.html') context = Context({ 'latest_poll_list': latest_poll_list, }) return HttpResponse(template.render(context))
このコードはtemplates/polls/index.htmlのテンプレートをロードし,コンテキストに通す.contextはテンプレート変数とPythonオブジェクトをマッピングさせるための辞書である.
ショートカット:render()
テンプレートをロードするコードは定石なのでショートカットが存在する.index()を書きなおしたものは以下の通り.
# polls/views.py from django.shortcuts import render from polls.models import Poll def index(request): latest_poll_list = Poll.objects.all().order_by('-pub_date')[:5] context = {'latest_poll_list': latest_poll_list} return render(request, 'polls/index.html', context)
render()メソッドは:
- 第一引数にrequestオブジェクト
- 第二引数にテンプレートの名前
- 第三引数に辞書型データ(テンプレート変数とPythonオブジェクトのマッピング)
を受け取り,HttpResponseオブジェクトを返す.第三引数はオプショナルらしい. 上記に変更すると,loaderとContextはインポートする必要は無くなる.
404エラーを発生させる
from django.http import Http404 # ... def detail(request, poll_id): try: poll = Poll.objects.get(pk=poll_id) except Poll.DoesNotExist: raise Http404 return render(request, 'polls/detail.html', {'poll': poll})
もし要求されたIDのpollが存在しなければhttp404例外を投げる.上記コードでコンテキストに渡したpollオブジェクトはテンプレート中で以下のように書くと参照出来る.
{{ poll }}
ショートカット: get_object_or_404()
get()とHttp404のraiseは頻出イディオムなのでこれにもショートカットが存在する.以下はdetail viewを書きなおしたものである.
from django.shortcuts import render, get_object_or_404 def detail(request, poll_id): poll = get_object_or_404(Poll, pk=poll_id) return render(request, 'polls/detail.html', {'poll': poll})
get_object_or_404()は第一引数にDjangoのModelオブジェクトを受け取り,任意の数のキーワード引数をそれ以降で受け付ける.これらはmodelsのマネージャのget()メソッドに通すものである.もしオブジェクトが存在しなけばHttp404例外を投げる.
404(page not found)viewを書く
Http404例外があるviewで発生したら,Djangoは404エラーを操作するハンドラ―をロードしようとする.DjangoはルートURLConf中のhandler404変数を参照し,そこに登録されたハンドラ―(通常の場合と同様のview)を呼び出す.
500(server error)viewを書く
同様にルートURLConfにはhandler500
も定義できる.これはサーバーエラー(ステータスコードが500)に対応するviewを呼ぶ.
テンプレートシステムを使う
コンテキスト変数としてpollが与えられたとしたときのpolls/detail.htmlは以下のようになる.
<h1>{{ poll.question }}</h1> <ul> {% for choice in poll.choice_set.all %} <li>{{ choice.choice_text }}</li> {% endfor %} </ul>
Djangoのテンプレートシステムでは,変数の属性へのアクセスはdot-lookup構文
(ドットでオブジェクトを区切るおなじみのやつ)で行う.その手順は以下の通り({{ poll.question }}
を例として説明)
- オブジェクト
poll
上の辞書探索を行う. - もし見つからなければオブジェクトの属性探索を行う.(上記例ではこれで見つかる)
- もし見つからなければリストでの探索を行う.
より詳しいテンプレートの情報はここを参照.
テンプレート中のハードコードされたURLの削除
今のpolls/index.html
はURLがハードコードされている.
<li><a href="/polls/{{ poll.id }}/">{{ poll.question }}</a></li>
これだと沢山のテンプレートを使うプロジェクトではURLを変更するのが非常に難しくなる.でもurlsモジュール(urls.py)でurl()
関数に名前を付けていた場合,その名前でURLを参照することが出来る.
<li><a href="{% url 'detail' poll.id %}">{{ poll.question }}</a></li>
こうすると,もしURLを変更したくなったらテンプレートを修正するのではなく,polls/urls.py
を変更すればよい.例えば以下のように
# added the wod 'specifics' url(r'^/specifics/(?P<poll_id>\d+)/$', views.detail, name='detail')
URL名の名前空間
{% url %}
の仕組みは便利だが,一つのプロジェクト中の異なるアプリ同士で同じURLを使っている場合,DjangoはどちらのアプリのURLかが判断出来なくなる.このような問題に対応するためにURLの名前空間を利用する.利用方法はプロジェクトのurls.py
のurl()
関数にnamespace
キーワード引数を指定すればよい.
from django.conf.urls import patterns, include, url from django.contrib import admin admin.autodiscover() urlpatterns = patterns('', url(r'^polls/', include('polls.urls', namespace="polls")), url(r'^admin/', include(admin.site.urls)), )
こうした後にテンプレートのURL指定を以下のようにすればOK.
<li><a href="{% url 'polls:detail' poll.id %}">{{ poll.question }}</a></li>