土鍋のサイズの記事が人気となる時期がやって参りました。
Categories情報処理

Django公式サイトのチュートリアルを咀嚼しながらやってみる

Date 2021/05/25 02:30  Author Yutaka  Tags やってみた

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

出典:はじめての Django アプリ作成、その 3 | Django ドキュメント | Django
https://docs.djangoproject.com/ja/3.2/intro/tutorial03/

4.1. オーバービュー

 Polls アプリ(投票アプリ)では以下の4つのビューを作成します。

  • 質問 “インデックス” ページ — 最新の質問をいくつか表示
  • 質問 “詳細” ページ — 結果を表示せず、質問テキストと投票フォームを表示
  • 質問 “結果” ページ — 特定の質問の結果を表示
  • 投票ページ — 特定の質問の選択を投票として受付

 そのほか URLconf の基本的な使い方を学びます。

4.2. もっとビューを書いてみる

 polls/views.py を編集してビューを追加します。

# インデックス ページ
def index(request):
    #(省略)

# 詳細 ページ
def detail(request, question_id):
    return HttpResponse("You're looking at question %s." % question_id)
    # printf形式の文字列書式化を使用しています。このやり方は古いらしく、
    # "You're looking at question {}.".format(question_id)
    # とするほうがイマドキらしい。

    # 7. 入力と出力 — Python 3.9.4 ドキュメント - 7.1.4. 古い文字列書式設定方法
    # https://docs.python.org/ja/3/tutorial/inputoutput.html#old-string-formatting

    # 7. 入力と出力 — Python 3.9.4 ドキュメント - 7.1.2. 文字列の format() メソッド
    # https://docs.python.org/ja/3/tutorial/inputoutput.html#the-string-format-method

# 結果 ページ
def results(request, question_id):
    response = "You're looking at the results of question %s."
    return HttpResponse(response % question_id)
    # メッセージを 一回変数 response に入れてるけど、
    # やってることは上の detail と同じ。

# 投票 ページ
def vote(request, question_id):
    return HttpResponse("You're vooting on question %s." % question_id)

 polls.urls モジュール (polls/urls.py) に path() コールを追加して、ビューと紐付けます。

#(省略)

urlpatterns = [
    # ex: /polls/
    path('', views.index, name='index'),
    # polls/ (pollsのroot) にアクセスすると views.index を呼び出す という意味。
    # name は template から url を参照するときに便利らしい。
    #
    # python、djangoのurls.pyで設定するnameってなんやねん?? - Qiita
    # https://qiita.com/sr2460/items/11a1129975913ed584d3

    #ex: /polls/5/
    path('<int:question_id>/', views.detail, name='detail'),
    # <int:question_id> を引数として views.detail に渡す。
    # <int:question_id> はパスコンバータ。
    # 正規表現の (?P<question_id>[0-9]+) に相当する。
    #
    # URL ディスパッチャ | Django ドキュメント | Django - Path converters
    # https://docs.djangoproject.com/ja/3.2/topics/http/urls/#path-converters
    #
    # たとえば /polls/5/ にアクセスすると以下のようにviews のメソッドが呼び出される。
    # detail(request=<HttpRequest object>, question_id=5)
    
    #ex: /polls/5/results/
    path('<int:question_id>/results/', views.results, name='results'),
    #ex: /polls/5/vote/
    path('<int:question_id>/vote/', views.vote, name='vote'),
]

 開発サーバを立ち上げて、動作を確認します。

% python manage.py runserver

 以下のURLにアクセスして、それぞれ数字(34)が反映されたメッセージが表示されればおk。

  • http://127.0.0.1:8000/polls/34/
    >>> You’re looking at question 34.
  • http://127.0.0.1:8000/polls/34/results/
    >>> You’re looking at the results of question 34.
  • http://127.0.0.1:8000/polls/34/vote/
    >>> You’re vooting on question 34.

