Django – Tutorial4 – フォーム

フォームの配置

このページはDjangoのDocumentation、Writing your first Django app, part 4に対応している。

detailページの機能

チュートリアルのテンプレートのところで作成したindexページでは、データベースのquestionテーブルに保存されたQuestionデータの一覧が表示される。

各Questionデータのリンクをクリックすると、polls.viewsモジュールのdetail関数にルーティングされ、detailテンプレートが呼ばれてページが表示される。

現状でこのページでは、"You're looking at question 1."のようにクリックしたQuestionデータのidを含むテキストが表示されるだけ。

ここでdetailページに以下のような機能を持たせる。

  • クリックしたQuestionデータのquestion_textを表示する
  • そのQuestionデータに関連付けられた(すなわちその回答選択肢となる)Choiceデータ群を、ラジオボタンとともに表示する
  • Voteボタンを押すと、選択した回答のidが送信される

id送信後の振る舞いについては後に実装することとして、ここでは上の機能をテンプレートで実装していく。

コードの変更

pollsアプリケーションのdetailテンプレートを以下の内容に変更する。

テンプレートがビューから受け取る変数

questionオブジェクトとerror_message(テキスト)が渡されることを想定している(上記コードのL4, 6, 8)。

form要素のactionとmethod

form要素のオプション設定は以下の通り。

  • actionの呼び出し先はpollsアプリケーションのname='vote'ルート→vote関数で、questionデータのidをURLパラメーターで渡す
  • メソッドはPOST

csrf_tokenタグ

form要素内の最初にCSRF/XSRF (cross-site request forgery)対策として{% csrf_token %}を置いている。form要素にこのタグを置かないと、Djangoによりアクセス禁止のエラーとなる。

qustion_textの表示

ビューから受け取ったquestionオブジェクトのquestion_textプロパティーの内容をh1要素として表示させる。

エラー表示

ビューの処理でエラーになった場合error_message変数が定義されることを前提としている。DTLのifタグにより、error_messageがセットされているときはこれが表示され、選択肢とラジオボタンが表示される。

選択肢の表示

questionデータに対する選択肢はChoiceデータが関連付けられていて、そのコレクションはDTLのドット検索によりquestion.choice_set.allで得られる。

フォームでは、このコレクションの要素(各Choiceデータ)をDTLのforタグで1つずつ取出している。そして、各choiceについてラジオボタンとラベルテキストを生成している。

ラジオボタンの属性は以下のとおり。

name=”choice”
すべての選択肢に同じname属性を適用してグループ化している。
id=”choice{{ forloop.counter }}”
label要素で参照するidで、先頭から連番でchoice1, choice2, …のような値となる。forloop.counterDTLのforタグの変数で、ループカウンターの値が得られる。
value=”{{ choice.id }}”
選択されたラジオボタンに対応する値で、Choiceデータのidが充てられる。このidによって、データベース上の指定したデータが得られる。

このテンプレートがレンダリングされたときのHTMLは以下のとおり。valueの値が1, 2, 4となっているが、データの削除・追加によってAUTO_INCREMENTの値は必ずしも連続にならない。一方、forloop.counterを利用したidの数字部分は連番となっている。

ここでindexページのQuestionデータのうち"What's up?"をクリックすると、レンダリングされたdetail.htmlにより以下のページが表示される。

この段階でVoteボタンを押すと、polls.viewsモジュールのvote関数にルーティングされるが、この段階では”You’re voting on question 1.”とだけ表示される。

vote関数の実装

vote関数での処理

ここではフォームのアクション先であるvote関数の処理を実装する。処理内容は、Questionの持つ選択肢のうちditailページで選択・送信されたChoiceデータのvoteカウンターを1つ増やしてデータベースを変更するというもの。

コード

vote関数の内容を以下のように変更する。

モジュールのインポート

パッケージ先頭でHttpResponseRedirectモジュールをインポートしている。このモジュールは、URLconfで定義したルート名とパラメーターを与えてレスポンスを返す。

またChoiceモデルを扱うために、Questionに加えてChoiceモジュールもインポートしている。

Questionの取得

