# Django 4.1.8.dev20230308103347 ドキュメント

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

This tutorial begins where Tutorial 6 left off. We're continuing the web-poll application and will focus on customizing Django's automatically-generated admin site that we first explored in Tutorial 2.

困ったときは:

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

## admin フォームのカスタマイズ

`admin.site.register(Question)` の呼び出しによって `Question` モデルを登録したことで、Django はデフォルトの形式でオブジェクトを表示できました。 admin フォームの表示方法や操作の仕方をデフォルトから変更したいこともよくあります。それには、オブジェクトを登録する時にオプションを指定します。

ためしに、編集フォームでのフィールドの並び順を並べ替えてみましょう。 `admin.site.register(Question)` の行を以下のように置き換えてみてください。

`polls/admin.py`

    from django.contrib import admin

    from .models import Question

    class QuestionAdmin(admin.ModelAdmin):
        fields = ['pub_date', 'question_text']

    admin.site.register(Question, QuestionAdmin)

このように、モデルの admin のオプションを変更したいときには、モデルごとに admin クラスを作成して、 `admin.site.register()` の 2 番目の引数に渡すと いうパターンに従ってください。

上の例では、「Publication date」フィールドの表示位置を「Question」フィールドよりも前に変更しています:

二つしかフィールドがないので、あまりぱっとした変化ではありませんね。しかし admin フォームで数十ものフィールドを操作するような場合には、直感的なフィー ルドの並び順というものはユーザビリティ上重要な要素です。

また、数十ものフィールドがある場合、フォームを複数のフィールドセットに分割したいこともあるでしょう。

`polls/admin.py`

    from django.contrib import admin

    from .models import Question

    class QuestionAdmin(admin.ModelAdmin):
        fieldsets = [
            (None,               {'fields': ['question_text']}),
            ('Date information', {'fields': ['pub_date']}),
        ]

    admin.site.register(Question, QuestionAdmin)

`fieldsets` の各タプルの先頭の要素はフィールドセットのタイトルです。 フォームは以下のように表示されます。

## リレーションを張ったオブジェクトの追加

OK、 Question の管理ページはできました。しかし `Question` は複数の `Choice` を持つのに、管理ページには表示されていませんね。

現状はそうですが、表示はできます。

この問題を解決するには2つの方法があります。1つ目は、 `Question` と同じようにadminに `Choice` を登録する方法です:

`polls/admin.py`

    from django.contrib import admin

    from .models import Choice, Question
    # ...
    admin.site.register(Choice)

これで、 Django の管理サイト上で「Choice」 を選べます。「Choice の追加」フォーム は以下のようになります。

このフォームでは「Question」フィールドは選択ボックスで、データベース上の全ての質問を選択できます。Django は `ForeignKey` を表示する時には `<select>` ボックスを使わねばならないということを知っているのです。今の時点では、 Question はデータベース上に一つしかないはずですね。

Also note the "Add another question" link next to "Question." Every object with a `ForeignKey` relationship to another gets this for free. When you click "Add another question", you'll get a popup window with the "Add question" form. If you add a question in that window and click "Save", Django will save the question to the database and dynamically add it as the selected choice on the "Add choice" form you're looking at.

しかし、この方法は `Choice` オブジェクトをシステムに追加するには効率的ではありません。 `Question` オブジェクトを追加する時に Choice をひと揃い追加できた方が便利ですよね。そうしてみましょう。

`Choice` モデルに対する `register()` を削除して、 `Question` の登録する部分を以下のように書き換えてください。

`polls/admin.py`

    from django.contrib import admin

    from .models import Choice, Question

    class ChoiceInline(admin.StackedInline):
        model = Choice
        extra = 3

    class QuestionAdmin(admin.ModelAdmin):
        fieldsets = [
            (None,               {'fields': ['question_text']}),
            ('Date information', {'fields': ['pub_date'], 'classes': ['collapse']}),
        ]
        inlines = [ChoiceInline]

    admin.site.register(Question, QuestionAdmin)

この行は Django に対して、「`Choice` オブジェクトは `Question` の管理ページから編集する。デフォルトでは、 3 つの `Choice` を表示するのに十分なフィールドを用意すること」と指示しています。

「questionを追加 (Add question)」ページを読み込んで、どのように見えるか確かめてみましょう。

変わった点をみてみましょう。リレーション相手である `Choice` を表示するために 3 つのスロットがあります (`extra` に指定した数ですね)。また、作成済みのオブジェクトを「編集する」ページに行けば、いつでも 3 つ余分のスロットが表示されるはずです。

現在の3つのスロットの最後に「別の選択肢を追加」というリンクがあります。 これをクリックすると新しいスロットが追加されます。追加されたスロットを削除したい場合は、追加されたスロットの右上にある×をクリックしてください。この画像は追加されたスロットを示しています:

しかし、一つだけ小さな問題があります。関連する `Choice` オブジェクトを入力するためのすべてのフィールドを表示するには、多くの画面スペースを必要とします。そのため、Django は関連するオブジェクトをインラインで表示する表形式の方法を提供しています。それを使うには、`ChoiceInline` 宣言を読み替える:

`polls/admin.py`

    class ChoiceInline(admin.TabularInline):
        #...

`StackedInline` に代わって `TabularInline` を使うと、 リレーション相手のオブジェクトはコンパクトなテーブル形式で表示されます:

Note that there is an extra "Delete?" column that allows removing rows added using the "Add another Choice" button and rows that have already been saved.

## 管理サイトのチェンジリストページをカスタマイズする

さあ、これで Question の管理ページはだいぶよくなってきました。今度は「チェンジリスト」ページをすこしいじりましょう。チェンジリスト (change list) は、システム上の全ての Question を表示するページです。

現時点では以下のように表示されています。

デフォルトでは、 Django はオブジェクトの `str()` を表示します。しかし、オブジェクトの各フィールドの値を表示したい場合もあるでしょう。各フィールドの値を表示させるには、 `list_display` オプションを使います。このオプションには、カラム表示したいフィールドの名前をタプルにして指定します。

`polls/admin.py`

    class QuestionAdmin(admin.ModelAdmin):
        # ...
        list_display = ('question_text', 'pub_date')

念のため、 Tutorial 2 の was_published_recently() メソッドも含めましょう。

`polls/admin.py`

    class QuestionAdmin(admin.ModelAdmin):
        # ...
        list_display = ('question_text', 'pub_date', 'was_published_recently')

これで、Question のチェンジリストのページは以下のようになります:

カラムのヘッダをクリックすると、カラムの値に応じてエントリを並べ換えできます。ただし `was_published_recently` ヘッダは例外であり、これはメソッドの戻り値を使った並べ換えをサポートしていないからです。 `was_published_recently` のカラムヘッダは、デフォルトではメソッド名 (アンダースコアを空白に置き換えたもの) になっていることに注目してください。また、各行は戻り値の文字列表現となっています。

これを改善するには、`display()` デコレータを次のように (`polls/models.py` の) メソッドに使います:

`polls/models.py`

    from django.contrib import admin

    class Question(models.Model):
        # ...
        @admin.display(
            boolean=True,
            ordering='pub_date',
            description='Published recently?',
        )
        def was_published_recently(self):
            now = timezone.now()
            return now - datetime.timedelta(days=1) <= self.pub_date <= now

デコレータで設定できるプロパティに関する詳しい情報は、`list_display` を参照してください。

`polls/admin.py` ファイルをもう一度編集して、`Question` のチェンジリストのページに `list_filter` を追加して、さらに改良しましょう。それには、`QuestionAdmin` に次に行を追加します。

    list_filter = ['pub_date']

これで、「フィルタ (Filter)」サイドバーができ、チェンジリストを `pub_date` フィールドの値に従ってフィルタできるようになります。

フィルタの種類は、フィルタ対象のフィールドの種類に応じて変化します。`pub_date` は `DateTimeField` なので、Django はこのフィールドにふさわしいフィルタオプションが、「すべての期間 ("Any date")」「今日 ("Today")」「今週 ("Past 7 days")」「今月 ("This month")」であることを知っているのです。

いい感じに体裁が整ってきました。今度は検索機能を追加してみましょう。

    search_fields = ['question_text']

これでチェンジリストの上部に検索ボックスが表示されます。ユーザが検索語を入力すると、 Django は `question_text` フィールドを検索します。フィールドはいくらでも使えますが、舞台裏では `LIKE` クエリを使うのでデータベースに過剰な負荷をかけないために常識的な範囲にしましょう。

さて、ここでお知らせしたいこととして、チェンジリストには最初からページ分割機能があります。デフォルトではページあたり 100 個の要素を表示します。`ページ分割`、`検索ボックス`、`フィルタ`、`日付による階層化`、`カラムヘッダを使った並び替え` の機能は、すべて協調して思いのままに動作します。

## 管理サイトのルック & フィールをカスタマイズする

管理サイトの上部には「Django 管理 (Django adminstration)」と表示されていますが、これはいささか滑稽ですね。これは単なるプレースホルダテキストにすぎません。

ただし、Djangoのテンプレートシステムを使用して変更できます。 Django admin はDjango自体を利用しており、そのインターフェースはDjango独自のテンプレートシステムを使用しています。

### プロジェクト テンプレートをカスタムする。

`templates` ディレクトリをプロジェクトディレクトリ (`manage.py` が置かれているディレクトリ) に作成してください。テンプレートは Django がアクセスできるファイルシステム内のどこに置いても構いません (アプリケーションサーバーを実行したユーザーでアクセスします)。しかし従うべき慣習として、テンプレートはプロジェクト内に置きましょう。

設定ファイル (`mysite/settings.py`) を開いて、`TEMPLATES` 設定オプションの中に、次のように `DIRS` オプションを追加します。

`mysite/settings.py`

    TEMPLATES = [
        {
            'BACKEND': 'django.template.backends.django.DjangoTemplates',
            'DIRS': [BASE_DIR / 'templates'],
            'APP_DIRS': True,
            'OPTIONS': {
                'context_processors': [
                    'django.template.context_processors.debug',
                    'django.template.context_processors.request',
                    'django.contrib.auth.context_processors.auth',
                    'django.contrib.messages.context_processors.messages',
                ],
            },
        },
    ]

