はじめに
このチュートリアルは、パブリックインターフェイスビューの作成が終わったところから始まります。私たちはウェブ投票アプリケーションを続け、フォーム処理とコードの削減に焦点を当てます。
このチュートリアルは、パブリックインターフェイスビューの作成が終わったところから始まります。私たちはウェブ投票アプリケーションを続け、フォーム処理とコードの削減に焦点を当てます。
前回のチュートリアルで作成した投票詳細テンプレート(polls/detail.html
)を更新しましょう。このテンプレートに HTML の<form>
要素を含めます。
<form action="{% url 'polls:vote' question.id %}" method="post">
{% csrf_token %}
<fieldset>
<legend><h1>{{ question.question_text }}</h1></legend>
{% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}
{% 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 %}
</fieldset>
<input type="submit" value="Vote">
</form>
簡単な説明:
value
は、関連する質問の選択肢の ID です。各ラジオボタンのname
は"choice"
です。つまり、誰かがラジオボタンの 1 つを選択してフォームを送信すると、POST データchoice=#
が送信されます。ここで#
は選択された選択肢の ID です。これが HTML フォームの基本的な概念です。action
を{% url 'polls:vote' question.id %}
に設定し、method="post"
に設定しました。method="post"
(method="get"
とは対照的)を使用することは非常に重要です。なぜなら、このフォームを送信するとサーバーサイドのデータが変更されるからです。サーバーサイドのデータを変更するフォームを作成する場合は常にmethod="post"
を使用します。このヒントは Django に特有のものではありません。一般的な良いウェブ開発の慣習です。forloop.counter
は、for
タグがループを何回行ったかを示します。{% csrf_token %}<csrf_token>
テンプレートタグを使用する必要があります。では、送信されたデータを処理して何かを行う Django のビューを作成しましょう。覚えておいてください。**パブリックインターフェイスビューの作成**
では、投票アプリケーション用の URLconf を作成しました。この URLconf には次の行が含まれています。
path("<int:question_id>/vote/", views.vote, name="vote"),
また、vote()
関数のダミー実装も作成しました。本物のバージョンを作成しましょう。polls/views.py
に次のコードを追加します。
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
from django.urls import reverse
from.models import Choice, Question
#...
def vote(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):
## 質問の投票フォームを再表示します。
return render(
request,
"polls/detail.html",
{
"question": question,
"error_message": "You didn't select a choice.",
},
)
else:
selected_choice.votes += 1
selected_choice.save()
## POST データを正常に処理した後は常に HttpResponseRedirect を返します。
## これにより、ユーザーが戻るボタンを押した場合にデータが 2 回送信されるのを防ぎます。
return HttpResponseRedirect(reverse("polls:results", args=(question.id,)))
このコードには、このチュートリアルでまだ扱っていないいくつかの要素が含まれています。
request.POST <django.http.HttpRequest.POST>
は、キー名で送信されたデータにアクセスできる辞書のようなオブジェクトです。この場合、request.POST['choice']
は選択された選択肢の ID を文字列として返します。request.POST <django.http.HttpRequest.POST>
の値は常に文字列です。
ただし、Django は同じ方法で GET データにアクセスするためのrequest.GET <django.http.HttpRequest.GET>
も提供しています。ただし、私たちはコードで明示的にrequest.POST <django.http.HttpRequest.POST>
を使用しています。これにより、データが POST 呼び出しを介してのみ変更されることを確認します。
request.POST['choice']
は、POST データにchoice
が提供されていない場合にKeyError
を発生させます。上記のコードはKeyError
をチェックし、choice
が指定されていない場合にエラーメッセージ付きで質問フォームを再表示します。
選択肢のカウントを増やした後、コードは通常の~django.http.HttpResponse
ではなく~django.http.HttpResponseRedirect
を返します。~django.http.HttpResponseRedirect
は 1 つの引数を取ります。この引数は、ユーザーがリダイレクトされる URL です(この場合、URL を構築する方法については次の項を参照)。
上の Python のコメントにもあるように、POST データを正常に処理した後は常に~django.http.HttpResponseRedirect
を返す必要があります。このヒントは Django に特有のものではありません。一般的な良いウェブ開発の慣習です。
この例では、~django.http.HttpResponseRedirect
コンストラクタで~django.urls.reverse
関数を使用しています。この関数は、ビュー関数で URL をハードコードする必要を回避するのに役立ちます。これには、制御を渡したいビューの名前と、そのビューを指す URL パターンの可変部分が与えられます。この場合、**パブリックインターフェイスビューの作成**
で設定した URLconf を使用すると、この~django.urls.reverse
呼び出しは次のような文字列を返します。
"/polls/3/results/"
ここで3
はquestion.id
の値です。このリダイレクトされた URL は、最終的なページを表示するために'results'
ビューを呼び出します。
**パブリックインターフェイスビューの作成**
で述べたように、request
は~django.http.HttpRequest
オブジェクトです。~django.http.HttpRequest
オブジェクトに関する詳細については、request and response documentation </ref/request-response>
を参照してください。
誰かが質問に投票した後、vote()
ビューは質問の結果ページにリダイレクトします。そのビューを書きましょう。
from django.shortcuts import get_object_or_404, render
def results(request, question_id):
question = get_object_or_404(Question, pk=question_id)
return render(request, "polls/results.html", {"question": question})
これは、**パブリックインターフェイスビューの作成**
のdetail()
ビューとほぼ同じです。唯一の違いはテンプレート名です。後でこの冗長性を修正します。
では、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>
<a href="{% url 'polls:detail' question.id %}">Vote again?</a>
では、ブラウザで/polls/1/
に移動して質問に投票してみましょう。投票するたびに結果ページが更新されるはずです。選択肢を選ばずにフォームを送信すると、エラーメッセージが表示されるはずです。
cd ~/project/mysite
python manage.py runserver 0.0.0.0:8080
注記:
私たちのvote()
ビューのコードには小さな問題があります。まず、データベースからselected_choice
オブジェクトを取得し、次にvotes
の新しい値を計算し、そしてそれをデータベースに保存します。あなたのウェブサイトの 2 人のユーザーがまさに同時に投票しようとした場合、これがうまくいかないことがあります。同じ値、たとえば 42 がvotes
として取得されます。そして、両方のユーザーにとって新しい値 43 が計算されて保存されますが、期待される値は 44 です。
これは「競合条件」と呼ばれます。興味があれば、avoiding-race-conditions-using-f
を読んで、この問題をどのように解決するか学ぶことができます。
detail()
(**パブリックインターフェイスビューの作成**
から)とresults()
ビューは非常に短く、上記のように冗長です。投票一覧を表示するindex()
ビューも同様です。
これらのビューは、基本的なウェブ開発の一般的なケースを表しています。URL に渡されたパラメータに基づいてデータベースからデータを取得し、テンプレートを読み込み、レンダリングされたテンプレートを返すというものです。これは非常に一般的なため、Django は「汎用ビュー」システムと呼ばれるショートカットを提供しています。
汎用ビューは、共通のパターンを抽象化しており、アプリを書くために Python コードさえ書かなくても良いほどまでです。
投票アプリを汎用ビューシステムを使うように変更しましょう。そうすることで、自分たちのコードをたくさん削除できます。変更にはいくつかの手順が必要です。
詳細は以下を読んでください。
なぜコードを入れ替えるのか?
一般的に、Django アプリを書く際は、汎用ビューが問題に適しているかどうかを評価し、最初から使うようにします。途中でコードをリファクタリングすることはありません。しかし、このチュートリアルではこれまでコアコンセプトに焦点を当てるため、「難しい方法」でビューを書くことに重点を置いてきました。
電卓を使い始める前に基本的な数学を知っておく必要があります。
まず、polls/urls.py
の URLconf を開き、以下のように変更します。
from django.urls import path
from. import views
app_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"),
]
2 番目と 3 番目のパターンのパス文字列での一致するパターンの名前が、<question_id>
から<pk>
に変更されていることに注意してください。
次に、古いindex
、detail
、results
ビューを削除し、代わりに Django の汎用ビューを使います。そのために、polls/views.py
ファイルを開き、以下のように変更します。
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
from django.urls import reverse
from django.views import generic
from.models import Choice, Question
class IndexView(generic.ListView):
template_name = "polls/index.html"
context_object_name = "latest_question_list"
def get_queryset(self):
"""Return the last five published questions."""
return Question.objects.order_by("-pub_date")[:5]
class DetailView(generic.DetailView):
model = Question
template_name = "polls/detail.html"
class ResultsView(generic.DetailView):
model = Question
template_name = "polls/results.html"
def vote(request, question_id):
... ## 上と同じで、変更は不要。
ここでは 2 つの汎用ビューを使っています。~django.views.generic.list.ListView
と~django.views.generic.detail.DetailView
です。それぞれのビューは、「オブジェクトの一覧を表示する」と「特定の種類のオブジェクトの詳細ページを表示する」という概念を抽象化しています。
model
属性を使って提供されます。~django.views.generic.detail.DetailView
汎用ビューは、URL からキャプチャされた主キー値が"pk"
と呼ばれることを期待しています。そのため、汎用ビュー用にquestion_id
をpk
に変更しました。デフォルトでは、~django.views.generic.detail.DetailView
汎用ビューは<アプリ名>/<モデル名>_detail.html
と呼ばれるテンプレートを使用します。私たちの場合、"polls/question_detail.html"
を使用します。template_name
属性は、Django に自動生成されたデフォルトのテンプレート名の代わりに特定のテンプレート名を使用するように指示するために使用されます。また、results
一覧ビューのtemplate_name
も指定しています。これにより、結果ビューと詳細ビューがレンダリングされる際に異なる外見を持つようになります。実際には、両方とも背後で~django.views.generic.detail.DetailView
です。
同様に、~django.views.generic.list.ListView
汎用ビューは、<アプリ名>/<モデル名>_list.html
と呼ばれるデフォルトのテンプレートを使用します。私たちはtemplate_name
を使って、~django.views.generic.list.ListView
に既存の"polls/index.html"
テンプレートを使用するように指示しています。
このチュートリアルの前の部分では、テンプレートにはquestion
とlatest_question_list
のコンテキスト変数が含まれるコンテキストが提供されていました。DetailView
の場合、question
変数は自動的に提供されます。Django モデル(Question
)を使用しているため、Django はコンテキスト変数に適切な名前を決定できます。一方、ListView
の場合、自動生成されるコンテキスト変数はquestion_list
です。これをオーバーライドするために、context_object_name
属性を提供して、latest_question_list
を代わりに使用することを指定しています。別の方法として、新しいデフォルトのコンテキスト変数に合わせてテンプレートを変更することもできます。しかし、Django に使用したい変数を指定する方がはるかに簡単です。
サーバーを起動して、汎用ビューに基づく新しい投票アプリを使ってみましょう。
おめでとうございます!あなたはフォーム処理とコードの削減の実験を完了しました。あなたのスキルを向上させるために、LabEx でさらに実験を行って練習することができます。