[前回] Django+Reactで学ぶプログラミング基礎(6): Djangoチュートリアル(投票アプリその2)
はじめに
Django公式チュートリアル、その3です。
前回は、アプリを雛形レベルに仕上げました。
今回は、Djangoビューの仕様を深掘りします。
Djangoアプリ作成(その3): 投票(poll)アプリ
今回の内容
- ビューとURLのマッピング
- ビューのレスポンス
- テンプレートによるビューとデザインの分離
ビューとは
- Djangoアプリで、ウェブページとコンテンツはビューによって提供される
- 各ビューは単純にPython関数(クラスベースビューの場合はメソッド)として実装される
- ビューは、リクエストされたURL(URLのドメイン以降の部分)から決定される
- URLパターンは、URLを一般化したもの
- たとえば、
/newsarchive/<year>/<month>/
- たとえば、
- Djangoは
URLconf
を使用し、URLからビューを特定-
URLconf
は、URLパターンとビューのマッピング
-
- URLパターンは、URLを一般化したもの
- 投票アプリの4つのビュー
- 質問の索引ページ
- 最新の質問をいくつか表示
- 質問の詳細ページ
- 質問文と投票フォームを表示
- 質問の結果ページ
- 特定質問の結果を表示
- 投票ページ
- 特定質問の選択を投票として受付
- 質問の索引ページ
投票アプリにビューを追加
- ビューを追加
polls/views.py
def detail(request, question_id):
return HttpResponse("You're looking at question %s." % question_id)
def results(request, question_id):
response = "You're looking at the results of question %s."
return HttpResponse(response % question_id)
def vote(request, question_id):
return HttpResponse("You're voting on question %s." % question_id)
-
path()
コールを追加し、新しいビューをpolls.urls
モジュールと結びつける
polls/urls.py
from django.urls import path
from . import views
urlpatterns = [
# ex: /polls/
path('', views.index, name='index'),
# ex: /polls/5/
path('<int:question_id>/', views.detail, name='detail'),
# 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'),
]
ブラウザで、URLから該当ビューにアクセス
- VS Codeターミナルで、仮想環境をアクティベート
C:\kanban\pollsite>..\venv\.venv\Scripts\activate
- 開発サーバーを起動
(venv) C:\kanban\pollsite>python manage.py runserver
- ブラウザで
http://127.0.0.1:8000/polls/34/vote/
にアクセス
上記で追加された、投票ページ(ビュー)が表示される。
ビューのレスポンス
- 2種類のレスポンス
-
HttpResponse
オブジェクトを返却- リクエストされたページのコンテンツを含む
- 例外を返却
-
Http 404 Not Found
エラーなど
-
-
index
ビューを作成
- 最新5件の質問項目をカンマで区切り、日付順に表示するビュー
- 試しに書いたコード(問題あり)
polls/views.py
from django.http import HttpResponse
from .models import Question
def index(request):
# Questionモデル(テーブル)から、最新質問5件を取得
latest_question_list = Question.objects.order_by('-pub_date')[:5]
# 画面表示のため、質問をカンマ区切りで連結
output = ', '.join([q.question_text for q in latest_question_list])
# 結果をHTTPレスポンスで返す
return HttpResponse(output)
# 残りのビュー(detail, results, vote)はそのまま
- 上述コードの問題は?
- ビューにページのデザインまで含まれている
- デザインを、ビューから分離すべき
- ページの見栄えを変えるたびに
- 例えば、連結して表示ではなく、1質問につき1行表示など
- Pythonコードを編集する必要あるため
- ページの見栄えを変えるたびに
- 対処として、Djangoのテンプレートシステムを使用
- ビューから使用できるテンプレートを作成
テンプレートを作成
-
- プロジェクト設定ファイル
settings.py
のTEMPLATES
に設定される- DjangoTemplatesバックエンドが設定されている
-
APP_DIRS
オプションがTrue
- DjangoTemplatesはアプリそれぞれの
templates
サブディレクトリを検索
- DjangoTemplatesはアプリそれぞれの
- プロジェクト設定ファイル
-
テンプレートはどこに書く?
polls/templates/polls/index.html
- Django内で、テンプレートを単に
polls/index.html
のように参照できる
polls/templates/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 %}
-
index
ビューを更新し、上記テンプレートを使用するように- テンプレートをロードし、そこにコンテキストを渡す
- コンテキストは、テンプレート変数名をPythonオブジェクト(質問リスト)にマッピングする辞書
polls/views.py
from django.http import HttpResponse
from django.template import loader
from .models import Question
def index(request):
latest_question_list = Question.objects.order_by('-pub_date')[: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/
にアクセス
前回登録した質問事項から、最新5つが表示されます。
ショートカット: render()
- 以下複数処理を
render()
一発で- テンプレートをロード
- コンテキストに値を入れる
- テンプレートをレンダリングした結果を
HttpResponse
オブジェクトで返す
-
render
のみインポート-
loader
やHttpResponse
をインポート不要
-
polls/views.py
from django.shortcuts import render
from .models import Question
def index(request):
latest_question_list = Question.objects.order_by('-pub_date')[:5]
context = {'latest_question_list': latest_question_list}
return render(request, 'polls/index.html', context)
質問詳細のdetail()
ビューで、Http404エラーを返却
- 質問詳細ビュー
- 指定された投票の質問文を表示するビュー
- リクエストした
ID
を持つ質問が存在しないとき、Http404を返却
polls/views.py
from django.http import Http404
from django.shortcuts import render
from .models import Question
def detail(request, question_id):
try:
question = Question.objects.get(pk=question_id)
except Question.DoesNotExist:
raise Http404("Question does not exist")
return render(request, 'polls/detail.html', {'question': question})
# ...
- テンプレートを作成
polls/templates/polls/detail.html
{{ question }}
ショートカット: get_object_or_404()
- 以下処理を
get_object_or_404()
一発で-
get()
を実行 - オブジェクトが存在しない場合、Http404を返却
-
-
detail()
ビューを書き換え
polls/views.py
from django.shortcuts import get_object_or_404, render
from .models import Question
def detail(request, question_id):
question = get_object_or_404(Question, pk=question_id)
return render(request, 'polls/detail.html', {'question': question})
# ...
-
detail()
ビューのテンプレート作成
polls/detail.html
<h1>{{ question.question_text }}</h1>
<ul>
{% for choice in question.choice_set.all %}
<li>{{ choice.choice_text }}</li>
{% endfor %}
</ul>
テンプレート内のハードコードされたURLを削除
- ハードコードの問題
- プロジェクトにテンプレートが多数ある場合、URLの変更が困難
- 変更前: パスに
/polls
が含まれる
polls/index.html
<li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>
- 変更後: テンプレートタグの
{%url%}
を使用
polls/index.html
<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>
- 投票詳細ビューのURLを変更したい場合
- 対象となるテンプレートを変更せず、
polls/urls.py
を変更
- 対象となるテンプレートを変更せず、
polls/urls.py
...
# added the word 'specifics'
path('specifics/<int:question_id>/', views.detail, name='detail'),
...
URLの名前空間
- Djangoプロジェクトに複数アプリがある場合、それぞれのURLを区別したい
-
URLconf
にアプリケーションの名前空間を追加する
-
polls/urls.py
from django.urls import path
from . import views
app_name = 'polls'
urlpatterns = [
path('', views.index, name='index'),
path('<int:question_id>/', views.detail, name='detail'),
path('<int:question_id>/results/', views.results, name='results'),
path('<int:question_id>/vote/', views.vote, name='vote'),
]
- 名前空間つきの詳細ビューを指すように、テンプレートを変更
- 変更前
polls/templates/polls/index.html<li><a href="{% url 'detail' question.id %}">{{ question.question_text }}</a></li>
- 変更後
polls/templates/polls/index.html<li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>
おわりに
Djangoビューのレスポンスなど深掘りしました。
テンプレートによるビューとデザインの分離も理解しました。
次回も続きます。お楽しみに。