4.3. 実際に動作するビューを書く

 データベースAPIを利用したビューを作るため、polls/views.py を以下のように Question を読み込み、 index() を書き換えます。

from django.http import HttpResponse

# Question オブジェクトの読み込み(追加)
from .models import Question

# インデックス ページ
def index(request):
    latest_question_list = Question.objects.order_by('-pub_date')[:5]
    # Question を order_by(-pub_date) でソートし、前から5つの配列を取得
    #   マイナスをつけるだけでデータを降順にしてくれる。
    #   [:5] は配列をスライスする。[0:5]と同じで0番目から5つ取得する意味。
    output = ', '.join([q.question_text for q in latest_question_list])
    # リスト内包表記 [式 for 任意の変数名 in イテラブルオブジェクト]
    # やってることは以下と同じ。
    #
    # question_array =[]
    # for q in latest_question_list:
    #	question_array.append(q.question_text)
    # output = ', '.join(question_array)
    #
    # 5. データ構造 — Python 3.9.4 ドキュメント - 5.1.3. リストの内包表記
    # https://docs.python.org/ja/3/tutorial/datastructures.html#list-comprehensions
    return HttpResponse(output)

#(省略)

 http://127.0.0.1:8000/polls/ にアクセスすると最新5件の質問をカンマで区切り出力します。

4.3.1. テンプレート

 現在の状態ではページデザインを views.py に直接記入しています。これはメンテナンス性が低いので、ページデザインとロジックを切り離すため、テンプレートを作成します。

 polls/template/polls/ に以下を記述したテンプレートファイル index.html を作成します。

{% if latest_question_list %}
    <ul>
    {% for question in latest_question_list %}
        <li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>
    {% endfor %}
    </ul>
{% else %}
    <p>No polls are available.</p>
{% endif %}

 現在のpollsディレクトリの構成は以下のような感じです。「+」は今回追加したファイルです。

polls/
    __init__.py
    admin.py
    apps.py
    migrations/
  + templates/
      + polls/
          + index.html
    __init__.py
    models.py
    tests.py
    urls.py
    views.py

 あわせて polls/views.py のindex() を以下のように書き換えます。

from django.http import HttpResponse

#テンプレートの読み込み(追加)
from django.template import loader

# Question オブジェクトの読み込み
from .models import Question

# インデックス ページ
def index(request):
    latest_question_list = Question.objects.order_by('-pub_date')[:5]
    # Question を order_by(-pub_date) でソートし、前から5つの配列を取得
    #   マイナスをつけるだけでデータを降順にしてくれる。
    #   [:5] は配列をスライスする。[0:5]と同じで0番目から5つ取得する意味。

    # テンプレートの読み込み
    template = loader.get_template('polls/index.html')

    # テンプレートで使う変数を設定
    context = {
        'latest_question_list': latest_question_list,
    }
    return HttpResponse(template.render(context, request))

#(省略)

 http://127.0.0.1:8000/polls/ にアクセスすると詳細ページへのリンクの付いた質問のリストが表示されます。

4.3.2. ショートカット: render()

 HttpResponse + template の組合せはよく使われるので、ショートカット render が用意されています。

# ショートカットの読み込み
from django.shortcuts import render
# 最初から入ってるコード。ついに出番がきた。

# view をすべて render を使って書き換えた場合、
# HttpResponse と loader の読み込みが不要になります。
# 現段階ではまだ index 以外で HttpResponse を使っているので残してあります。
from django.http import HttpResponse
#from django.template import loader

# Question オブジェクトの読み込み
from .models import Question