フォームのaction属性で、Questionデータのidをクエリーパラメーターで渡すようになっていて、vote関数の引数でこれを受け取る。

そのidQuestionデータを取得し、存在しなければ404エラーとなる。

投票登録:try~except~elseブロック

このブロックで、Questionデータに対する選択肢をデータベースから取得し、エラーならエラー処理をし、成功したならその選択肢の得票数を1つ増やす。詳細は以下の通り。

try節

try節では、前段で得られたQuestionデータの選択肢Coiceデータのうちフォームで選択されたものを取得する。

  • getメソッドでpkPOST['choice']に等しいデータを取り出している
  • POST['choice']はフォームにおいてname='choice'INPUT要素から得られるvalue属性の内容
  • 今回のフォームではラジオボタンのグループが'choice'であり、POST['choice']から選択されたボタンのvalue属性の値が得られる
  • フォームではvalue={{ choice.id }}としているので、この値は選択された選択肢に対応するChoiceデータのidの値

except節

except節では、引数で指定したKeyErrorChoice.DoesNotExistの2つの例外をキャッチしている。いずれかの例外が発生した場合に、現在処理中のQuestionデータを与えて元のdetailページを表示させるが、このときエラーメッセージをセットしているのでこれも表示される。

KeyErrorPOST['choice']でデータを取り出すときに'choice'というキーが存在しない場合に発生する。このような状況は次の場合に発生する。

  • 選択肢を持たないQuestionデータに対してVoteボタンを押した場合(今回の例の場合question_text"Hou're you doing?"のデータ)
  • HTMLのラジオボタンのname属性が改ざんされた場合

Choice.DoesNotExistの方は、getメソッドで条件に合うデータがデータベース上にないときに発生する。ただ今回のコードでは、DB上のデータが取得できない場合はget_object_or_404により404エラーへと飛ぶようになっているので、ここでDoesNotExist例外は発火しないのではないか。

else節

else節はtry節の処理が正常終了した場合に実行される。今回の場合、フォームで選択された選択肢のChoiceデータが無事取得できたことになるので、データベース上の得票数カウンターを1つ増やして投票結果表示のresultページにリダイレクトする。

得票数のカウントアップは簡単で、取得済みのデータについて以下を実行。

ただしこれはデータベースからメモリー上に取得されたデータを変更しただけなので、変更後のデータをデータベースに登録する必要がある。

最後に戻り値としてHttpResponseRedirectを返している。これはDjangoに特化したものではなく、HTTPでの処理の標準的な手筋。

動作確認

 

Django – render/redirect/HttpResponseなど

概要

ビュー関数/ビュークラスのメソッドはHttpResponseクラスやその派生クラスのインスタンスを戻り値として、テンプレートをレンダリングしたり指定したURLに遷移する。

この戻り値の与え方としては、HttpResponseやその派生クラスのインスタンスを直接与える方法と、django.shortcutsで定義された関数(render、 redirect)を使う方法がある。

HttpResponseはレスポンスボディ―を指定して表示。ショートカットのrenderは引数にテンプレートファイルを指定し、それを表示する。

URLを指定したリダイレクトには、HttpResponseRedirectやショートカットのrenderを使う。

レンダリング

HttpResponse

HttpResponsedjango.httpパッケージのモジュールで、ヘッダーや内容などを持つHTTPレスポンスのクラス。インスタンス生成時に引数にテキストを指定したテキストがレスポンスボディ―になり、直接表示される。

テキストのほか、イテレーターやファイルも指定できる。

render

renderdjango.shortcutsパッケージのモジュールで、指定したテンプレートファイルの内容をHttpRequestインスタンスとして返す。

テンプレートファイルに加えて、テンプレートに引き渡す変数コンテキストも指定できる。

リダイレクト

HttpResponseRedirect

HttpResponseRedirectdjango.httpパッケージのモジュールで、指定したURLに遷移するためのHTTPレスポンスのインスタンスを生成する。

HttpResponseRedirectHttpResponseRedirectBaseを継承し、HttpResponseRedirectBaseHttpResponseを継承している。

ビュー関数/メソッドの中で、URLを引数に与えてHttpResponseRedirectのインスタンスを生成し、戻り値にそのインスタンスを渡すことで、指定したURLにリダイレクトされる。

