Help us understand the problem. What is going on with this article?

Djangoを始めよう! 〜チュートリアル③〜

More than 3 years have passed since last update.

概要

前回の続きです。
今回は公開用のインタフェース、ビューの作成を焦点に解説していきたいと思います。

作成するビュー

今回の投票アプリでは、以下の4ビューが必要です。

  • 質問のインデックスページ
    • 最新の質問がいくつか表示されている。
  • 質問の詳細ページ
    • 結果を表示せず、質問テキストと投稿フォームを表示する。
  • 質問の結果ページ
    • 特定の質問の結果を表示する。
  • 投票ページ
    • 特定の質問を投票として受付する。

Djangoでは、各ページのコンテンツはビューで提供されます。
各ビューは単純なPythonの関数です。
Djangoはビューを、リクエストされたURLから決定します。

DjangoはURLconfという考え方で、URLパターンとビューを紐付けます。

ビューを作成する

実際に、ビューを作成していきましょう。

mysite/polls/views.py
from django.shortcuts import render
from django.http import HttpResponse


def index(request):
    return HttpResponse('Hello World from Polls')


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)

単純な文字列のHTTPレスポンスを返すだけのビューです。

実際にルーティング(URLconf)の設定を行います。

mysite/polls/urls.py
from django.conf.urls import url

from . import views

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

今回も、正規表現でURLconfを設定します。

今回利用している正規表現の内容を簡単に解説します。

まず、^は「〜で始まる。」、$は「〜で終わる。」を表します。
正規表現内を()で囲むと、そのパターンにマッチしたテキストをキャプチャし、ビュー関数の引数として送信されます。

Pの部分はマッチしたパターンを識別する為の名前を定義しています。
(ビュー関数のパラメータ名に当たる。)

[0-9]+は1桁以上の数字にマッチする正規表現です。

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

各ビューには2つの役割があります。
1つはリクエストされたページのコンテンツを含むHttpResponseオブジェクトを返す事。
もう1つは、Http404の様な例外を検出する事です。

ビューはデータベースからレコードを読みだしても、読み出さなくてもどちらでも構いません。

Djangoのテンプレートシステム、あるいはサードパーティのPythonテンプレートシステムを利用しても構いません。

また、PDFファイルの生成、XML出力、ZIPファイルの生成などPythonライブラリを使ってやりたい事を何でも実現できます。

Djangoにとって必要なのはHttpResponseか、あるいは例外かのどちらかです。

indexビューを利用して、前回作成したデータベースを操作してみましょう。

以下のviews.pyを以下に修正します。

mysite/polls/views.py
from django.shortcuts import render
from django.http import HttpResponse
from .models import Question


def index(request):
    latest_question_list = Question.objects.order_by('-pub_date')[:5]
    output = ', '.join([q.question_text for q in latest_question_list])
    print(output)
    return HttpResponse(output)


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)

修正が終わって、ブラウザで確認すると以下の文字列が表示されるはずです。

スクリーンショット 2016-10-13 1.29.21.png

データベースの前回作成したquestionsテーブル(Questionモデル)からデータを引っ張ってきています。

しかし、このままでは、毎回デザインもビューごとに設定しなければなりません。

そこで、テンプレートファイルを作成してデザインを統一します。

まずは、pollsディレクトリ以下にtemplatesディレクトリを作成します。
Djangoはこのtemplates以下をテンプレートと見なしてテンプレートを探しにいきます。

ここから少しややこしい話なのですが、Djangoがどの様にテンプレートをロードし、レンダリングするかについて説明します。

デフォルトのDjangoTemplatesの設定ファイルは、APP_DIRSのオプションがTrueに設定されています。
設定により、DjangoTemplatesはINSTALLED_APPSのそれぞれのサブディレクトリのtemplatesを検索します。

先ほど作成したtemplatesディレクトリ内にpollsというディレクトリを作成し、その中にhtmlファイルを作成します。
(ローダーはpolls/index.htmlと認識できる様になる。)

要は、mysite/polls/templates/polls/index.htmlというパスになります。

