LoginSignup
5
1

More than 1 year has passed since last update.

Django+Reactで学ぶプログラミング基礎(7): Djangoチュートリアル(投票アプリその3)

Last updated at Posted at 2022-06-06
[前回] 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パターンとビューのマッピング
  • 投票アプリの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/にアクセス

上記で追加された、投票ページ(ビュー)が表示される。

image.png

ビューのレスポンス

  • 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のテンプレートシステムを使用
      • ビューから使用できるテンプレートを作成

テンプレートを作成

  • Djangoはどのようにテンプレートを探してロードする?
    image.png

    • プロジェクト設定ファイルsettings.pyTEMPLATESに設定される
      • DjangoTemplatesバックエンドが設定されている
      • APP_DIRSオプションがTrue
        • DjangoTemplatesはアプリそれぞれのtemplatesサブディレクトリを検索
  • テンプレートはどこに書く?

    • 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 %}

image.png

  • 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))

image.png

  • ブラウザでhttp://127.0.0.1:8000/polls/にアクセス

image.png

前回登録した質問事項から、最新5つが表示されます。

ショートカット: render()

  • 以下複数処理をrender()一発で
    • テンプレートをロード
    • コンテキストに値を入れる
    • テンプレートをレンダリングした結果をHttpResponseオブジェクトで返す
  • renderのみインポート
    • loaderHttpResponseをインポート不要
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)

image.png

質問詳細の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 }}

image.png

ショートカット: 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'),
...

image.png

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ビューのレスポンスなど深掘りしました。
テンプレートによるビューとデザインの分離も理解しました。
次回も続きます。お楽しみに。

[次回] Django+Reactで学ぶプログラミング基礎(8): Djangoチュートリアル(投票アプリその4-1)
5
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
1