引数のURLは以下のように与えることができる。

  • ホスト名を含む絶対URL
  • アプリケーション名から始まるサブディレクトリーによる相対URL
  • urls.pypath関数で設定したルート名をreverse関数の引数に与えた戻り値(ルート名を直接与えることはできない)

redirect

redirectはdjango.shortcutsパッケージのモジュールで、指定したURLにリダイレクトするためのHTTPレスポンのためのスインスタンスを返す。

redirectは関数として定義され、デフォルトではHttpResponseRedirectのインスタンスを返すが、引数にpermanent=Trueを設定すると、HttpResponsePermanentRedirectのインスタンスを返す(このクラスは301ステータスを返す)。

redirectHttpResponseRedirectを返す際、resolve_url関数を介していて、reverse関数を使わず直接ルート名を渡すことができる。

 

Django – HttpResponse

概要

HttpResponsedjango.httpパッケージにあり、HTTPリクエストに対してサーバーからクライアントに返されるHTTPレスポンスを表すクラス。

Django Documentation – Request and response objects

プロパティー

HttpResponseは以下のプロパティーを持つ(一部記載)。

status_code
ステータスコードの値
headers
ヘッダーの各項目の内容
content
レスポンスボディの内容。バイトストリング。
charset
文字コード名
reason_phrase
ステータスコードのフレーズ。RFCに準拠。

インスタンス生成例

まずモジュールをインポートし、文字列を渡してHttpResponseインスタンスを生成。インスタンス表現や型を確認。

ステータスコードとステータスフレーズを確認。

ヘッダーとそのうちの文字コードを確認。

ボディー部の確認。バイトストリングになっている。

 

Django – フォームデータ

概要

テンプレートのフォームで入力・送信されたデータをビューで受け取って処理する方法の基本。

ビュー関数/メソッドの引数(たとえばrequest)のPOST配列でフォームのINPUT要素のname属性値を指定して値を取得する。

request.POST['name属性値']

実装例

2つのページへのルーティング

urls.pyinputページとresultページへのルーティングを設定。inputページにフォームを置き、そこから送信されたデータをresultページで表示することとする。

DTLのurlタグを使うため、各ルートにname引数でルート名を付けている。

inputページのためのビュー

ルート名formtest:inputでルーティングされるview関数。単にinput.htmlテンプレートをレンダリングしている。

input.htmlテンプレート

input.htmlテンプレート。フォームには、textタイプのINPUT要素と、3つの選択肢を持つradioタイプのINPUT要素群が置かれている。

  • textタイプのINPUT要素のname属性はinput_test
  • radioタイプの3つのINPUT要素のname属性はradio_choiceで値はpinebambooplum

送信ボタンが押されると、methodで指定したURLに飛ぶが、ここではurls.pyで定義したformtest:resultに飛ぶようにDTLのurlタグを使っている。

resultビュー

フォームからの送信に対してviews.pyresult関数が呼ばれる。result.htmlテンプレートにcontext辞書を介して2つの変数input_textradio_choiceを渡している。

  • input_text変数には、name='input_text'で指定されたテキストの内容がrequest.POST['input_text']で取得されて代入される
  • radio_choice変数には、name='radio_choice'で指定されたラジオボタンのうち選択されたもののvalue値がrequest.POST[‘radio_choice’]で取得され、これに対応する文字列が代入される

result.htmlテンプレート

resultテンプレートでは、ビューのcontextで設定された変数の内容を表示する。

 

Django – DTL – URL

概要

urls.pyでURLパターンからビューへのルーティングを指定しただけの場合、テンプレートでそのルーティングを使うにはURLを直書きしなければならない。

これに対して、urls.pyのURLパターンを任意に変更しても影響が及ばないようにするには、DTLのurlタグを使う。

URLハードコーティングの場合

たとえばmyappアプリケーションのmainページからsubページへのリンクを貼りたいとする。

アプリケーションのurls.pyは以下のようになっているとする。

ルーティングされたビュー関数が以下のように書かれているとする。

レンダリングされるテンプレートでURLをハードコーティングした場合は以下のようになる。

main.html

sub.html

