勉強会用資料です.
django1.8のチュートリアルを辿りつつ説明していきます.
https://docs.djangoproject.com/en/1.8/intro/tutorial04/
日本語の公式ドキュメントはバージョン1.4が最新なので若干違いが有りますが大まかな流れは一緒なので一読するのもいいと思います.
http://django-docs-ja.readthedocs.org/en/latest/intro/tutorial04.html
→ チュートリアルまとめ
投票フォームの作成
ソース:99b01e3
→49d2e35
まずは本家チュートリアル通りにdetail.html
を投票用に書き換え,投票できるようにしてみましょう.
<h1>{{ question.question_text }}</h1>
{% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}
<form action="{% url 'poll_vote' question.id %}" method="post">
{% csrf_token %}
{% for choice in question.choice_set.all %}
<input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}" />
<label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br />
{% endfor %}
<input type="submit" value="Vote" />
</form>
URLのネームスペース化はしてないので
polls:vote
をpoll_vote
に書き換えています.
polls_voteという名前のurlはまだ定義してないのでこのままブラウザを開いてもエラーがでます.
とりあえずurls.pyとviews.pyを書き換えて皮だけ用意しましょう.
...
def vote(request, pk):
pass
urlpatterns = [
url(r'^$', views.index, name='index'),
url(r'(?P<pk>\d+)/$', views.detail, name='poll_detail'),
url(r'(?P<pk>\d+)/vote$', views.vote, name='poll_vote'),
]
ブラウザで詳細画面を確認.
いい感じですね.
ちなみにvote関数を実装していないのでボタンを押すとエラーになります.
html説明
少し戻ってhtmlにコードを解説していきます.
{% if %}
タグはまだ出ていませんでしたが,そのままの意味でerror_messageが渡されており,値があれば {% endif %}
までのブロックを出力します.
{% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}
{% csrf_token %}
タグは名前の通りcsrfトークンをセットするためのタグです.
<form method="post">
タグの中に必ずセットする必要があります.
このタグをセットし忘れるとcsrf exceptionが発生します.
wikipedia より
クロスサイトリクエストフォージェリ(Cross site request forgeries、略記:CSRF、またはXSRF)は、WWW における攻撃手法のひとつである。
具体的な被害としては、掲示板に意図しない書き込みをさせられたり、オンラインショップで買い物をさせられたりするなどが挙げられる。また、ルーターや無線LAN等の情報機器のWebインタフェースが攻撃対象となれば、それらの機器の設定を勝手に変更される懸念もある。
要するに.正規の入力ページからデータが送信されたことを保証するためのものです.
他サイトからPOSTで勝手にデータを投げられないようにするために,tokenを入力ページに埋め込むようになっています.
投稿用viewの編集
ソース:49d2e35
→274bd82
vote関数を編集していきましょう.
と言ってもほぼ本家チュートリアルのコピペです.
リダイレクト先が本家では結果ページになっていますが,今回は一覧画面に飛ばすようにしています.
from django.shortcuts import redirect
from .models import Choice
...
def vote(request, pk):
question = get_object_or_404(Question, pk=pk)
try:
selected_choice = question.choice_set.get(pk=request.POST['choice'])
except (KeyError, Choice.DoesNotExist):
return render(request, 'polls/detail.html', {
'question': question,
'error_message': "You didn't select a choice.",
})
else:
selected_choice.votes += 1
selected_choice.save()
return redirect('index')
少しコードの解説を.
selected_choice = question.choice_set.get(pk=request.POST['choice'])
choice_set
という記述がありますが,これはquestionオブジェクトに対してリレーションを張っているChoiceモデルを操作するためのmanagerです.
question.choice_set
とChoice.objects.filter(question=question)
が同じ意味です.
よく意味がわからない方は軽く流しといてください.
postされた値はrequest.POST
に辞書のような形式で入っています.
inputタグのname属性値がchoice
なのでrequest.POST['choice']で選択肢が取れます.
次の行の
except (KeyError, Choice.DoesNotExist):
ここでchoiceの入力がない場合(KeyError),選択肢外の値を投げられた場合(DoesNotExist)の処理を書いています.
今回の例ではerror_messageを入れ,再度入力ページを表示しています.
else:
句は無事投稿用のchoiceが取れた場合入ってきます.
選択されたchoiceのvotes(投稿数)をインクリメントして保存し,一覧ページへジャンプしています.
本家チュートリアルでは
return HttpResponseRedirect(reverse('polls:results', args=(p.id,)))
のように書いてますが,ここではredirect
関数を使用しています.やってることは全く同じです.
結果ページ追加
ソース:274bd82
→4ecb885
一覧画面じゃなくてちゃんとした結果ページを作りましょう.
htmlは例によって本家チュートリアルからのコピペです.
<html>
<body>
<h1>{{ question.question_text }}</h1>
<ul>
{% for choice in question.choice_set.all %}
<li>{{ choice.choice_text }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}</li>
{% endfor %}
</ul>
<a href="{% url 'poll_detail' question.id %}">Vote again?</a>
</body>
</html>
続いてviews.pyにresults関数を追加します.
内容はdetailとほぼ一緒で,htmlのパスだけ変えています.
def results(request, pk):
obj = get_object_or_404(Question, pk=pk)
return render(request, 'polls/results.html', {
'question': obj,
})
最後にurlを編集してviewを繋いで完成です.
urlpatterns = [
url(r'^$', views.index, name='index'),
url(r'(?P<pk>\d+)/$', views.detail, name='poll_detail'),
url(r'(?P<pk>\d+)/vote$', views.vote, name='poll_vote'),
url(r'(?P<pk>\d+)/results$', views.results, name='poll_results'),
]
結果ページができました.
リダイレクト先変更
投稿後に一覧ページに飛ぶようになっていたので結果ページに飛ぶように直しましょう.
一覧ページと違ってpoll_results
は引数が必要なので注意しましょう.
def vote(request, pk):
...
return redirect('poll_results', pk)
これで投稿後は結果ページへジャンプするようになりました.
クラスベース汎用View
ドキュメント→https://docs.djangoproject.com/en/1.8/intro/tutorial04/#use-generic-views-less-code-is-better
クラスベースViewのドキュメント→https://docs.djangoproject.com/en/1.8/topics/class-based-views/
モデルの一覧表示,詳細表示,テンプレート指定など,よく使われるviewについて,
djangoではクラスベース汎用Viewを提供しています.
今書いているviews.pyの中にあるindex
, detail
, results
はすでに行数が少ないので恩恵が感じにくいかもしれませんが,voteなどのようにFormが絡み,少し複雑になってくると非常に強力な道具になります.
Formクラスについてはまだチュートリアルで出てないので今回はソースの変更はせず,
本家チュートリアルを見つつ紹介に留めます.
クラスベースView化は次(かその次)のチュートリアルでやるので興味のない方は読み飛ばしてください.
TemplateView
本家チュートリアルにはありませんが,使用するテンプレートを設定するだけのシンプルなViewです.
どのテンプレートを使用するか設定するだけで使用できます.
templateに渡す辞書を設定する場合はget_context
メソッドをオーバーライドします.
urlにはview関数を渡す必要が有るため,全てのクラスベースViewはas_view()
メソッドを使用して関数化する必要があります.
from django.views.generic import TemplateView
class Index(TemplateView):
template_name = 'app/index.html'
index = Index.as_view()
こんな感じです.
template_nameなど,クラスのプロパティはas_viewの時に渡すこともできます.
つまりviews.pyを変更せずに,urls.pyの中でこんなふうにかけます.
from django.conf.urls import url
from django.views.generic import TemplateView
urlpatterns = [
url(r'^$', TemplateView.as_view(template_name='app/index.html'), name='app_index'),
]
DetailView
使用するテンプレートに加えて,どのモデルを使うかを指定します.
from django.views.generic import DetailView
from .model import AppModel
class Detail(DetailView):
template_name = 'app/detail.html'
model = AppModel
detail = Detail.as_view()
もちろんこのクラスもurls.pyに直書きできます.
templateにはobject
という名前(と,appmodel
のようにモデル名を小文字にした名前)で
取得したオブジェクトが渡されます.
デフォルトではpk
という名前でurls.pyから呼び出されるようになっており,
オブジェクトが見つからないとHttp404がraiseされます.
ListView
Detailはあるモデルの単一オブジェクトだったのに対し,ListViewは複数オブジェクトを返します.
どのオブジェクトを返すかはqueryset
プロパティを渡すか,get_queryset
メソッドをオーバーライドすることで指定します.
from django.views.generic import ListView
from .model import AppModel
class Index(ListView):
template_name = 'app/index.html'
queryset = AppModel.objects.all()
このクラスにはページングや並び順など,多彩なカスタマイズ項目を持っています.
--
本家チュートリアルではこの次にテストについて説明がありますが,
次回チュートリアルでは少し脇道にそれてFormクラス,ModelFormクラスの説明をしていきます.