# Django 5.0.9.dev20240806133106 ドキュメント

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

このチュートリアルは チュートリアルその 2 の続きです。ここでは、引続き Web 投票アプリケーションの開発を例にして、公開用のインタフェース、ビュー(view) の作成を焦点に解説します。

困ったときは:

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

## オーバービュー

ビューとは、 Django のアプリケーションにおいて特定の機能を提供するウェブペー ジの「型 (type)」であり、各々のテンプレートを持っています。例えばブログアプリケーションなら、以下のようなビューがあるでしょう:

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

`question_id=34` の部分は `<int:question_id>` から来ています。 山括弧を使用すると、URLの一部が「キャプチャ」され、キーワード引数としてビュー関数に送信します。 文字列の `:question_id` 部分は、一致するパターンを識別するために使用される名前を定義し、`int` 部分は、URLパスのこの部分に一致するパターンを決定するコンバータです。コロン (`:`) はコンバータとパターン名を区切ります。

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

各ビューには二つの役割があります: 一つはリクエストされたページのコンテンツを含む `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 に進んで、簡単なフォームの処理と汎用ビューについて学びましょう。