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

revision-up-to:17812 (1.4)

このチュートリアルは チュートリアルその 1 の続き です。ここでは、引続き Web 投票アプリケーションの開発を例にして、Django が 自動生成する管理サイト (admin) を中心に解説します。

設計哲学

コンテンツの追加や変更、削除を行うためのスタッフや顧客向けの管理サイト 構築は、創造性の欠けた退屈なものです。そこで、 Django ではモデルを管 理するためのインタフェース生成を完全に自動化しています。

Django はニュースルーム環境で開発されました。ニュースルーム環境では、 「コンテンツの作成者 (content publisher) 用」と「公開用 (public) 」サイ トをきわめて明確に区別しています。サイト管理者は新たな話題やイベント、 スポーツのスコアなどの入力に使い、コンテンツは公開用サイト上で表示され ます。 Django は、サイト管理者向けの一元化されたコンテンツ編集インタフェー スの提供という問題を解決しているのです。

admin は一般のサイト訪問者に使ってもらうためのものではなく、サイト管理 者のためのものです。

管理サイトの有効化

デフォルトでは、 Django の管理サイトは無効化されていて、自分で選択して有効 にせねばなりません。 admin を有効にするには、以下の 3 つの作業が必要です:

  • INSTALLED_APPS 設定の "django.contrib.admin" をコメント アウトを解除します。

  • python manage.py syncdb を実行します。新たなアプリケーションを INSTALLED_APPS に追加したので、データベースを更新せねばな りません。

  • mysite/urls.py ファイルを編集し、 admin に関連する3行のコメントアウト を解除します。このファイルは URLconf といいます。 URLconf についてはチュー トリアルの次の部で解説します。今はただ、この設定が URL をアプリケーション に対応づけていることだけを覚えておきましょう。最終的に、 urls.py は 以下のようになるはずです:

    from django.conf.urls import patterns, include, url
    
    # Uncomment the next two lines to enable the admin:
    from django.contrib import admin
    admin.autodiscover()
    
    urlpatterns = patterns('',
        # Examples:
        # url(r'^$', '{{ project_name }}.views.home', name='home'),
        # url(r'^{{ project_name }}/', include('{{ project_name }}.foo.urls')),
    
        # Uncomment the admin/doc line below to enable admin documentation:
        # url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
    
        # Uncomment the next line to enable the admin:
        url(r'^admin/', include(admin.site.urls)),
    )
    

    (太字の行はコメントを外した部分です)

開発サーバの起動

開発用サーバを起動して、管理サイトを探検してみましょう。

チュートリアルその 1 で、開発サーバを以下のように起動したのを思い出してくだ さい:

python manage.py runserver

次はブラウザを起動して、ローカルドメインの “/admin/” 、つまり http://127.0.0.1:8000/admin/ にアクセスします。以下のような admin のログイ ン画面が表示されるはずです:

Django 管理サイトのログイン画面

自分の画面と違う場合

もしこの時点で、上のログイン画面の代わりに以下のようなエラーの画面が 表示された場合は:

ImportError at /admin/
cannot import name patterns
...

おそらくこのチュートリアルのバージョンとは違う Django のバージョンを 使用していることでしょう。古いチュートリアルを参照するか、新しいバー ジョンの Django を入手してください。

管理サイトに入る

さあログインしてみましょう。(チュートリアルその 1 で、スーパユーザのアカウ ントを作成したはずです。覚えていますか?) ログインしたら、管理サイトのインデ クスページが表示されるはずです:

Django 管理サイトのインデクスページ

「グループ (Groups)」や「ユーザ (Users)」、「サイト (Sites)」といった編集 可能なコンテンツが表示されるはずです。これらはデフォルトで Django に付属し ているコアの機能です。

Poll モデルを admin 上で編集できるようにする

ところで、 polls アプリケーションはどこにあるんでしょう? admin のインデ クスページを見ても表示されていませんね。

実は、まだ一つやるべきことが残っていました。 admin に Poll モデルがイ ンタフェースを持つことを教える必要があるのです。 polls ディレクトリに admin.py ファイルを追加して、以下のように編集してください:

from polls.models import Poll
from django.contrib import admin

admin.site.register(Poll)

admin のページをリロードして、どんな変化が起きたか見てみましょう。通常、 開発サーバはプロジェクトを自動的にリロードしてくれるので、コードに加えた変 更はただちにブラウザで確認できます。

admin の機能を探究してみる

Poll モデルを登録したので、 Django は Poll を管理サイトに表示するよ うになりました:

Django 管理サイトに Poll が表示されるようになった

「Polls」 をクリックしてみてください。 チェンジリスト (change list) のペー ジに入ります。このページはデータベース上の全ての Poll オブジェクトを表 示していて、変更したいオブジェクトを選択できます。前のチュートリアルで作成 した 「What’s up」という Poll オブジェクトがありますね。

Poll のチェンジリストページ

「What’s up?」をクリックして編集してみましょう:

Poll オブジェクトの編集

以下の点に注意してください:

  • フォームは Poll モデルをもとに自動的に生成されています。
  • モデルのフィールドの型によって ( DateTimeField, CharField などの) 適切な HTML 入力ウィジェッ トが対応しています。各タイプのフィールドには、 Django 管理サイトでデー タを表示する方法が定義されています。
  • DateTimeField には JavaScript のショートカッ トがついています。日付 (Date) のカラムには「今日 (Today)」へのショート カットと、カレンダーのポップアップボタンがあります。時刻 (Time) のカラ ムには「現在 (Now)」へのショートカットと、よく入力される時間のリストを 表示するポップアップボタンがあります。

ページの末尾の部分には操作ボタンがいくつか表示されています:

  • 保存 (Save) – 変更を保存して、このモデルの変更リストのページに戻ります。
  • 保存して編集を続ける (Save and continue editing) – 変更を保存して、こ のオブジェクトの編集ページをリロードします。
  • 保存してもう一つ追加 (Save and add another) – 変更を保存して、このモデ ルのオブジェクトを新規追加するための空の編集ページをロードします。
  • 削除 (Delete) – 削除確認ページを表示します。

もし「Date published」の値がチュートリアル 1 で作成した時間と違う場合は、 TIME_SONE に現在のタイムゾーンの設定をし忘れた可能性があります。 変更し、リロードして正しい値が表示されるか確認してください。

「今日」や「現在」ショートカットをクリックして、「Date published」を変更し てみましょう。変更したら、「保存して編集を続ける」を押します。次に、右上に ある「履歴 (History)」をクリックしてみましょう。ユーザが管理サイト上でオブ ジェクトに対して行った全ての変更履歴を、変更時刻と変更を行ったユーザの名前 つきでリストにしたページを表示します:

Poll オブジェクトの変更履歴

管理サイトフォームのカスタマイズ

しばらく操作してみましょう。これだけの機能をコードを書かずに実現したこ とに驚くはずです。 admin.site.register(Poll) の呼び出しによって、 Django はオブジェクトの表示方法を「推測」し、管理サイトでモデルの編集を行え るようにします。管理サイトの表示や挙動を少し変えたい場合には、モデルを登録 するときにオプションを指定します。

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

class PollAdmin(admin.ModelAdmin):
    fields = ['pub_date', 'question']

admin.site.register(Poll, PollAdmin)

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

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

フィールドの並び順を変えた場合

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

同じく何ダースもフィールドがある場合、フォームを複数のフィールドセットに分 割したいこともあるでしょう:

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

admin.site.register(Poll, PollAdmin)

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

フィールドセットでフォームを表示

各フィールドセットには任意の HTML クラスを指定できます。 Django では "collapse" というクラスを提供していますが、このクラスを指定すると、フィー ルドセットは最初折り畳まれた状態で表示されます。これは普段は使わないフィー ルドがたくさんあるようなフォームを使っている場合に便利です:

class PollAdmin(admin.ModelAdmin):
    fieldsets = [
        (None,               {'fields': ['question']}),
        ('Date information', {'fields': ['pub_date'], 'classes': ['collapse']}),
    ]
フィールドセットが最初は折畳みで表示されている

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

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

今のところは。

この問題の解決法は二つあります。一つ目は、 Poll と同様、以下のようにし

Choice モデルを管理サイトに登録するというものです:

from polls.models import Choice

admin.site.register(Choice)

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

管理サイトでの Choice の表示

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

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

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

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

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

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

admin.site.register(Poll, PollAdmin)

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

「Poll を追加」ページをロードして、どんな表示になったか見てみましょう。開発 サーバをリスタートする必要があるかもしれません:

「Poll を追加」ページに選択肢が表示された

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

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

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

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

「Poll を追加」ページの選択肢がコンパクトになった

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

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

作業前のチェンジリストのページは以下のように表示されています:

Poll のチェンジリストページ

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

class PollAdmin(admin.ModelAdmin):
    # ...
    list_display = ('question', 'pub_date')

おまけとして、チュートリアル 1 で定義したカスタムメソッド was_published_recently も追加してみましょう:

class PollAdmin(admin.ModelAdmin):
    # ...
    list_display = ('question', 'pub_date', 'was_published_recently')

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

カスタムメソッドのカラムを追加した Poll のチェンジリスト

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

以下の属性を (models.py の中の) メソッドに与えることで、これを改良出来ま す:

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

admin.py ファイルを編集し、もう一点、 Poll のチェンジリストを改良して、フィル タをを加えます。以下の行を PollAdmin の中に入れます:

list_filter = ['pub_date']

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

Poll のチェンジリストをさらに改良

表示されるフィルタのタイプは、フィルタに使うフィールドのタイプによって変わ ります。 pub_dateDateTimeField なので、 Django はデフォルトのフィルタのオプションとして 「すべての日 (Any date)」、「今日 (Today)」、「過去 7 日間 (Past 7 days)」、 「今月 (This month)」そして「今年 (This year)」を与えればよいと考えます。

細工は隆々ですね。こんどは検索機能を追加してみましょう:

search_fields = ['question']

これで変更リストの上部に検索ボックスが表示されます。ユーザが検索語を入力 すると、 Django は question フィールドを検索します。フィールドはいくつ でも使えますが、舞台裏では LIKE クエリを使っているので、データベースに 負荷をかけないためには常識的な検索語を指定しましょう。

最後に、 Poll オブジェクトには日付データがあるので、日付を使って絞り込める と便利なはずです。以下の一行を追加しましょう:

date_hierarchy = 'pub_date'

これで、日付を使った階層的なナビゲーションが変更リストページの上部に追加さ れます。トップレベルでは、エントリのある年を表示します。その下は月になって いて、最後は日ごとにエントリを表示します。

さて、変更リストには何もしなくてもページ分割機能がある、ということをここで お知らせしておいた方がよいでしょう。デフォルトではページあたり 100 個の要素 を表示します。ページ分割、検索ボックス、フィルタ、日付による階層化、カラム ヘッダを使った並べ換えといった変更リストの機能は、すべてが協調して思いのま まに動作するのです。

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

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

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

設定ファイル (mysite/settings.py でしたよね) を開いて、 TEMPLATE_DIRS という設定を探して下さい。 TEMPLATE_DIRS はファイルシステム上のディレクトリ名からなるタプル で、 Django テンプレートをロードするときに探す場所を指定します。つまり検索 パスです。

デフォルトでは、 TEMPLATE_DIRS には何も指定されていません。一行 追加して、Django に自作テンプレートの置き場所を教えましょう:

TEMPLATE_DIRS = (
    '/home/my_username/mytemplates', # 自分の環境に合わせて変更してください。
)

Django のソースコードの中にある admin アプリケーションのデフォルトのテンプ レート置場 (django/contrib/admin/templates) から、 admin/base_site.html というテンプレートをコピーして、 TEMPLATE_DIRS 上の admin というディレクトリ下に置きます。例え ば、 TEMPLATE_DIRS"/home/my_username/mytemplates" と設定 していれば、 django/contrib/admin/templates/admin/base_site.html/home/my_username/mytemplates/admin/base_site.html にコピーします。 admin というサブディレクトリを作るのを忘れないようにして下さい。

ファイルを編集して、 Django と書かれた部分を自分のサイトに合わせて変更して ください。

テンプレートファイルは、 {% block branding %}{{ title }} といっ たテキストを多く含みます。 {%{{ タグは Django のテンプレート言語 のうちの一つです。 Django が admin/base_site.html を読み込んだ時、この テンプレート言語が評価され、最終的な HTML ページが作られます。今の時点でテ ンプレートについて理解できなくても心配しないでください。テンプレート言語に ついてはチュートリアルその 3 で詳しく掘り下げていきます。

admin アプリケーションのデフォルトのテンプレートはどれもオーバライド可能で す。テンプレートをオーバライドするには、 base_site.html と同じ、つまり デフォルトのディレクトリからカスタムディレクトリにコピーして編集するという 手順をとってください。

賢明な読者はこう疑問に思うでしょう:「 TEMPLATE_DIRS はデフォルト で何も指定していないのに、 Django はどうしてデフォルトのテンプレートを捜し 当てられるのだろう?」答えは、「デフォルトでは、 Django はテンプレートが見 つからない場合、自動的に各アプリケーションパッケージの templates/ サブ ディレクトリ下を探すようフォールバックるようになっている」です。詳しくは テンプレートローダタイプの解説 を参照してください。

管理サイトインデクスページのカスタマイズ

もしかすると、同じようにして Django admin サイトのインデクスページのルック & フィールをカスタマイズしたくなるかもしれませんね。

デフォルトでは、インデクスページは INSTALLED_APPS 設定に登録され ていて、 admin アプリケーションに登録されている全てのアプリケーションをアル ファベット順に表示します。レイアウトはどのようにも変更できます。なんにせよ、 インデクスページというものは admin で最も重要なページであり、簡単に使えなけ ればならないはずです。

カスタマイズすべきテンプレートは admin/index.html です (前節の admin/base_site.html の場合と同じように、デフォルトのディレクトリからカ スタムテンプレート置場のディレクトリにコピーしてください)。ファイルを開くと、 app_list というテンプレート変数が見つかるはずです。この変数に、インス トール済みの全てのアプリケーションが入っています。この変数の代りに、特定の オブジェクトごとに admin ページのリンクをハードコードすれば、自分の思い通り に変更できます。繰り返しますが、もしテンプレート言語について理解できなくて も心配しないで下さい。チュートリアルその 3 でテンプレート言語について扱って いきます。

admin サイトを使いこなせるようになったら、 チュートリアルその 3 に進んで、 poll アプリケーショ ンの公開用ビュー作成にとりかかりましょう。