このときのURL指定と各ページの対応は以下のとおり。

  • [DOMAIN]/myapp/ → views.main → main.html
  • [DOMAIN]/myapp/sub → views.sub → sub.html

URLハードコーティングの問題

urls.py中のpath関数のURLのパターンを任意に変化させると、各テンプレート中のURLを全て見直さなければならない。これの影響を及ぼさせないようにしたい。

urls.pyでのname指定

urls.py中、抽象化したいpath関数にname引数を追加する。この場合はsubページへのpath関数を以下のように変更。

そして、subページへのURLの代わりにDTLのurlタグを使い、その引数にurls.pynameで指定した値を与える。

アプリケーションを指定する場合

異なるアプリケーションでnameに指定したい値が重複する場合、[app_name]:[path_name]で指定できるようにする。

path()関数でnameを指定する際、urls.pyの冒頭でapp_name変数にアプリケーション名の文字列を指定しておく。

そして、DTLのurlタグの引数にapp_nameで指定した値を加える。

まとめ

グローバルなパス名の場合

  1. urls.pypath関数にname引数でパス名を指定
  2. URLの代わりに{% url 'パス名' %}を指定

アプリケーションローカルなパス名の場合

  1. urls.pypath関数にname引数でパス名を指定
  2. urls.pyの冒頭でapp_name='アプリケーション名'を記述
  3. URLの代わりに{% url 'アプリケーション名:パス名' %}を指定

 

Django – クラスベースビューの基本

概要

Djangoのビューを実装するのに、関数で書く方法と、Viewクラスやその派生クラスを継承して必要なメソッドをオーバーライドする方法がある。

Djangoのオーソドックスな書き方ではクラスベースビューのListViewなどを利用し、コード量を減らすことが推奨されている。

ここでは関数による方法とViewクラスの継承クラスによる方法の2つを比較する。

基本形

テンプレートの準備

ビューからパラメーターを受け取り、単にそれを表示するテンプレートを準備する。ここではviewtestというアプリケーションを想定し、準備するテンプレートファイルはindex.htmlとする。

関数によるビューの実装

views.pyの記述

アプリケーションディレクトリー下のviews.pyファイルに、indexビューを関数で定義する。ビューではテンプレートに渡すパラメーターを文字列'Hello!'としている。

urls.pyの記述

アプリケーションのサブディレクトリーfunctionから、同じアプリケーションのindexビューへのルーティングを定義する。この例の場合localhost:8000/viewtest/functionのようにURLを指定し、ブラウザーでアクセスすることになる。

ここでサーバー起動状態でブラウザーに先のURLを指定すると、ブラウザーに以下のように表示される。

関数ビューの場合、GETを含むリクエストに対してurls.pyに従ってビューが呼び出され、その内容が実行される。

クラスベースビューによる実装

関数によるビューと同じ処理をクラスベースのビューで実装してみる。ここでは比較のために、関数ビューのコードを残してクラスベースビューのコードを追加していく。

views.pyの記述

クラスベースのビューは、Viewクラスやその派生クラス(ListViewなど)を継承し、getpostなどのリクエストに対応したメソッドを記述する。ここではGETリクエストにのみ反応させるため、getメソッドを実装している。

Viewクラスではgetメソッドは定義されていないが、Viewの派生クラスでGETメソッドでのリクエストがあるとViewクラスのhandlerが実行され、View.get()が実行される。そして派生クラスでget()を定義しているとこれがオーバーライドされたメソッドとして実行される。

参考:Django ViewではHttpメソッド名の関数を定義することができる

urls.pyの記述

クラスベースビューによるメソッドが呼び出されるようにアプリケーションのurls.pypath関数を追加する。

関数ベースの場合はpath関数の第2引数に関数表現のビューモジュールを指定したが、クラスベースの場合はIndexViewの親クラスViewas_view()メソッドを指定している。as_view()メソッドは内部関数view()を通してdispatchメソッドを返すので、結局パスが解決されるとdispatchメソッドが呼び出されることになる。

ここでサーバー起動状態でlocalhost:8000/viewtest/classなどとURLを指定すると、ブラウザーに以下のように表示される。

URLパラメーターを含む場合