`DIRS` は、Django がテンプレートを読み込む時にチェックする、ファイルシステム上のディレクトリのリストです。サーチパスのようなものです。

テンプレートの構成

static ファイルと同じように、すべてのテンプレートを1つの大きなテンプレートディレクトリの中に置くことも 可能 ではあります。しかし、特定のアプリケーションに属するテンプレートは、プロジェクトのテンプレートディレクトリ(`templates`)ではなく、各アプリケーションのテンプレートディレクトリ(例: `polls/templates`)に配置するべきです。 reusable apps tutorial では、どうしてそのようにすべき 理由 を説明しています。

さて、`templates` の中に `admin` という名前のディレクトリを作りましょう。Django 自体のソースコード内にある、デフォルトの Django admin テンプレートディレクトリ (`django/contrib/admin/templates`) を探して、 `admin/base_site.html` というテンプレートを、新しく作ったディレクトリにコピーします。

Django のソースファイルの場所はどこ？

Django のソースファイルがシステム中のどこにあるのか分からない場合は、以下のコマンドを実行してください。

/ 

    $ python -c "import django; print(django.__path__)"

    ...\> py -c "import django; print(django.__path__)"

そして、ファイルを編集して `{{ site_header|default:_('Django administration') }}` を置き換えます。(中括弧を含む)に自分のサイト名を入れてください。最終的には、次のようなコードのセクションになるはずです:

    {% block branding %}
    <h1 id="site-name"><a href="{% url 'admin:index' %}">Polls Administration</a></h1>
    {% endblock %}

このようなアプローチを今使ったのは、テンプレートをオーバーライドする方法を示すためです。実際のプロジェクトで今行ったのと同じカスタマイズを行いたい場合には、 `django.contrib.admin.AdminSite.site_header` 属性を使えば、もっと簡単に設定できます。

このテンプレートファイルは `{% block branding %}` や `{{ title }}` のようなテキストを多く含んでいます。 `{%` と `{{` のタグは Django のテンプレート言語の一部です。 チュートリアル その3 で見たように、Djangoが `admin/base_site.html` をレンダリングする時に、このテンプレート言語が評価され、最終的なHTMLページが生成されます。

Django のデフォルトのadminテンプレートはすべてオーバーライドできることに注意してください。テンプレートを上書きするには、`base_site.html` で行ったのと同じことをしてください -- デフォルトのディレクトリからカスタムディレクトリにコピーして変更してください。

### アプリケーション用の テンプレートをカスタマイズする

するどい読者はこう質問されるでしょう: `DIRS` はデフォルトで空っぽなのに、 Django はなぜデフォルトの admin テンプレートを見つけることができたのだろう? その答えは `APP_DIRS` が `True` に設定されているため、 Django は自動的に各アプリケーションのパッケージのサブディレクトリからフォールバックとして `templates/` を探すからです (`django.contrib.admin` は一つのアプリケーションだということを忘れないでください)。

投票アプリケーションはそれほど複雑ではないので、カスタムの admin テンプレートは必要ないでしょう。しかしアプリケーションがさらに凝ったものに成長し、何か機能を実現するために標準の admin テンプレートを変更する必要が出てきた場合、 アプリケーションの テンプレートを編集するほうが、 プロジェクト のテンプレートを編集するより賢い選択になるでしょう。そうすれば、投票アプリケーションを他のどの新しいプロジェクトにも追加でき、投票アプリケーションが（自分自身に必要な）カスタムテンプレートを確実に見つけることができます。

Django のテンプレートの検索方法に関する詳しい情報は、テンプレート読み込み ドキュメント を参照してください。

## admin index ページをカスタムする

近い話題として、 Django の admin index ページのルックアンドフィールをカスタマイズもできます。

デフォルトでは、 admin アプリケーションで登録された `INSTALLED_APPS` のすべてのアプリケーションが、アルファベット順に表示されます。レイアウトを大きく変更したい場合もあるでしょう。なんといっても、index ページは admin の中でおそらく最も重要なページなので、使いやすいほうがいいですからね。

カスタマイズ対象のテンプレートは `admin/index.html` です。 (前セクションで `admin/base_site.html` にしたことと同じことをしてください。つまり、デフォルトのディレクトリからコピーして、カスタムテンプレートのディレクトリに配置してください)。編集してみると `app_list` というテンプレート変数が使われているのがわかるでしょう。この変数にはインストールされた全 Django アプリが含まれています。これを使う代わりに、各オブジェクトの admin ページヘのリンクをハードコードし、好きなようにカスタマイズできます。

## 次は何をしましょうか？

これで、初めての人のためのチュートリアルはおしまいです。次のステップへ では、ここから進める場所について説明しています。

Python のパッケージングについて知っていて、polls アプリケーションを「再利用可能なアプリケーション」へと変える方法について学びたい場合は、チュートリアル 応用編: 再利用可能なアプリケーションの書き方 を読んでみてください。