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での処理の標準的な手筋。

動作確認

 

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です