関数によるビューの実装

urls.pyの記述

URLパラメーターを含む場合、URLパターンに型名やパラメーター名を入れる。

views.pyの記述

ルーティングで記述したパラメーター名をビュー関数の引数に指定し、これをテンプレートに渡す。

ここでサーバー起動状態でlocalhost:8000/viewtest/function/1などとURLを指定すると、ブラウザーに以下のように表示される。

クラスベースによる実装

urls.pyの記述

URLパターンにURLパラメーターを記述する方法は関数の場合と同じ。クラスベースビューの場合はas_viewメソッドの実行結果を渡す。

views.pyの記述

クラスベースビューの場合、getなどのメソッドの引数にパラメーターを指定する。引数の指定パターンは関数の場合と同じ。

ここでサーバー起動状態でlocalhost:8000/viewtest/class/2などとURLを指定すると、ブラウザーに以下のように表示される。

GET/POST処理

関数によるビューの実装

テンプレートの準備

GETで表示されるページにフォームを追加し、送信先をfunctionサブディレクトリー、メソッドをPOSTとする。

urls.pyの記述

今回のindexテンプレートはGET、POST両方のリクエストから呼び出されるため、functionサブディレクトリーのルーティング1つ。

views.pyの記述

リクエストメソッドの違いはrequest.methodの文字列で判定して処理を分ける。GETリクエストの場合は1つ目のifブロックが実行され、テンプレートに文字列'GET!'が渡される。またPOSTリクエストの場合は2つ目のifブロックが実行され、テンプレートには文字列'POST!'が渡される。

ここでサーバー起動状態でlocalhost:8000/viewtest/functionなどとURLを指定すると、views.index関数でGETに対応したifブロックが実行され、ブラウザーに以下のようにGET!と表示される。

このページで「送信」ボタンを押すと、今度はviews.index関数でPOSTに対応したifブロックが実行され、ブラウザーにPOST!と表示される。

クラスベースによる実装

テンプレートファイルの準備

POSTに対する処理を関数の場合と分けるため、送信先のURLのサブディレクトリーをclassとする。

urls.pyの記述

GET/POSTに関わらず、classサブディレクトリーでのリクエストを受け取った場合には、IndexViewクラス(のインスタンス)に処理を渡すように定義。

views.pyの記述

ルーティングで

ここでサーバー起動状態でlocalhost:8000/viewtest/classなどとURLを指定すると、views.IndexViewクラスでオーバーライドしたgetメソッドが実行され、ブラウザーに以下のように表示される。

このページで「送信」ボタンを押すと、今度はviews.IndexViewクラスでオーバーライドしたpostメソッドが実行され、ブラウザーに以下のように表示される。

クラスベースビューの基本のまとめ

要点

基本のクラスベースビューの要点は以下のとおり。

  • Viewクラスを継承したクラスを定義
  • GETやPOSTなどに対応したメソッドを定義
  • ルーティングでは、継承先のクラスの.as_view()メソッドを渡す

views.pyの記述

  • django.views.genericパッケージのViewモジュールをインポート
  • Viewクラスを継承したクラスを定義
  • getメソッド、postメソッドなどを定義
    • 第1引数はself
    • 第2引数はrequest
    • パラメーターがあればそれ以降
  • getメソッドの記述内容は、関数ベースの場合の内容と同じ
    • たとえばrender(request, 'target_page')

urls.pyの記述

  • urlpatterns属性のpath関数の第2引数でビュークラスを指定し、このときas_viewメソッドを指定する
    • たとえばpath('sub_dir', views_dir.MyViewClass.as_view())

 

Django – URLパラメーター

URLパラメーターの送信

HTTPリクエストで、以下のようにURLにパラメーターを付して送り、これをDjangoで受け取って処理する手順。

ここではparamtestアプリケーションでパラメーターとして100が送信されるとする。

ルーティングの設定

まずURLに含まれるパラメーターを認識するため、ルーティングを設定する。アプリケーションのurls.pyで、パラメーターを受け取るために以下のように記述する。

<int:....>によってパラメーターが整数形式であることを明示していて、整数以外の値が渡された場合は404エラーとなる。

ビューの記述

