最初のDjangoアプリの作成、パート4 —Djangoドキュメント

提供:Dev Guides
< DjangoDjango/docs/3.2.x/intro/tutorial04
移動先:案内検索

最初のDjangoアプリの作成、パート4

このチュートリアルは、 Tutorial 3 が中断したところから始まります。 私たちはWeb-pollアプリケーションを継続しており、フォーム処理とコードの削減に焦点を当てます。

助けを得る場所:

このチュートリアルで問題が発生した場合は、FAQの Geting Help セクションにアクセスしてください。


最小限のフォームを書く

前回のチュートリアルの投票詳細テンプレート(「polls / detail.html」)を更新して、テンプレートにHTML <form>要素が含まれるようにします。

投票/テンプレート/投票/詳細.html

<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に固有のものではありません。 それは一般的に良いWeb開発の実践です。
  • forloop.counterは、:ttag: `for` タグがループを通過した回数を示します
  • POSTフォーム(データを変更する効果がある可能性があります)を作成しているため、クロスサイトリクエストフォージェリについて心配する必要があります。 ありがたいことに、Djangoにはそれを防ぐための便利なシステムが付属しているので、それほど心配する必要はありません。 つまり、内部URLを対象とするすべてのPOSTフォームは、 :ttag: `{%csrf_token%} ` テンプレートタグ。

それでは、送信されたデータを処理してそれを処理するDjangoビューを作成しましょう。 Tutorial 3 で、次の行を含む投票アプリケーションのURLconfを作成したことを思い出してください。

polls / urls.py

path('<int:question_id>/vote/', views.vote, name='vote'),

vote()関数のダミー実装も作成しました。 実際のバージョンを作成しましょう。 polls/views.pyに以下を追加します。

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/'

    ここで、3question.idの値です。 このリダイレクトされたURLは、'results'ビューを呼び出して、最終ページを表示します。

Tutorial 3 で説明したように、requestHttpRequest オブジェクトです。 HttpRequest オブジェクトの詳細については、要求と応答のドキュメントを参照してください。

誰かが質問に投票すると、vote()ビューは質問の結果ページにリダイレクトされます。 そのビューを書いてみましょう:

polls / views.py

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テンプレートを作成します。

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>

<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コードを作成する必要がないほど、一般的なパターンを抽象化します。

投票アプリを汎用ビューシステムを使用するように変換して、独自のコードの束を削除できるようにします。 変換を行うには、いくつかの手順を実行する必要があります。 私達はします:

  1. URLconfを変換します。
  2. 古い不要なビューの一部を削除します。
  3. Djangoの一般的なビューに基づいた新しいビューを導入します。

詳細については、以下をお読みください。

なぜコードシャッフル?

一般に、Djangoアプリを作成するときは、ジェネリックビューが問題に適しているかどうかを評価し、コードを途中でリファクタリングするのではなく、最初から使用します。 ただし、このチュートリアルでは、コアコンセプトに焦点を当てるために、これまで「難しい方法」でビューを作成することに意図的に焦点を当ててきました。

電卓を使い始める前に、基本的な数学を知っておく必要があります。


URLconfを修正する

まず、polls/urls.py URLconfを開き、次のように変更します。

polls / urls.py

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>に変更されていることに注意してください。


ビューを修正する

次に、古いindexdetail、およびresultsビューを削除し、代わりにDjangoの汎用ビューを使用します。 これを行うには、polls/views.pyファイルを開き、次のように変更します。

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.

ここでは、 ListViewDetailView の2つの汎用ビューを使用しています。 それぞれ、これら2つのビューは、「オブジェクトのリストを表示する」と「特定のタイプのオブジェクトの詳細ページを表示する」という概念を抽象化します。

  • 各汎用ビューは、どのモデルに作用するかを知る必要があります。 これは、model属性を使用して提供されます。
  • DetailView ジェネリックビューは、URLからキャプチャされた主キー値が"pk"と呼ばれることを想定しているため、ジェネリックのquestion_idpkに変更しました。ビュー。

デフォルトでは、 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 を読んで、投票アプリのテストについて学習してください。