# Django 4.1.8.dev20230308103347 ドキュメント

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

This tutorial begins where Tutorial 2 left off. We're continuing the web-poll application and will focus on creating the public interface -- "views."

困ったときは:

このチュートリアルの実行に問題がある場合は、FAQ の Getting Help セクションに進んでください。

## オーバービュー

A view is a "type" of web page in your Django application that generally serves a specific function and has a specific template. For example, in a blog application, you might have the following views:

- Blog ホームページ - 最新エントリーをいくつか表示
- エントリー詳細ページ - 1エントリーへのパーマリンク (permalink) ページ
- 年ごとのアーカイブページ - 指定された年のエントリーの月を全て表示
- 月ごとのアーカイブページ - 指定された月のエントリーの日をすべて表示
- 日ごとのアーカイブページ - 指定された日の全てのエントリーを表示
- コメント投稿 - エントリーに対するコメントの投稿を受付

投票アプリケーションでは、以下4つのビューを作成します:

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

Django では、ウェブページとコンテンツはビューによって提供されます。各ビューは単純に Python 関数 (クラスベースビューの場合はメソッド) として実装されています。 Django はビューを、リクエストされた URL (正確には、URLのドメイン以降の部分) から決定します。

今、あなたはウェブ上で `ME2/Sites/dirmod.htm?sid=&type=gen&mod=Core+Pages&gid=A6CD4967199A42D9B65B1B` のような美しいものに出くわしたことがあるかもしれません。Django はそれよりもはるかにエレガントな URL パターン を提供してくれます。

URLパターンは、URLを単に一般化したものです。たとえば、 `/newsarchive/<year>/<month>/` などです。

URLからビューを得るために、Django は「URLconf」と呼ばれているものを使います。URLconf はURLパターンをビューにマッピングします。

このチュートリアルでは URLconf の基本的な使い方を知ってもらいます。より詳しくは URL ディスパッチャ を参照してください。

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

それではもう少しviewを `polls/views.py` に追加していきましょう。これから追加するviewでは引数をとります。