ルーティング先のviews.indexでは、このパラメーターを引数で受け取る。以下の例では、受け取ったパラメーターをそのままテンプレートに渡している。

テンプレートが以下のようになっていれば、URLで与えたパラメーターの値がブラウザーにそのまま表示される。

 

 

Django – Tutorial – テンプレート

概要

DjangoドキュメントのチュートリアルPart3中”Write views that actually do something“の冒頭でビュー関数でのモデル操作を実装した後、テンプレートファイルを使った表示を実装している。

テンプレートの基礎についてはこちらを参照。

テンプレートディレクトリー/ファイルの作成

テンプレートファイルの内容

pollsディレクトリー下にtemplatesディレクトリーを、その下にpollsディレクトリーを作成する。そのディレクトリー下に以下の内容でindex.htmlファイルを作成する。

これから、polls/viewsモジュールからこのテンプレートを参照するよう編集していく。

パスの書き方の注意点

テンプレートファイルのアンカータグは以下のように書かれている。

当初/polls/{{ ... }}の部分の先頭の'/'を抜かして書いてしまって、polls/{{ ... }}としたところ、URLが以下のようになってしまった。

http://localhost:8000/polls/polls/2/

現在のアプリケーションからの相対パスになってしまったためで、

http://localhost:8000/polls/ + /polls/2/

と解釈されてしまう。先頭のスラッシュを入れるとホスト名をルートとするパスとなる。ただしこの記事の最後の方にあるDTLのurlタグを使うと、このような心配をしなくて済むようになる。

テンプレートの探索

ディレクトリー・ファイル構成は以下の通り。

config/settings.pyで、以下のように設定している。

  • INSTALLED_APPS'polls.apps.PollsConfig'を登録
    pollsが探索に含まれる
  • TEMPLATES'APP_DIRS'Trueに設定
    polls/templatesが探索に含まれる

ので、polls/templatesとそのサブディレクトリー下がテンプレートファイルの探索場所に含まれる。

ビューにおけるレンダリング

loaderによる標準形

ビューの編集

テンプレートファイルを導入した場合の標準形は以下の構成になる。

  • django.templateパッケージのloaderオブジェクトをインポート
  • loader.get_template('テンプレートへのパス')でテンプレートを読み込み
  • テンプレートのrenderメソッドでレンダリングした結果を、HttpResponseに渡す
    render(context, request)

動作確認

http://localhost:8000/pollsにアクセスすると、2つのリンクが表示される。

 

このHTMLのソースコードは以下のようになっている。

それぞれのリンクをクリックしたときの遷移先URLとブラウザー表示は以下の通り。

  • href="/polls/2/"
    • http://localhost:8000/polls/2/
    • You’re looking at question 2.
  • href="/polls/1/"
    • http://localhost:8000/polls/1/
    • You’re looking at question 1.

renderショートカット

loaderを用いた標準形では、

  1. テンプレートのローダーによってテンプレートファイルを読み込み、
  2. これをレンダリングした結果をHttpResponseに渡す、

という2段階の処理となっている。Djangoにはこれを1行で済ませるrenderショートカットが準備されている。renderショートカットを使って、indexビューをより簡潔にしてみる。

1行目のHttpResponseのインポートはまだ手を入れていない他のビュー関数のためで、renderによる処理には必要ない。loaderのインポートも不要で、必要なのは3行目のrenderのインポートのみ。

14行目がrenderショートカットを導入した効果で、この1行でリクエスト、テンプレート、contextの指定を済ませてレスポンスを返している。

render(request, 'テンプレートパス', context)

404エラーの実装

存在しないidへの反応

indexテンプレートではQuestionの2つの要素が表示され、それぞれid=1, 2をリクエストパラメーターとして渡している(href="/polls/1/"など)。ところがQuestionに存在しないidを指定したURLでアクセスすると以下のようなエラーがブラウザーに表示される。

このエラーの代わりに、404エラーで反応させたい。

例外処理による標準形

ビューの編集

現在のdetailビューは以下のような内容。

存在しないインスタンスを指定すると例外が投げられるので、それをキャッチして404エラーをレイズするようにする。

テンプレートの準備

