Quantcast
Channel: 初心者タグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 21089

初心者の初心者による初心者のためのDjangoチュートリアルまとめ④

$
0
0

はじめに

この記事はDjangoの公式チュートリアルを進めていくシリーズものです。
今回は4記事目「はじめての Django アプリ作成、その 4」を進めていきます。

初心者の初心者による初心者のためのDjangoチュートリアルまとめ①
初心者の初心者による初心者のためのDjangoチュートリアルまとめ②
初心者の初心者による初心者のためのDjangoチュートリアルまとめ③

はじめての Django アプリ作成、その 4

Write a minimal form

前回の記事で作成したdetail.html内に投票のためのフォームを設置します。

polls/templates/polls/detail.html
<h1>{{ question.question_text }}</h1>

{% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}

<formaction="{% url 'polls:vote' question.id %}"method="post">
{% csrf_token %}
{% for choice in question.choice_set.all %}
    <inputtype="radio"name="choice"id="choice{{ forloop.counter }}"value="{{ choice.id }}"><labelfor="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br>
{% endfor %}
<inputtype="submit"value="Vote"></form>

forloop.counterは、 for タグのループが何度実行されたかを表す値です。
内容を見るとQuestionに関連付くchoice全てがfor文でradioボタンの選択肢として表示されることがわかります。

CSRF対策にPOSTのformを作成する際は{% csrf_token %}を挿入する必要があります。

次にvoteがしっかり機能するようにpolls/views.pyを編集します。

polls/views.py
fromdjango.httpimportHttpResponse,HttpResponseRedirectfromdjango.shortcutsimportget_object_or_404,renderfromdjango.urlsimportreversefrom.modelsimportChoice,Question# ...
defvote(request,question_id):question=get_object_or_404(Question,pk=question_id)try:selected_choice=question.choice_set.get(pk=request.POST['choice'])except(KeyError,Choice.DoesNotExist):# Redisplay the question voting form.
returnrender(request,'polls/detail.html',{'question':question,'error_message':"You didn't select a choice.",})else:selected_choice.votes+=1selected_choice.save()# Always return an HttpResponseRedirect after successfully dealing
# with POST data. This prevents data from being posted twice if a
# user hits the Back button.
returnHttpResponseRedirect(reverse('polls:results',args=(question.id,)))

request.POST['choice']により上記のFormの'choice'のnameでPOSTされた値をselected_choiceとして読み取っています。

request.POST['choice']が存在しないとき(すなわちユーザーが何も選択しなかった場合)、KeyErrorが送出されます。
その場合はエラーメッセージを添えてもう一度formがrenderされます。

処理が正常に終了するとresultsにredirectします。
(POSTなどの処理が正常に終了した際はredirectをかけるべきです(Djangoにかかわらず))

reverse関数でURLのハードコートを防ぐことができます。
使用方法はindex.htmlで使用したURLの指定と似ています。

ではresultsのviewをpolls/views.pyに追記します。

polls/views.py
fromdjango.shortcutsimportget_object_or_404,renderdefresults(request,question_id):question=get_object_or_404(Question,pk=question_id)returnrender(request,'polls/results.html',{'question':question})

内容はdetailのviewとほぼほぼ同じになっています。

Templateも作成しましょう。

polls/templates/polls/results.html
<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><ahref="{% url 'polls:detail' question.id %}">Vote again?</a>

ここまでで一つ問題点があることに気づいたでしょうか

現在の処理の流れでは最初にselected_choiceをDBから取得し、それのvotesから新しい値を計算してDBに保存します。

votesの値がもともと42だったとして、AさんとBさんが同時に投票すると本来では44にならなくてはいけないところが43になってしまいます。

こういう問題は「競合問題」と呼ばれるそうです、解決のためのF()の説明は以下の記事が参考になります。
JavaおじさんがPythonを使えるようになるまでの全記録(その16)

汎用ビューを使う: コードが少ないのはいいことだ

Djangoには凡用View(generic view)というショートカットが存在します

URL を介して渡されたパラメータに従ってデータベースからデータを取り出し、テンプレートをロードして、レンダリングしたテンプレートを返します。これはきわめてよくあることなので、 Django では、汎用ビュー(generic view)というショートカットを提供しています。
汎用ビューとは、よくあるパターンを抽象化して、 Python コードすら書かずにアプリケーションを書き上げられる状態にしたものです。

とのことです。

URLconf の修正

polls/urls.pyを以下のように変更します

polls/urls.py
fromdjango.urlsimportpathfrom.importviewsapp_name='polls'urlpatterns=[path('',views.IndexView.as_view(),name='index'),path('<int:pk>/',views.DetailView.as_view(),name='detail'),path('<int:pk>/results/',views.ResultsView.as_view(),name='results'),path('<int:question_id>/vote/',views.vote,name='vote'),]

違いはpathの指定するviewにviews.IndexView.as_view()などを渡している点です。
また、question_idpkとして名称変更されています。(理由は後述)

view内でIndexViewクラスなどを作成します。

polls/views.py
fromdjango.httpimportHttpResponseRedirectfromdjango.shortcutsimportget_object_or_404,renderfromdjango.urlsimportreversefromdjango.viewsimportgenericfrom.modelsimportChoice,QuestionclassIndexView(generic.ListView):template_name='polls/index.html'context_object_name='latest_question_list'defget_queryset(self):"""Return the last five published questions."""returnQuestion.objects.order_by('-pub_date')[:5]classDetailView(generic.DetailView):model=Questiontemplate_name='polls/detail.html'classResultsView(generic.DetailView):model=Questiontemplate_name='polls/results.html'defvote(request,question_id):...# same as above, no changes needed.

ListViewDetailViewが使用されています。
question_idpkとして名称変更されていたのはDetailViewでそのように規定されているためです。

もともとデフォルトでは、 DetailView汎用ビューは /_detail.html という名前のテンプレートを使いますが、template_name属性を指定することで使用するテンプレートを変更できます。(ListViewも同様です)


Viewing all articles
Browse latest Browse all 21089

Trending Articles