こうする事で、名前空間の汚染を防ぎ、polls/index.htmlというテンプレートとして、Django内で識別する事が可能になります。

一種の決まり文句的な構造なので、少し違和感がありますが慣れましょう。

では、実際にテンプレートを制作します。

mysite/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 %}

ビューから渡ってきた変数(後述)の存在確認をし、存在すれば、ループで出力しています。

続いて、ビューの修正をします。
やることは、

  • どのテンプレートを利用するか
  • テンプレートに渡す変数の受け渡し

です。

mysite/polls/views.py
from django.shortcuts import render
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))

修正箇所は非常にシンプルです。

① loaderをインポート
② loader.get_templateでtemplateファイルの選定し、変数を返す
③ templateに渡す辞書型の変数を定義
④ ②で作成したテンプレート変数のrenderメソッドを利用し、HttpResponseを返す。

テンプレートをロードしてコンテキストに値を入れ、テンプレートをレンダリングした結果をHttpResponseで返す、というイディオムは非常によく使われる為、ショートカットも存在します。

以下がショートカットを使った例です。

mysite/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)

非常にすっきりしました。
この作業で、loaderやHttpResponseの読み込みも不要になります。

render()は第1引数にrequestオブジェクト、第2引数にテンプレート名、第3引数にその他のオプションを辞書型で受けます。

404エラーページの送出

指定された投票の質問文を表示するページの詳細ビュー(detail)を作成します。
ビューは以下の様になります。

mysite/polls/views.py
from django.shortcuts import render
from django.http import Http404

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)


def detail(request, question_id):
    try:
        question = Question.objects.get(pk=question_id)
        context = {
            'question': question
        }
    except Question.DoesNotExist:
        raise Http404('Question does not exist')
    return render(request, 'polls/detail.html', context)


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)

リクエストIDがない場合は、404ページに飛ぶ様になっています。

get()を実行し、オブジェクトが存在しない場合にはHttp404を送出することは非常によく使われるイディオムの為、こちらもDjangoがショートカットを用意してくれています。

以下の様になります。

404のテストができたので、detailページを作り込んでいきます。

mysite/polls/templates/polls/detail.html
<h1>{{ question.question.text }}</h1>
<ul>
    {% for choice in question.choice_set.all %}
        <li>{{ choice.choice_text }}</li>
    {% endfor %}
</ul>

ループの中のquestion.choice_set_allはquestionに紐付くchoiceを取得する為のメソッドです。

上記だけで、questionに紐付く、choiseを取得する事ができました。

テンプレート内のハードコーディングされたURLを削除

urls.pyでURL名(name)を指定していれば、html内でurl関数を用いてハードコーディング部分を削除できます。

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

URL名の名前空間

今はアプリがpollsのみですが、普通のプロジェクトでは、多くのアプリケーションで1つのプロジェクトを整形します。

その際にURL名がindexやdetailだと名前衝突を起こす可能性が高いです。

Djangoでは名前衝突を回避する為に、以下の様な変数を定義します。

mysite/polls/urls.py
from django.conf.urls import url

from . import views

app_name = 'polls'  # アプリ名を追加

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

併せて、index.htmlも修正します。

mysite/polls/templates/polls/index.html
{% if latest_question_list %}
    <ul>
        {% for question in latest_question_list %}
            <li><a href="{% url 'polls:detail' question.id %}">{{question.question_text}}</a></li>
        {% endfor %}
    </ul>
{% else %}
    <p>No polls are available.</p>
{% endif %}

これで名前衝突も防ぎながら、より直感的にミニマムな名前をつける事ができる様になりました。

まとめ

ここまででかなりアプリケーションの作成が出来る様になったと思います。

今回は、

  • ビューの作成
  • テンプレートの作成
  • テンプレートタグの使い方
  • Djangoが提供するショートカットの利用
  • 名前衝突回避方法

をやってきました。

次回は、フォーム関連の処理とコードの縮小化を解説したいと思います。

GitHub

シリーズ

tfrcm
React / ReactNative / Go / TypeScript / AWS / Docker / k8s
https://gemcook.com
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした