---
title: "はじめての Django アプリ作成、その 7"
version: 2.1
locale: ja
source: https://docs.djangoproject.com/ja/2.1/intro/tutorial07/
canonical: https://djangodocs.dev/ja/2.1/intro/tutorial07/
---
# はじめての Django アプリ作成、その 7

このチュートリアルは [チュートリアル その6](/ja/2.1/intro/tutorial06/) の続きです。ここでは、引き続き Web 投票アプリケーションの開発を続け、 [チュートリアル その2](/ja/2.1/intro/tutorial02/) で少し触れた、Django が 自動生成する管理サイトのカスタマイズに焦点を当てます。

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

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

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

*polls/admin.py*

```python
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」フィールドよりも前に変更しています:

![Fields have been reordered](intro/_images/admin07.png)

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

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

*polls/admin.py*

```python
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`](/ja/2.1/ref/contrib/admin/#django.contrib.admin.ModelAdmin.fieldsets) の各タプルの先頭の要素はフィールドセットのタイトルです。 フォームは以下のように表示されます。

![Form has fieldsets now](intro/_images/admin08t.png)

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

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

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

この問題の解決法は二つあります。一つ目は、 `Question` と同様、以下のようにして `Choice` モデルを管理サイトに登録するというものです。これは簡単です。

*polls/admin.py*

```python
from django.contrib import admin

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

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

![Choice admin page](intro/_images/admin09.png)

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

Question フィールドの隣に「もう一つ追加 (Add Another)」リンクがあるのに注意してください。 `ForeignKey` の関係にあるオブジェクトなら、何もしなくてもこのリンクが表示されます。「もう一つ追加」をクリックすると、「Poll を追加 (Add poll)」というポップアップウィンドウを表示します。このウィンドウで Poll を追加して「保存」を押すと、 Django は Poll をデータベースに保存して、もとの 「Choice の追加」フォームに選択済みの項目として動的に追加します。

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

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

*polls/admin.py*

```python
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)」ページを読み込んで、どのように見えるか確かめてみましょう。

![Add question page now has choices on it](intro/_images/admin10t.png)

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

現状ある3つのスロットの末尾に、「他の Choice を追加する (Add another Choice)」リンクがあります。クリックすると、新しいスロットが追加されます。追加されたスロットを削除したい場合は、追加されたスロット右上の 'X' をクリックしてください。最初からある 3 つのスロットを削除できないことに注意してください。この画像は、追加されたスロットを示すものです（訳注： 画像の一番下の Choice が「追加されたスロット」）。

![Additional slot added dynamically](intro/_images/admin14t.png)

さて、このコードにはちょっと問題があります。というのも、 `Choice` オブジェク トを入力するためのフィールドを全部表示しようとすると、相当な広さのスクリーンが必要だからです。このため、 Django にはテーブルを使ってインラインでリレーション相手のオブジェクトを表示する方法があります。以下のように、 `ChoiceInline` の宣言を変更してください。

*polls/admin.py*

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

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

![Add question page now has more compact choices](intro/_images/admin11t.png)

追加の "Delete?" カラムがあることに注意してください。このカラムは、 "Added Another Choice" ボタンで追加された行や、すでに保存された行を削除するためにあります。

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

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

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

![Polls change list page](intro/_images/admin04t.png)

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

*polls/admin.py*

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

おまけとして、[チュートリアル その2](/ja/2.1/intro/tutorial02/) で定義したカスタムメソッド `was_published_recently()` も追加してみましょう。

*polls/admin.py*

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

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

![Polls change list page, updated](intro/_images/admin12t.png)

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

これを改善するには、このメソッド (`polls/models.py` にある) に次のようにいくつかの属性を追加します。

*polls/models.py*

```python
class Question(models.Model):
    # ...
    def was_published_recently(self):
        now = timezone.now()
        return now - datetime.timedelta(days=1) <= self.pub_date <= now
    was_published_recently.admin_order_field = 'pub_date'
    was_published_recently.boolean = True
    was_published_recently.short_description = 'Published recently?'
```

このメソッドのプロパティに関する詳しい情報は、 [`list_display`](/ja/2.1/ref/contrib/admin/#django.contrib.admin.ModelAdmin.list_display) を参照してください。

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

```
list_filter = ['pub_date']
```

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

![Polls change list page, updated](intro/_images/admin13t.png)

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

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

```
search_fields = ['question_text']
```

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

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

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

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

変更するのは簡単で、 Django のテンプレートシステムを使います。 Django の管理サイトは、それ自身 Django で作られているので、インタフェースは Django のテンプレートシステムを使っています。

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

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

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

*mysite/settings.py*

```python
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(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`](/ja/2.1/ref/settings/#std-setting-TEMPLATES-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 のソースファイルがシステム中のどこにあるのか分からない場合は、以下のコマンドを実行してください。
>
> ```console
> $ python -c "import django; print(django.__path__)"
> ```
>
> *Windows*
>
> ```doscon
> ...\> py -c "import django; print(django.__path__)"
> ```

あとは、ファイルを編集して、`{{ site_header|default:_('Django administration') }}` (カーリーブラケットも含みます) を適当な自分自身のサイト名に書き換えるだけです。コードは最終的に次のようになります。

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

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

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

Django のデフォルトの admin 用テンプレートはオーバーライド可能です。オーバーライドするには、 `base_site.html` にしたことと同じことをするだけです。つまり、デフォルトのディレクトリから自分のカスタムディレクトリにコピーして、変更してください。

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

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

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

Django のテンプレートの検索方法に関する詳しい情報は、[テンプレート読み込み ドキュメント](/ja/2.1/topics/templates/#template-loading) を参照してください。

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

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

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

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

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

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

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