`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()` コールを追加して、新しいviewを `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'),
    ]

お使いのブラウザで、 "/polls/34/" を見てください。 `detail()` メソッドが実行され、URLで提供したIDが表示されます。 "/polls/34/results/" と "/polls/34/vote/" も試してください。結果と投票ページのプレースホルダがそれぞれ表示されます。

誰かがWebサイトの 「/polls/34/」 をリクエストすると、 Django は `ROOT_URLCONF` に指定されている Python モジュール `mysite.urls` をロードします。そのモジュール内の `urlpatterns` という変数を探し、順番にパターンを検査していきます。 `polls/` にマッチした箇所を見つけた後、一致した文字列 (`"polls/"`) を取り除き、残りの文字列である "34/" を次の処理のために 『polls.urls』 の URLconf に渡します。これは `'<int：question_id>/'` に一致し、結果として下記のように `detail()` が呼び出されます。

    detail(request=<HttpRequest object>, question_id=34)

The `question_id=34` part comes from `<int:question_id>`. Using angle brackets "captures" part of the URL and sends it as a keyword argument to the view function. The `question_id` part of the string defines the name that will be used to identify the matched pattern, and the `int` part is a converter that determines what patterns should match this part of the URL path. The colon (`:`) separates the converter and pattern name.

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

各ビューには二つの役割があります: 一つはリクエストされたページのコンテンツを含む `HttpResponse` オブジェクトを返すこと、もう一つは `Http404` のような例外の送出です。それ以外の処理はユーザ次第です。

ビューはデータベースからレコードを読みだしても、読み出さなくてもかまいません。 Django のテンプレートシステム、あるいはサードパーティの Python テンプ レートシステムを使ってもよいですし、使わなくてもかまいません。 PDF ファイルを生成しても、 XML を出力しても、 ZIP ファイルをその場で生成してもかまいません。 Python ライブラリを使ってやりたいことを何でも実現できます。

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

簡単のため、 チュートリアルその 2 で解説した Django のデータベース API を使ってみましょう。試しに次のような `index()` ビューを作ります。これは、システム上にある最新の 5 件の質問項目をカンマで区切り、日付順に表示するビューです:

`polls/views.py`

    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])
        return HttpResponse(output)

    # Leave the rest of the views (detail, results, vote) unchanged

このコードには問題があります。ビューの中で、ページのデザインがハードコードされています。ページの見栄えを変更するたびに、 Python コードを編集する必要があります。 Django のテンプレートシステムを使って、ビューから使用できるテンプレートを作成し、Python からデザインを分離しましょう。

最初に、 `polls` ディレクトリの中に、 `templates` ディレクトリを作成します。 Django はそこからテンプレートを探します。

プロジェクトの `TEMPLATES` には、Django がどのようにテンプレートをロードしレンダリングするかが書かれています。デフォルトの設定ファイルでは、 `DjangoTemplates` バックエンドが設定されており、その `APP_DIRS` のオプションが `True` になっています。規約により、 `DjangoTemplates` は `INSTALLED_APPS` のそれぞれの "templates" サブディレクトリを検索します。

先ほど作成した `templates` ディレクトリ内で `polls` というディレクトリを作成し、さらにその中に `index.html` というファイルを作成してください。つまり、テンプレートは `polls/templates/polls/index.html` に書く必要があります。 `app_directories` テンプレートローダは前述のように動くため、Django 内でこのテンプレートを単に `polls/index.html` のように参照できます。

テンプレートの名前空間

作ったテンプレートを (`polls` という別のサブディレクトリを作らずに) 直接 `polls/templates` の中に置いてもいいのではないか、と思うかもしれませんね。しかし、それは実際には悪い考えです。Django は、名前がマッチした最初のテンプレートを使用するので、もし 異なる アプリケーションの中に同じ名前のテンプレートがあった場合、Django はそれらを区別することができません。そのため、Django に正しいテンプレートを教えてあげる必要がありますが、一番簡単な方法は、それらに 名前空間を与える ことです。アプリケーションと同じ名前をつけた もう一つの ディレクトリの中にテンプレートを置いたのは、そういうわけなのです。

テンプレートには次のコードを書きます:

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

注釈

チュートリアルを短くするために、すべてのテンプレートの例では不完全なHTMLを使用しています。独自のプロジェクトでは 完全な HTML ドキュメント を使用する必要があります。

このテンプレートを使用するために `polls/views.py` の `index` ビューを更新してみましょう:

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

このコードは、 `polls/index.html` というテンプレートをロードし、そこにコンテキストを渡します。コンテキストは、テンプレート変数名を Python オブジェクトにマッピングする辞書です。

ブラウザで "/polls/" を開くと、箇条書きのリストが表示されるはずです（リストには、 チュートリアルその2 で作った "What's up" という質問が入っていますね）。リンクは質問の詳細ページを指します。

### ショートカット: `render()`

テンプレートをロードしてコンテキストに値を入れ、テンプレートをレンダリングした結果を `HttpResponse` オブジェクトで返す、というイディオムは非常によく使われます。 Django はこのためのショートカットを提供します。これを使って `index()` ビューを書き換えてみましょう:

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

全部の view をこのように書き換えてしまえば、 `loader` や `HttpResponse` を import する必要はなくなります (`detail` 、 `results` 、 `vote` を引き続きスタブメソッドにするなら、 `HttpResponse` はそのままにしておいたほうがいいでしょう)。

`render()` 関数は、第1引数として request オブジェクトを、第2引数としてテンプレート名を、第3引数（任意）として辞書を受け取ります。この関数はテンプレートを指定のコンテキストでレンダリングし、その `HttpResponse` オブジェクトを返します。

## 404 エラーの送出

さて、質問詳細ビューに取り組みましょう。このページは、指定された投票の質問文を表示するものです。ビューは次のようになります:

`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})

新しい概念が出てきました。このビューは、リクエストした ID を持つ質問が存在しないときに `Http404` を送出します。

`polls/detail.html` テンプレートに何を書けばよいかは後で解説しますが、さしあたって上の例題を動かしたければ、単に:

`polls/templates/polls/detail.html`

    {{ question }}

と書いておいてください。

### ショートカット: `get_object_or_404()`

`get()` を実行し、オブジェクトが存在しない場合には `Http404` を送出することは非常によく使われるイディオムです。 Django はこのためのショートカットを提供しています。ショートカットを使って、 `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})

`get_object_or_404()` 関数は、Django モデルを第一引数に、任意の数のキーワード引数を取り、モデルのマネージャの `get()` 関数に渡します。オブジェクトが存在しない場合は `Http404` を発生させます。

設計思想

なぜ `ObjectDoesNotExist` 例外を高水準で自動的にキャッチせず、ヘルパー関数 `get_object_or_404()` を使うのでしょうか、また、なぜモデル API に `ObjectDoesNotExist` ではなく、 `Http404` を送出させるのでしょうか?

答えは、モデルレイヤとビューレイヤをカップリングしてしまうからです。 Django の最も大きな目標の一つは、ルーズカップリングの維持にあります。いくつかの制御カップリングは、 `django.shortcuts` モジュールの中にあります。

`get_list_or_404()` という関数もあります。この関数は `get_object_or_404()` と同じように動きますが、 `get()` ではなく、 `filter()` を使います。リストが空の場合は `Http404` を送出します。

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

投票アプリの `detail()` ビューに戻りましょう。コンテキスト変数を `question` とすると、 `polls/detail.html` テンプレートは次のようになります:

`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.question_text }}` を例にすると、はじめに Django は `question` オブジェクトに辞書検索を行います。それに失敗したら、今度は属性として検索を行い、このケースの場合は成功します。もし属性の検索に失敗すると、リストインデックスでの検索を行います。

メソッドの呼び出しは `{% for %}` ループの中で行われています。 `question.choice_set.all` は、 Python コードの `question.choice_set.all()` と解釈されます。その結果、`Choice` オブジェクトからなるイテレーション可能オブジェクトを返し、 `{% for %}` タグで使えるようになります。

テンプレートの詳しい動作は テンプレートガイド を参照してください。

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

`polls/index.html` テンプレートで質問へのリンクを書いたとき、リンクの一部は次のようにハードコードされていました:

    <li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>

このハードコードされた、密結合のアプローチの問題は、プロジェクトにテンプレートが多数ある場合、URLの変更が困難になってしまうことです。しかし、 `polls.urls` モジュール の `path()` 関数で name 引数を定義したので、テンプレートタグの `{％url％}` を使用して、URL 設定で定義されている特定の URL パスへの依存をなくすことができます:

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

これが機能するのは、 `polls.urls` モジュールに指定されたURLの定義を検索するからです。 'detail' のURL名は以下の箇所で定義されています:

    ...
    # the 'name' value as called by the {% url %} template tag
    path('<int:question_id>/', views.detail, name='detail'),
    ...

投票の詳細ビューの URL を何か他のものに変更したい場合、たとえば `polls/specifics/12/` のように変更したいとき、対象となるテンプレートを変更する代わりに、 `polls/urls.py` を変更します:

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

## URL 名の名前空間

このチュートリアルプロジェクトが持つアプリは `polls` アプリ1つだけです。実際の Django プロジェクトでは、5個、10個、20個、あるいはそれ以上のアプリがあるかもしれません。 Django はどうやってこれらの間の URL 名を区別するのでしょうか？ 例えば、 `polls` アプリは `detail` ビューを含みますが、同じプロジェクトにブログのためのアプリがあり、そのアプリも同名のビューを含むかもしれません。 `{% url %}` テンプレートタグを使ったとき、 Django はどのアプリのビューに対して url を作成すればいいでしょうか？ これを Django にどう知らせればいいでしょうか。

答えは、 URLconf に名前空間を追加すること、です。どうぞ `polls/urls.py` ファイル内で `app_name` を追加し、アプリケーションの名前空間を設定してください。

`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/index.html` テンプレートを変更します。以下の形から、

`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>

ビューを書けるようになったら、 チュートリアルその4 に進んで、簡単なフォームの処理と汎用ビューについて学びましょう。