フォームメディア( Form Media )

revision-up-to:17812 (1.4)

魅力的で使いやすい Web フォームをレンダしたいなら、 HTML だけでは能力不足で しょう。CSS スタイルシートが必要でしょうし、ファンシーな “Web2.0” 的ウィジェッ トを使いたければ、各ページに JavaScript を仕込む必要もあるでしょう。あるペー ジでどのCSS と JavaScript の組み合わせが必要かは、そのページで使われている ウィジェットによって異なります。

そこで、 Django のメディア定義が登場します。 Django は様々なメディアファイ ルをそのメディアファイルを必要とするフォームやウィジェットに関連付けられる ようにしています。例えば、カレンダーを使って DateField をレンダしたいので、 カスタムの Calendar ウィジェットを定義したとします。このウィジェットには、 カレンダーをレンダするときに必要な CSS や JavaScript を関連付けておけます。 Calendar ウィジェットをフォーム上で使うときに、 Django は必要な CSS や JavaScript ファイルを特定して、それらのファイル名のリストを Web ページに簡 単に組み込める形式で提供します。

Django admin とメディアファイル

Django の admin アプリケーションは、カレンダーやフィルタ機能つきのセレ クタといった、様々なカスタムウィジェットを定義しています。これらのウィ ジェットは、それぞれが必要なメディアを定義していて、 Django は admin サ イトを表示するときに、デフォルトのウィジェットの代りにカスタムウィジェッ トを表示しています。 admin のテンプレートは、ページごとに必要なメディア ファイルだけを取り込み、ウィジェットをレンダするようになっています。

Django の admin アプリケーションのウィジェットを使いたいのなら、どうぞ ご自由に!ウィジェットは、全て django.contrib.admin.widgets で定義 されています。

どの JavaScript ツールキットを使えばいいの ?

JavaScript ツールキットは世の中に沢山あり、そのほとんどが(カレンダーの ような)アプリケーションの操作性を向上させられるウィジェットを定義して います。 Django は特定の JavaScript ツールキットを贔屓にしないよう慎重 に設計されています。どのツールキットにも、お互いに長所や短所があります。 自分にとって使いやすいツールキットを使ってください。 Django はどんな JavaScript ツールキットでも組み込めます。

メディア定義を静的に行う

メディア定義の簡単な方法は、クラスで静的に行うというものです。この方法では、 メディア定義を内部クラスとして宣言します。必要なメディアファイルの情報は、 内部クラスのプロパティとして定義します。

簡単な例を示しましょう:

class CalendarWidget(forms.TextInput):
    class Media:
        css = {
            'all': ('pretty.css',)
        }
        js = ('animations.js', 'actions.js')

このコードでは、 TextInput をベースにして CalendarWidget を定義して います。 CalendarWidget をフォーム内で使うと、フォームは CSS ファイル pretty.css と JavaScript ファイル animations.js および actions.js を組み込むよう指示を受けます。

このような静的なメディア定義は、実行時に media というウィジェットのプロ パティに変換されます。 CalendarWidget インスタンスのメディア情報は、プ ロパティを介して以下のように取り出せます:

>>> w = CalendarWidget()
>>> print w.media
<link href="http://media.example.com/pretty.css" type="text/css" media="all" rel="stylesheet" />
<script type="text/javascript" src="http://media.example.com/animations.js"></script>
<script type="text/javascript" src="http://media.example.com/actions.js"></script>

Media には、以下のようなオプションがあります。必須のものはありません。

css

出力媒体ごとの CSS ファイル要件を定義した辞書です。

この辞書の値はメディアファイル名を列挙したタプルまたはリストです。メディア ファイルのパスを指定する方法は メディアパスの節 を参照してください。

キーは出力媒体の名前です。このキーは、 CSS ファイルのメディアタイプ宣言で使 われるのと同じ、すなわち ‘all’, ‘aural’, ‘braille’, ‘embossed’, ‘handheld’, ‘print’, ‘projection’, ‘screen’, ‘tty’ そして ‘tv’ です。 メディアタイプごとに別々のスタイルシートを使いたければ、各メディアごとに CSS ファイルのリストを指定してください。以下の例では、スクリーン (screen) と印刷 (print) 用の CSS オプションを定義しています:

class Media:
    css = {
        'screen': ('pretty.css',),
        'print': ('newspaper.css',)
    }

一つの CSS ファイルグループが複数のメディアタイプをカバーできる場合、辞書の キーにメディアタイプをカンマ区切りのリストにして指定してください。以下の例 では、テレビ (tv) およびプロジェクタ (projector) に同じメディアファイル要件 を指定しています:

class Media:
    css = {
        'screen': ('pretty.css',),
        'tv,projector': ('lo_res.css',),
        'print': ('newspaper.css',)
    }

‘print’ の CSS 定義を使ってレンダリングを実行すると、以下のような HTML を出 力します:

<link href="http://media.example.com/pretty.css" type="text/css" media="screen" rel="stylesheet" />
<link href="http://media.example.com/lo_res.css" type="text/css" media="tv,projector" rel="stylesheet" />
<link href="http://media.example.com/newspaper.css" type="text/css" media="print" rel="stylesheet" />

js

必要な JavaScript ファイルを列挙したタプルです。メディアファイルのパスを指 定する方法は メディアパスの節 を参照してください。

extend

メディア宣言を親クラスから継承するかどうかを決めるブール値です。

デフォルトの設定では、静的メディア宣言を使うオブジェクトは全て、親クラスの ウィジェットで定義されているメディアを継承します。この継承は、親クラスでど んなメディアファイル要件を宣言したかに関係なく行われます。例えば、上の例の カレンダーウィジェットを、以下のように拡張したとします:

>>> class FancyCalendarWidget(CalendarWidget):
...     class Media:
...         css = {
...             'all': ('fancy.css',)
...         }
...         js = ('whizbang.js',)

>>> w = FancyCalendarWidget()
>>> print w.media
<link href="http://media.example.com/pretty.css" type="text/css" media="all" rel="stylesheet" />
<link href="http://media.example.com/fancy.css" type="text/css" media="all" rel="stylesheet" />
<script type="text/javascript" src="http://media.example.com/animations.js"></script>
<script type="text/javascript" src="http://media.example.com/actions.js"></script>
<script type="text/javascript" src="http://media.example.com/whizbang.js"></script>

FancyCalendarWidget は、親ウィジェットの全てのメディアを継承します。こ のやり方で親モデルのメディア宣言を継承したくないのなら、 extend=FalseMedia クラスの宣言に追加します:

class FancyCalendar(Calendar):
    class Media:
        extend = False
        css = {
            'all': ('fancy.css',)
        }
        js = ('whizbang.js',)

>>> w = FancyCalendarWidget()
>>> print w.media
<link href="http://media.example.com/fancy.css" type="text/css" media="all" rel="stylesheet" />
<script type="text/javascript" src="http://media.example.com/whizbang.js"></script>

メディア宣言の継承をより細かく制御したければ、 動的なプロパティ を使って メディア宣言を定義してください。動的プロパティを使えば、どのメディアファイ ルを継承し、どのファイルを継承しないかを完全に制御できます。

動的プロパティでのメディア定義

media プロパティを直接定義すれば、メディアファイル要件をより洗練された やり方で制御できます。この方法では、 forms.Media のインスタンスを 返すようなプロパティを定義します。 forms.Media のコンストラクタは cssjs といったキーワード引数をとり、それぞれ静的なメディア定義 で使われているのと同じ形式の値を指定できます。

例えば、先の CalendarWidget の静的メディア定義を動的なやり方で実現する と、以下のように書けます:

class CalendarWidget(forms.TextInput):
    def _media(self):
        return forms.Media(css={'all': ('pretty.css',)},
                           js=('animations.js', 'actions.js'))
    media = property(_media)

動的なメディアプロパティの戻り値を構築するための詳しい方法は、 Media オブジェクト の節を参照してください。

メディア定義のパス

Django 1.3 で変更されました: リリースノートを参照してください

メディアファイルの場所を指定するためのパスは、相対でも絶対でもかまいません。 パスが '/', 'http://', 'https://' のいずれかで開始していれば、 絶対パス指定として、値をそのまま使います。それ以外のパスの前には適切な プレフィクス(prefix)を指定します。 staticfiles app の部分的な紹介として、 “静的なファイル”(イメージや CSS, JavaScript, など)に完全な Web ページを 表示するための二つの新しい設定が追加されました。: STATIC_URL and STATIC_ROOT がそれです。 適切なプレフィクスを使うために、 Django は STATIC_URLNone ではないかどうかを確かめ、 MEDIA_URL を使うために自動的に 巻き戻ります。例えば、もしサイトの MEDIA_URL'http://uploads.example.com/' であって STATIC_URLNone であった場合:

>>> class CalenderWidget(forms.TextInput):
        class Media:
            css = {
                'all: ('/css/pretty.css',),
            }
            js = ('animations.js', 'http://othersite.com/actions.js')

>>> w = CalendarWidget()
>>> print w.media
<link href="/css/pretty.css" type="text/css" media="all" rel="stylesheet" />
<script type="text/javascript" src="http://uploads.example.com/animations.js"></script>
<script type="text/javascript" src="http://othersite.com/actions.js"></script>

ですが、もし STATIC_URL'http://static.example.com/' なら ば:

>>> w = CalendarWidget()
>>> print w.media
<link href="/css/pretty.css" type="text/css" media="all" rel="stylesheet" />
<script type="text/javascript" src="http://static.example.com/animations.js"></script>
<script type="text/javascript" src="http://othersite.com/actions.js"></script>

Media オブジェクト

ウィジェットやフォームの media 属性を調べると、 forms.Media オブジェク トが返されます。すでに解説した通り、 Media オブジェクトの文字列表現は、 HTML ページの <head> ブロックでメディアファイルを取り込むときに必要な HTML コードです。

とはいえ、 Media オブジェクトには他にもいくつか興味深いプロパティがあり ます。

Media サブセット

特定のタイプのメディアファイル情報だけを出力したければ、添え字表記を使って 以下のように欲しいメディアだけを指定できます:

>>> w = CalendarWidget()
>>> print w.media
<link href="http://media.example.com/pretty.css" type="text/css" media="all" rel="stylesheet" />
<script type="text/javascript" src="http://media.example.com/animations.js"></script>
<script type="text/javascript" src="http://media.example.com/actions.js"></script>

>>> print w.media['css']
<link href="http://media.example.com/pretty.css" type="text/css" media="all" rel="stylesheet" />

添え字表記を使ってアクセスすると、新たな Media オブジェクトが返されます。 ただし、このオブジェクトには指定したメディアしか入っていません。

Media オブジェクトを組み合わせる

Media オブジェクトは加算できます。二つの Media オブジェクトを加算す ると、結果の Media オブジェクトには両方のメディアファイル情報が入ります:

>>> class CalendarWidget(forms.TextInput):
...     class Media:
...         css = {
...             'all': ('pretty.css',)
...         }
...         js = ('animations.js', 'actions.js')

>>> class OtherWidget(forms.TextInput):
...     class Media:
...         js = ('whizbang.js',)
>>> w1 = CalendarWidget()
>>> w2 = OtherWidget()
>>> print w1.media + w2.media
<link href="http://media.example.com/pretty.css" type="text/css" media="all" rel="stylesheet" />
<script type="text/javascript" src="http://media.example.com/animations.js"></script>
<script type="text/javascript" src="http://media.example.com/actions.js"></script>
<script type="text/javascript" src="http://media.example.com/whizbang.js"></script>

フォームのメディア

メディアファイル定義を持てるのはウィジェットだけではありません。フォームに もまた、メディアを定義できます。フォームのメディアファイル定義には、ウィジェ ットと同じ規則が適用されます。すなわち、静的・動的な宣言ができ、宣言内容に は絶対・相対パスの解釈や継承といった規則がウィジェット同様に当てはまります。

どちらにメディア情報を定義したかに関係なく、フォームオブジェクトには 必ず media プロパティがあります。このプロパティのデフォルト値は、 フォームを構成する全てのウィジェットのメディアファイル定義を加算したもので す:

>>> class ContactForm(forms.Form):
...     date = DateField(widget=CalendarWidget)
...     name = CharField(max_length=40, widget=OtherWidget)

>>> f = ContactForm()
>>> f.media
<link href="http://media.example.com/pretty.css" type="text/css" media="all" rel="stylesheet" />
<script type="text/javascript" src="http://media.example.com/animations.js"></script>
<script type="text/javascript" src="http://media.example.com/actions.js"></script>
<script type="text/javascript" src="http://media.example.com/whizbang.js"></script>

フォーム固有のメディア宣言を付加したい場合、例えば、フォームのレイアウトを 決める CSS を指定したい場合には、以下のようにメディア定義を追加します:

>>> class ContactForm(forms.Form):
...     date = DateField(widget=CalendarWidget)
...     name = CharField(max_length=40, widget=OtherWidget)
...
...     class Media:
...         css = {
...             'all': ('layout.css',)
...         }
>>> f = ContactForm()
>>> f.media
<link href="http://media.example.com/pretty.css" type="text/css" media="all" rel="stylesheet" />
<link href="http://media.example.com/layout.css" type="text/css" media="all" rel="stylesheet" />
<script type="text/javascript" src="http://media.example.com/animations.js"></script>
<script type="text/javascript" src="http://media.example.com/actions.js"></script>
<script type="text/javascript" src="http://media.example.com/whizbang.js"></script>