# インデックス ページ
def index(request):
    latest_question_list = Question.objects.order_by('-pub_date')[:5]
    # Question を order_by(-pub_date) でソートし、前から5つの配列を取得
    #   マイナスをつけるだけでデータを降順にしてくれる。
    #   [:5] は配列をスライスする。[0:5]と同じで0番目から5つ取得する意味。

    # テンプレートの読み込み(コメントアウト)
    #template = loader.get_template('polls/index.html')

    # テンプレートで使う変数を設定
    context = {
        'latest_question_list': latest_question_list,
    }
    # HttpResponse を render で置き換え
    #return HttpResponse(template.render(context, request))
    return render(request, 'polls/index.html', context)

#(省略)

4.4. 404 エラーの送出

 リクエストしたIDを持つ質問が存在しない場合は404を返すように polls/views.py に Http404 も読み込み、 detail() を書き換えます。

from django.http import HttpResponse, Http404 #追加
from django.shortcuts import render

#(省略)

# 詳細 ページ
def detail(request, question_id):
    try:
        question = Question.objects.get(pk=question_id)
        # 主キーはそのカラム名に関わらず pk= で検索できる
    except Question.DoseNotExist:
        raise Http404('Question does not exist')
    return render(request, 'polls/detail.html', {'question': question})

#(省略)

 また、テンプレートディレクトリ polls/template/polls/ に detail.html を追加します。

{{ question }}

 ここまでで存在しないID http://127.0.0.1:8000/polls/4/ にアクセスすると以下のような画面が表示されます。

4.4.1. ショートカット: get_object_or_404()

 404にもショートカットが用意されています。
 polls/views.py の import に get_object_or_404 を追加し、 detail を書き換えます。

from django.http import HttpResponse # ショートカット get_object_or_404 読み込みにより Http404 が不要
from django.shortcuts import get_object_or_404, render

#(略)

# 詳細 ページ
def detail(request, question_id):
    # try:
    # 	question = Question.objects.get(pk=question_id)
    # 	# 主キーはそのカラム名に関わらず pk= で検索できる
    # except Question.DoseNotExist:
    # 	raise Http404('Question does not exist')
    # return render(request, 'polls/detail.html', {'question': question})

    question = get_object_or_404(Question, pk=question_id)
    return render(request, 'polls/detail.html', {'question': question})

#(省略)

 ここまでで存在しないIDにアクセスすると以下のような画面が表示されます。

4.5. テンプレートシステムを使う

 polls/detail.html をテンプレート変数を使って書き換えます。

<h1>{{ question.question_text }}</h1>
<ul>
{% for choice in question.choice_set.all %}
	<li>{{ choice.choice_text }}</li>
{% endfor %}
</ul>

 http://127.0.0.1:8000/polls/1/ にアクセスすると以下のような画面が表示されます。

4.6. テンプレート内のハードコードされたURLを削除

 polls/index.html のaタグを テンプレートタグ url を使って書き換えます。

{% url 'name' param %}

 このうち name は urls.py の path() で定義されている名前。parampath() に渡す引数です。これを使うとテンプレートは以下のように書き換えられます。

<li><a href="{% url 'detail' question.id %}/">{{ question.question_text }}</a></li>

 この中の 'detail' は polls/urls.py の中で name='detail' と定義されているものです。また、引数の question.id <int:question_id> として渡されます。

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

 たとえばこれを以下のように書き換えると詳細ページのURLが polls/specifics/1/ というふうに変わります。また、テンプレートタグ url を使っていればテンプレートを書き換えなくても自動的に新しいURLが適用されます。

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

4.7. URL 名の名前空間

 上の例だと polls/index.html のテンプレートタグ url は polls/urls.py を参照していますが、ほかのアプリに同じ name が存在したり、ほかのアプリを参照したい場合も出てくるかもしれません。そこで明示的に polls アプリを指定できるように polls/urls.py に app_name を追加します。

#(省略)

app_name = 'polls'
urlpatterns = [
    # ex: /polls/
    path('', views.index, name='index'),

#(省略)

 あわせて、polls/index.html のテンプレートタグ url も name 部分をただの detail から名前空間つきの polls:detail に書き換えます。

<li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>
Back to Top