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

# 概要

[前回](http://qiita.com/gcfuji/items/27d2c9e4b3334447b6af)の続きです。
今回は公開用のインタフェース、ビューの作成を焦点に解説していきたいと思います。

# 作成するビュー

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

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

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

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

# ビューを作成する

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

```py3: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)の設定を行います。

```py3: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<question_id>の部分はマッチしたパターンを識別する為の名前を定義しています。
(ビュー関数のパラメータ名に当たる。)

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

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

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

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

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

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

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

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

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

```py3: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](https://qiita-image-store.s3.amazonaws.com/0/129216/c56b803c-4dfd-3040-83bf-1c577d64dcc2.png "スクリーンショット 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内で識別する事が可能になります。

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

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

```html: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 %}
```

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

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

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

です。

```py: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で返す、というイディオムは非常によく使われる為、ショートカットも存在します。

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

```py: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)を作成します。
ビューは以下の様になります。

```py3: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ページを作り込んでいきます。

```html: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関数を用いてハードコーディング部分を削除できます。

```html: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では名前衝突を回避する為に、以下の様な変数を定義します。

```py3: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も修正します。

```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](https://github.com/gcfuji/django_tutorial/tree/tutorail-3)

# シリーズ

- [Djangoを始めよう！ 〜チュートリアル①〜](http://qiita.com/gcfuji/items/bff55a0b8ae1b76c0ca1)
- [Djangoを始めよう！ 〜チュートリアル②〜](http://qiita.com/gcfuji/items/27d2c9e4b3334447b6af)
- [Djangoを始めよう！ 〜チュートリアル③〜](http://qiita.com/gcfuji/items/c8a0e4add7a628c12f8d)
- [Djangoを始めよう！ 〜チュートリアル④〜](http://qiita.com/gcfuji/items/68101227141cde134326)
- [Djangoを始めよう！ 〜チュートリアル⑤〜](http://qiita.com/gcfuji/items/1b6079bec064a4e8b211)
- [Djangoを始めよう！ 〜チュートリアル⑥〜](http://qiita.com/gcfuji/items/8560eade5a11c08a0ff1)