クウネルマナブ

個人的なメモです(笑) 最近は、もっぱら趣味。WEBシステムを作るべくpython,HTML,PHPあたりの勉強を中心にしています!

Django公式 チュートリアル その4

Djangoをマスターしたいので、

チュートリアルにて一連の作業を学びます。

関連記事

 

(個人的な学習ノートです)

 

 

前回Djangoのインストールから仮想環境の設定を行いました。

apist.hateblo.jp

 

Django公式 チュートリアル その1の記事です↓ 

apist.hateblo.jp

 

Django公式 チュートリアル その2の記事です↓  

apist.hateblo.jp

 

Django公式 チュートリアル その3の記事です↓  

 

apist.hateblo.jp

  

 

そのままチュートリアルに進みます!

 

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

 

学習環境

macOS Sierra 10.12

python3.6

Django1.11

XAMPP

  MySQLMariaDB10.1.21)

テキストエディッタ:ATOM

 

参考サイト

 

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

 

上記サイトに沿って進めます。

 

 しんどい感じだけどもうちょい頑張ります!

Web 投票アプリケーションの開発を例にして、簡単なフォー ム処理とコードの縮小化を中心に解説

 

 

簡単なフォームを書く

前回作成した投票詳細テンプレート (「polls/detail.html」) を更新して、HTML の <form> 要素を挿入

 

( polls/templates/polls/detail.html )

<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 がこれに対応するとても使いやすい仕組みを提供してくれているため、あまり心配する必要はない。
  • 全ての自サイトへの POST フォームに、 {%csrf_token %}テンプレートタグを使う。

 

 

送信されたデータを処理するための Django のビューを作成

チュートリアル3で、作成した、

( polls/urls.py )

url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'),

を変更。

 

( polls/views.py )

from django.shortcuts import get_object_or_404, render
from django.http import HttpResponseRedirect, HttpResponse
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の値は常に文字列
  • GET データにアクセスするために同様に request.GETを提供している。ただし、このコードでは、POST を経由した呼び出しでないとデータを更新させないようにするために、request.POSTを明示的に使用。
 
■POST データに choice がなければ、 request.POST['choice'] は KeyErrorを出力
  • コードでKeyErrorをチェックし、 choice がない場合にはエラーメッセージ付きの質問フォームを再表示
 
■choice のカウントをインクリメントした後、通常のHttpResponse ではなく
HttpResponseRedirectを返す
HttpResponseRedirect はひとつの引数をとる
リダイレクト先のURL (このような場合にURLを構築する方法については、以下のポイントを参考に)
 
上記の Python コメントが指摘するように、POST データが成功した後に HttpResponseRedirect を常に返す必要がある。Django 固有のものではなく、良い Web 開発のプラクティス

 

■この例では、 HttpResponseRedirect コンストラクタの中で reverse()関数を使用

  • この関数を使うと、ビュー関数中での URL のハードコードを防げる
  • 関数には、制御を渡したいビューの名前と、そのビューに与える URL パターンの位置引数を与える
  • この例では、 チュートリアル3で設定した URLconf を使っているので、 reverse()を呼ぶと、次のような文字列が返っくる

    '/polls/3/results/'

  • この 3 は question.id の値
  • リダイレクト先の URL は 'results' ビューを呼び出し、最終的なページを表示

 

チュートリアル3でも触れたように、 request は HttpRequest オブジェクト

続いて、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>

 

http://127.0.0.1:8000/polls/1/ にアクセスして確認してみましょう。

 

 

汎用ビューを使う(コードが少ないことはいいことだ!)

detail() ( チュートリアル3 ) と results() ビューはとても簡単で、冗長。

投票の一覧を表示する index() ビュー も同様。

これらのビューは基本的な Web開発の一般的なケース。

URL を介して渡されたパラメータに従ってデータベースからデータを取り出し、テンプレートをロードして、レンダリングしたテンプレートを返す。

これはよくあることなので、 Django では、汎用ビュー 「generic view」 というショートカットを提供している。

 

汎用ビューとは、よくあるパターンを抽象化して、 Python コードすら書かずにアプリケーションを書き上げられる状態にしたもの。

 

これまで作成してきた poll アプリを汎用ビューシステムに変換して、 コードをばっさり捨ててしまいましょう。

変換にはほんの数ステップしかか かりません。

 

そのステップ:

  1. URLconf を変換する。
  2. 古い不要なビューを削除する。
  3. 新しいビューに Djangoの汎用ビューを設定する。

 

URLconf の修正

URLconf の polls/urls.py を開き、次のように変更

 

( polls/urls.py )

from django.conf.urls import url

from . import views

app_name = 'polls'
urlpatterns = [
    url(r'^$', views.IndexView.as_view(), name='index'),
    url(r'^(?P<pk>[0-9]+)/$', views.DetailView.as_view(), name='detail'),
    url(r'^(?P<pk>[0-9]+)/results/$', views.ResultsView.as_view(), name='results'),
    url(r'^(?P<question_id>[0-9]+)/vote/$', views.vote, name='vote'),
]

 

2 つ目と 3 つ目の正規表現でのマッチパターンの名前が <question_id> から <pk> に変更されたことに注意 

  

views の修正

次に、古い index 、 detail 、と results のビューを削除し、代わりに Django の汎用ビューを使用。

これを行うには、 polls/views.py ファイルを開き、次のように変更します:

 

( polls/views.py )

from django.shortcuts import get_object_or_404, render
from django.http import HttpResponseRedirect
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 を使用します。

これらのビューはそれぞれ、「オブジェクトのリストを表示する」および「あるタイプのオブジェクトの詳細ページを表示する」という二つの概念を抽象化。

  • 各汎用ビューは自分がどのモデルに対して動作するのか知らせておく必要があります。これは、 model 属性を使用して提供される。
  • DetailView 汎用ビューには、 "pk" という名前で URL からプライマリキーをキャプチャして渡すことになっている、 汎用ビュー向けに question_id を pk に変更。

デフォルトでは、 DetailView 汎用ビューは <app name>/<model name>_detail.html という名前のテンプレートを使う。

この場合、テンプレートの名前は "polls/question_detail.html" 。

template_name 属性は Django に自動生成されたデフォルトのテンプレート名ではなく、指定した名前を使うように伝えるために使われる。

results リストビューにも template_name を指定。

これは、 結果ビューと詳細ビューがお互い DetailView であるにも関わらず、レンダリングされたとき違った外観を持っているため。

 

同様に、 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 に使用したい変数名を伝えるほうが簡単だろう。

 

サーバを実行して、新しく汎用ビューベースにした投票アプリケーションを使ってみまる。

 

次回は、「初めてのDjangoアプリ作成、その5」に 取り掛かかります。

 

関連記事