またrenderで指定しているdetailテンプレートを、polls/template/pollsディレクトリーに以下の内容で作成しておく。

実行結果の確認

これで存在しないidの値を指定したときのエラーは以下のように変化する。メッセージがHttp404クラスのコンストラクターに与えたテキストになっている。

get_object_or_404ショートカット

Djangoには、上のような複数行の例外処理を一行で書くget_object_or_404ショートカットが準備されている。

以下のようにget_object_or_404ショートカットをインポートすると、1行でQuestionデータを取得し、存在しなければ404エラーをレイズしてくれる。

存在しないidを指定したときのエラー表示は以下のようになる。エラーメッセージの内容は固定されていて変更できないようだ。

なおsettings.pyDEBUGFalseにすると、ブラウザー表示は以下の2行のより簡素な内容になる

テンプレートシステム

DTLによる記述

先ほどのindexテンプレートの内容は1行で変数を表示するだけの内容だったが、これをテンプレートらしくDTL~テンプレート言語を使って以下のように書き換える。

そうすると大きな見出しでQuestionデータのテキストが表示され、そのQuestionデータに関連付けられたChoiceデータのテキストが箇条書きで表示される。

 

今回のDTLでは、オブジェクトの属性を得るのにドット検索が行われている。通常のPythonの場合リストや辞書型のオブジェクトの要素は[]で参照されるが、DTLではこの記述方法が使えない。代わりにドットを使って以下の順序で探していく

  1. 辞書型のキー
  2. オブジェクトの属性
  3. リストのインデックス

今回の例では、辞書型のキーで取得できず、オブジェクトの属性として取得に成功する。

URLのハードコーディング回避

ハードコーディングの問題

現時点でindexテンプレートのアンカー要素のURLは"/polls/{{ question_id }}/"と書かれている。

 

この場合にリンクが張られるdetailのURLはurlsで定義されていて、たとえば以下の通り。

http://localhost/polls/1

ここでプロジェクト進行中に以下のように変更することとなったとする。

http://localhost/polls/specifics/1

この場合、indexテンプレートのアンカー要素の内容を全て以下のように変更する必要がある。

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

さらにindexテンプレート以外でもこのルーティングを使っている場合、その分だけ変更が生じてしまう。

ハードコーディングの回避

urls.pyの確認

まずpolls/urls.pydetailビューへのルーティングにname='detail'で名前が付けられていることに留意。

ビューの変更

index/view.py中、アンカー要素のURL部分を以下のように変更する。

  • href="..."のURL部分を{% url %}に入れ替え
  • このタグの引数に、urlsnameで指定した名前とパラメーターを書く

一般化した書き方は以下の通り。このとき、先頭や末尾の'/'は不要。

{% url 'urlsのnameで定義した名前' クエリーパラメーター %}

HTMLの確認

生成されるHTMLは以下の通り。

URLの変更

URLの変更が必要な場合、1箇所だけ、urlsの内容を変更すればよい。

以下のように生成されるHTMLもすべて変更される。

そして以下のようにアクセスできるようになる。

URLの名前空間管理

名前空間管理の必要性

DTLのurlタグで、ルーティングに付けた名前を使って抽象化することができた。ところが、先の例の場合pollsアプリケーションのdetailと同じ名前を他のアプリケーションでも使う場合、見分けがつかない。

そこで、ルート名がどのアプリケーションのものか分かるようにする必要がある。

urls.pyの編集

urls.pyに以下の1行を追加する。

indexテンプレートの編集

次にindexテンプレートの名前指定部分を、以下のように'アプリケーション名:ルート名'に変更。

この場合の実行結果は変わらないが、他のアプリケーションでもdetailという名前のルーティングがある場合でも、pollsアプリケーションの同名のdetailが識別される。

urlsapp_nameが定義されている状態でurlタグでpolls:を書かないと以下のエラーになる。

 

Django – Tutorial – ビューでの処理

概要

DjangoドキュメントのチュートリアルPart3中”Write views that actually do something“では、それまで単に文字列だけを返してブラウザーに表示させていたビュー関数に、ビューでの処理を記述している。そこではデータベ ースのデータを使った処理を例にしている。

チュートリアルではこれに続いてテンプレートの扱い方を示しているが、ここではモデル操作の実装だけを取り出して整理する。

index関数の編集

チュートリアルで示されているindex関数の編集内容は以下の通り。polls.models.Questionをインポートしていることに注意。

http://localhost:8000/polls/にアクセスすると、questionsテーブルに登録されているデータが日時の新しい順に、カンマで区切られて表示される。

チュートリアルではデータが1つしか登録されていなくて分かりづらいので、もう一つデータを登録してみる。

コードの内容

モデルインスタンスのソートと抽出

Questionモデルの全てのインスタンスをpub_dateフィールドの降順に並べ替えて先頭から5個取出し、そのQuerySetインスタンスをlatest_question_listに参照させている。

  • モデル.objects.order_by()でモデルのデータをソートした結果をQuerySetとして返す
  • '-pub_date'で降順にソート/昇順の場合は'pub_date'
  • [:5]はコレクションのスライスで、先頭から5個のデータ(インデックスが5未満のデータ)を取り出す

シェルで実行してみると以下の通り。

引数を'pub_date'に変更すると並べ替え順序が昇順になり、古いデータから並べられたセットが得られる。

またスライスを[:1]にすると、インデックスが0の要素だけが得られる。

出力文字列の構成

latest_question_listの各要素を', 'をデリミターとして結合している。各要素はQuestionモデルのインスタンスだが、__str__メソッドが実装されているので、参照するとquestion_textの内容が得られる。

 

Django – Tutorial – ビューへのルーティング

概要

DjangoドキュメントのチュートリアルPart3では、ビューへのルーティングやテンプレートの使い方などが詳しく書かれているが、このうちURLconf~ルーティングの部分を整理する。

urls.pyの配置や書き方についてはこちら

view関数の定義

まず、polls/views.pyファイルに3つのview関数(detail, results, vote)を追加する。

いずれのビューでも動作の本質は同じ。

  • リクエストからパラメーターをquestion_idとして受け取る
  • 各ビューで想定している処理の内容に即したテキストをHttpResponseで返す
  • その際、question_idの値もテキストに含めて返す

なおテキストにquestion_idの内容を文字列として含めるために、Pythonの構文を使っている。

"... %s ..." % 変数

urls.pyの設定

追加したview関数をこれに対応したURLリクエストから呼び出すため、ルーティングを追加する。

いずれのルーティングもpollsアプリケーションのビューに関するものなので、最初に作成したpolls/urls.pyに追記する。

確認

サーバー起動状態で各URLを入力したとき、呼ばれるview関数とブラウザー上の文字列は以下のとおり。

  • http://localhost:8000/polls/34/
    views.detail
    →You’re looking at question 34.
  • http://localhost:8000/polls/34/results/
    views.results
    →You’re looking at the results of question 34.
  • http://localhost:8000/polls/34/vote
    views.vote
    →You’re voting on question 34.

URLconf~ルーティングの仕組み

たとえばhttp://ドメイン名/polls/34/というURLでリクエストがあった時、Djangoは以下のように解釈を始める。

  1. settings.pyROOT_URLCONFの設定に従って、config.urlsモジュールを読み込む(モジュールファイルはconfig/urls.py
  2. urlsモジュール中のurl_patterns配列から、パターンを1つずつ順に走査していく
  3. URLパターンが'polls/'パターンまでマッチした時、それ以前のパターンを取り除いて、polls.urlsモジュールを読み込む
  4. polls.urlsモジュールでURLパターンの'<int:question_id>/'パターンまでマッチしてその後がないとき、これに対応するviews.detailモジュールを呼び出す
  5. detailモジュールでは<int:queston_id>の内容が引数のquestion_idに渡され、ビュー内での処理に使われる

なおsettings.pyROOT_URLCONFはデフォルトで以下のように書かれていて、まずconfig.urlsが読み込まれるようになっている。

またconfig.urlsは、チュートリアルの最初の方で以下のように編集済み。

またパラメーターについては<int:question_id>と定義されているので、localhost:8000/polls/abc/のようにパラメーターが整数として解釈されない場合にはパスが見つからず、Page not foundエラーになる。