最初のDjangoアプリの作成、パート4
このチュートリアルは、 Tutorial 3 が中断したところから始まります。 私たちはWeb-pollアプリケーションを継続しており、フォーム処理とコードの削減に焦点を当てます。
最小限のフォームを書く
前回のチュートリアルの投票詳細テンプレート(「polls / detail.html」)を更新して、テンプレートにHTML <form>
要素が含まれるようにします。
<h1>{{ question.question_text }}</h1>
{% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}
<form action="{% url 'polls: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>
簡単な要約:
- 上記のテンプレートには、質問の選択肢ごとにラジオボタンが表示されます。 各ラジオボタンの
value
は、関連する質問の選択肢のIDです。 各ラジオボタンのname
は"choice"
です。 つまり、誰かがラジオボタンの1つを選択してフォームを送信すると、POSTデータchoice=#
が送信されます。ここで、#は選択した選択肢のIDです。 これがHTMLフォームの基本的な概念です。 - フォームの
action
を{% url 'polls:vote' question.id %}
に設定し、method="post"
を設定します。method="post"
(method="get"
ではなく)を使用することは非常に重要です。このフォームを送信すると、サーバー側のデータが変更されるためです。 サーバー側のデータを変更するフォームを作成する場合は、常にmethod="post"
を使用してください。 このヒントはDjangoに固有のものではありません。 それは一般的に良いWeb開発の実践です。 forloop.counter
は、:ttag: `for` タグがループを通過した回数を示します- POSTフォーム(データを変更する効果がある可能性があります)を作成しているため、クロスサイトリクエストフォージェリについて心配する必要があります。 ありがたいことに、Djangoにはそれを防ぐための便利なシステムが付属しているので、それほど心配する必要はありません。 つまり、内部URLを対象とするすべてのPOSTフォームは、 :ttag: `{%csrf_token%} ` テンプレートタグ。
それでは、送信されたデータを処理してそれを処理するDjangoビューを作成しましょう。 Tutorial 3 で、次の行を含む投票アプリケーションの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):
# Redisplay the question voting form.
return render(request, 'polls/detail.html', {
'question': question,
'error_message': "You didn't select a choice.",
})
else:
selected_choice.votes += 1
selected_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.
return HttpResponseRedirect(reverse('polls:results', args=(question.id,)))
このコードには、このチュートリアルでまだ取り上げていないことがいくつか含まれています。
request.POST は辞書のようなオブジェクトで、送信されたデータにキー名でアクセスできます。 この場合、
request.POST['choice']
は、選択した選択肢のIDを文字列として返します。 request.POST の値は常に文字列です。Djangoは、同じ方法でGETデータにアクセスするための request.GET も提供しますが、コードで request.POST を明示的に使用して、データがPOST呼び出し。
choice
がPOSTデータで提供されていない場合、request.POST['choice']
はKeyError
を発生させます。 上記のコードはKeyError
をチェックし、choice
が指定されていない場合は、エラーメッセージとともに質問フォームを再表示します。選択カウントをインクリメントした後、コードは通常の HttpResponse ではなく HttpResponseRedirect を返します。 HttpResponseRedirect は、ユーザーがリダイレクトされるURLという単一の引数を取ります(この場合のURLの作成方法については、次のポイントを参照してください)。
上記のPythonコメントが指摘しているように、POSTデータを正常に処理した後は、常に HttpResponseRedirect を返す必要があります。 このヒントはDjangoに固有のものではありません。 それは一般的に良いWeb開発の実践です。
この例では、 HttpResponseRedirect コンストラクターで reverse()関数を使用しています。 この関数は、ビュー関数でURLをハードコーディングする必要をなくすのに役立ちます。 制御を渡すビューの名前と、そのビューを指すURLパターンの可変部分が与えられます。 この場合、 Tutorial 3 で設定したURLconfを使用して、この reverse()呼び出しは次のような文字列を返します。
'/polls/3/results/'
ここで、
3
はquestion.id
の値です。 このリダイレクトされたURLは、'results'
ビューを呼び出して、最終ページを表示します。
Tutorial 3 で説明したように、request
は HttpRequest オブジェクトです。 HttpRequest オブジェクトの詳細については、要求と応答のドキュメントを参照してください。
誰かが質問に投票すると、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})
これは、 Tutorial 3 からの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/
に移動し、質問に投票します。 投票するたびに更新される結果ページが表示されます。 選択肢を選択せずにフォームを送信すると、エラーメッセージが表示されます。
ノート
vote()
ビューのコードには小さな問題があります。 最初にデータベースからselected_choice
オブジェクトを取得し、次にvotes
の新しい値を計算して、それをデータベースに保存します。 あなたのウェブサイトの2人のユーザーがでまったく同時にに投票しようとすると、これはうまくいかない可能性があります。同じ値、たとえば42がvotes
に対して取得されます。 次に、両方のユーザーについて、43の新しい値が計算されて保存されますが、44が期待値になります。
これは競合状態と呼ばれます。 興味がある場合は、 F()を使用した競合状態の回避を読んで、この問題を解決する方法を学ぶことができます。
一般的なビューを使用する:コードが少ないほど良い
detail()
( Tutorial 3 から)およびresults()
ビューは非常に短く、前述のように冗長です。 投票のリストを表示するindex()
ビューも同様です。
これらのビューは、基本的なWeb開発の一般的なケースを表しています。つまり、URLで渡されたパラメーターに従ってデータベースからデータを取得し、テンプレートをロードして、レンダリングされたテンプレートを返します。 これは非常に一般的であるため、Djangoは「汎用ビュー」システムと呼ばれるショートカットを提供します。
ジェネリックビューは、アプリを作成するためにPythonコードを作成する必要がないほど、一般的なパターンを抽象化します。
投票アプリを汎用ビューシステムを使用するように変換して、独自のコードの束を削除できるようにします。 変換を行うには、いくつかの手順を実行する必要があります。 私達はします:
- URLconfを変換します。
- 古い不要なビューの一部を削除します。
- Djangoの一般的なビューに基づいた新しいビューを導入します。
詳細については、以下をお読みください。
なぜコードシャッフル?
一般に、Djangoアプリを作成するときは、ジェネリックビューが問題に適しているかどうかを評価し、コードを途中でリファクタリングするのではなく、最初から使用します。 ただし、このチュートリアルでは、コアコンセプトに焦点を当てるために、これまで「難しい方法」でビューを作成することに意図的に焦点を当ててきました。
電卓を使い始める前に、基本的な数学を知っておく必要があります。
URLconfを修正する
まず、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):
... # same as above, no changes needed.
ここでは、 ListView と DetailView の2つの汎用ビューを使用しています。 それぞれ、これら2つのビューは、「オブジェクトのリストを表示する」と「特定のタイプのオブジェクトの詳細ページを表示する」という概念を抽象化します。
- 各汎用ビューは、どのモデルに作用するかを知る必要があります。 これは、
model
属性を使用して提供されます。 - DetailView ジェネリックビューは、URLからキャプチャされた主キー値が
"pk"
と呼ばれることを想定しているため、ジェネリックのquestion_id
をpk
に変更しました。ビュー。
デフォルトでは、 DetailView 汎用ビューは、<app name>/<model name>_detail.html
というテンプレートを使用します。 この場合、テンプレート"polls/question_detail.html"
を使用します。 template_name
属性は、自動生成されたデフォルトのテンプレート名の代わりに特定のテンプレート名を使用するようにDjangoに指示するために使用されます。 また、results
リストビューにtemplate_name
を指定します。これにより、結果ビューと詳細ビューは、両方とも DetailView [であっても、レンダリング時に異なる外観になります。 X199X]舞台裏。
同様に、 ListView 汎用ビューは、<app name>/<model name>_list.html
と呼ばれるデフォルトのテンプレートを使用します。 template_name
を使用して、 ListView に既存の"polls/index.html"
テンプレートを使用するように指示します。
チュートリアルの前の部分では、テンプレートには、question
およびlatest_question_list
コンテキスト変数を含むコンテキストが提供されています。 DetailView
の場合、question
変数が自動的に提供されます。Djangoモデル(Question
)を使用しているため、Djangoはコンテキスト変数の適切な名前を決定できます。 ただし、ListViewの場合、自動生成されるコンテキスト変数はquestion_list
です。 これをオーバーライドするために、context_object_name
属性を指定し、代わりにlatest_question_list
を使用することを指定します。 別のアプローチとして、新しいデフォルトのコンテキスト変数に一致するようにテンプレートを変更することもできますが、必要な変数を使用するようにDjangoに指示する方がはるかに簡単です。
サーバーを実行し、汎用ビューに基づいて新しいポーリングアプリを使用します。
ジェネリックビューの詳細については、ジェネリックビューのドキュメントを参照してください。
フォームと一般的なビューに慣れたら、このチュートリアルのパート5 を読んで、投票アプリのテストについて学習してください。