フォームアセット (Media
クラス)¶
魅力的で使いやすいウェブのフォームを作るには、HTML だけでは不十分です。CSS スタイルシートも必要です。さらに豪華なウィジェットを使いたければ、JavaScript を各ページに配置する必要もあるでしょう。あるページに必要な CSS と JavaScript の組み合わせは、そのページに使用されるウィジェットによって異なります。
ここでアセット定義の出番です。Django では異なるファイル(例えばスタイルシートとスクリプト)を、これらを必要とするフォームおよびウィジェットと結びつけることができます。例として、DateFields を描画するのにカレンダーを使いたい場合、独自の Calendar ウィジェットを定義したとき、このウィジェットは CSS や JavaScript と紐付けることができます。フォーム上でこの Calendar ウィジェットを使用するとき、Django は必要な CSS および JavaScript のファイルを特定し、Web ページ上で参照できるよう、フォーム内にファイル名のリストを生成します。
アセットと Django Admin
Django Admin アプリケーションでは、カレンダーやフィルタリング選択など、いくつ かのカスタマイズされたウィジェットが定義されています。これらのウィジェットはアセット要件を定義し、 Django Admin は Django のデフォルトの代わりにカスタムウィジェットを使います。Admin テンプレートは、任意のページでウィジェットをレンダリングするのに必要なファイルだけを含めます。
Django Admin アプリケーションが使っているウィジェットが気に入ったら、ご自分の アプリケーションで自由に使ってください!これらは全て django.contrib.admin.widgets
に格納されています。
どの JavaScript ツールキットを使えばいいの?
多くの JavaScript ツールキットが存在し、その多くにはウィジェット (カレンダ ーウィジェットなど) が含まれていて、アプリケーションの拡張に利用できます。Django は、意図的に一つの JavaScript ツールキットだけを使うことを避けています。それぞれのツールキットには、相対的な長所と短所があります。Django はどんな JavaScript ツールキットとも統合できます。
定数として定義されたアセット¶
アセットを定義する最も簡単な方法は、定数とすることです。この方法を利用するには、内部 Media
クラスに宣言します。内部クラスのプロパティで必要なものを定義します。
以下に例を示します。
from django import forms
class CalendarWidget(forms.TextInput):
class Media:
css = {
"all": ["pretty.css"],
}
js = ["animations.js", "actions.js"]
このコードでは、CalendarWidget
を定義しており、これは TextInput
に基づいています。CalendarWidget をフォーム上で使用すると、CSS ファイル pretty.css
と JavaScript ファイル animations.js
および actions.js
を読み込めるようになります。
この静的定義は実行時に media
というウィジェットプロパティに変換されます。 CalendarWidget
インスタンスのアセットリストは、このプロパティから取得できます:
>>> w = CalendarWidget()
>>> print(w.media)
<link href="http://static.example.com/pretty.css" media="all" rel="stylesheet">
<script src="http://static.example.com/animations.js"></script>
<script src="http://static.example.com/actions.js"></script>
以下は指定可能な Media
オプションです。必須のものはありません。
css
¶
様々な形式の出力メディアに対して、必要な CSSファイルを定義するディクショナリです。
ディクショナリの値はファイル名のタプルかリストで指定します。ファイルのパスを指定する方法は パスの章 を参照してください。
ディクショナリのキーは、出力するメディアタイプを表します。これらは CSS ファイル認識可能なメディアタイプと同じです: 'all'、'aural'、'braille'、'embossed'、'handheld'、'print'、'projection'、'screen'、'tty'、'tv'。メディアタイプに応じたスタイルシートを提供する必要がある場合、各出力メディアに対して CSS ファイルを作成してください。以下の例は 2 つの CSS オプションを提供します -- 1 つはスクリーン用でもう一つは印刷用です:
class Media:
css = {
"screen": ["pretty.css"],
"print": ["newspaper.css"],
}
1 つのCSS ファイルのグループを複数の出力メディアタイプに適用するには、出力メディアタイプをカンマで区切ってディス書なりのキーに指定します。以下の例では、TV とプロジェクターは同じメディアを参照します:
class Media:
css = {
"screen": ["pretty.css"],
"tv,projector": ["lo_res.css"],
"print": ["newspaper.css"],
}
上記のCSS定義をレンダリングすると、次のようなHTMLになります:
<link href="http://static.example.com/pretty.css" media="screen" rel="stylesheet">
<link href="http://static.example.com/lo_res.css" media="tv,projector" rel="stylesheet">
<link href="http://static.example.com/newspaper.css" media="print" rel="stylesheet">
extend
¶
Media
宣言に対する継承動作を定義する真偽値です。
デフォルトでは、静的な Media
定義を使用しているオブジェクトは、親ウィジェットに関連するすべてのアセットを継承します。これは、親ウィジェットが自身の要件をどのように定義しているかによりません。例えば、上の例の基本的なカレンダーウィジェットを拡張するとします:
>>> class FancyCalendarWidget(CalendarWidget):
... class Media:
... css = {
... "all": ["fancy.css"],
... }
... js = ["whizbang.js"]
...
>>> w = FancyCalendarWidget()
>>> print(w.media)
<link href="http://static.example.com/pretty.css" media="all" rel="stylesheet">
<link href="http://static.example.com/fancy.css" media="all" rel="stylesheet">
<script src="http://static.example.com/animations.js"></script>
<script src="http://static.example.com/actions.js"></script>
<script src="http://static.example.com/whizbang.js"></script>
FancyCalendar ウィジェットは親ウィジェットからすべてのアセットを継承します。このように Media
を継承させたくない場合は、Media
の宣言に extend=False
宣言を追加してください:
>>> class FancyCalendarWidget(CalendarWidget):
... class Media:
... extend = False
... css = {
... "all": ["fancy.css"],
... }
... js = ["whizbang.js"]
...
>>> w = FancyCalendarWidget()
>>> print(w.media)
<link href="http://static.example.com/fancy.css" media="all" rel="stylesheet">
<script src="http://static.example.com/whizbang.js"></script>
継承をさらにコントロールしたい場合は、 動的なプロパティ を使ってアセットを定義してください。動的なプロパティはどのファイルを継承し、どのファイルを継承しないかを完全にコントロールできます。
動的プロパティとしての Media
¶
アセット要件のより詳細な操作が必要な場合、media
プロパティを直接定義できます。これは、forms.Media
のインスタンスを返すウィジェットプロパティを定義することで実現できます。forms.Media
に対するコンストラクタは、定数によるメディア定義と同様の形式で、css
と js
のキーワード引数を認識します。
例えば、上記例で扱ってきた Calendar Widget に対する定数の定義は、動的な方法では以下のように定義できます:
class CalendarWidget(forms.TextInput):
@property
def media(self):
return forms.Media(
css={"all": ["pretty.css"]}, js=["animations.js", "actions.js"]
)
動的な media
プロパティに対する戻り値を構成する方法については、 Media objects を参照してください。
アセット定義内のパス¶
文字列のパス¶
アセットを指定する文字列パスは相対パスと絶対パスがあります。パスが /
, http://
, https://
で始まる場合、絶対パスとして解釈され、そのまま残されます。それ以外のパスは適切なプレフィックスの値が先頭に付加されます。 django.contrib.staticfiles
アプリがインストールされている場合、アセットを配信するために使用されます。
django.contrib.staticfiles
を使うかどうかに関わらず、ウェブページを完全に表示するために STATIC_URL
と STATIC_ROOT
の設定が必要となります。
適切なプレフィックスを見つけるために、Django は STATIC_URL
の設定が None
でないかどうかをチェックし、自動的に MEDIA_URL
を使うようにフォールバックします。たとえば、あなたのサイトの MEDIA_URL
が 'http://uploads.example.com/'
で、 STATIC_URL
が None
だったとします:
>>> from django import forms
>>> class CalendarWidget(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" media="all" rel="stylesheet">
<script src="http://uploads.example.com/animations.js"></script>
<script src="http://othersite.com/actions.js"></script>
しかし、 STATIC_URL
が 'http://static.example.com/'
の場合は下記のようになります:
>>> w = CalendarWidget()
>>> print(w.media)
<link href="/css/pretty.css" media="all" rel="stylesheet">
<script src="http://static.example.com/animations.js"></script>
<script src="http://othersite.com/actions.js"></script>
または staticfiles
が ManifestStaticFilesStorage
を使って設定されている場合、下記のようになります:
>>> w = CalendarWidget()
>>> print(w.media)
<link href="/css/pretty.css" media="all" rel="stylesheet">
<script src="https://static.example.com/animations.27e20196a850.js"></script>
<script src="http://othersite.com/actions.js"></script>
オブジェクトとしてのパス¶
アセットパスは __html__()
メソッドを実装したハッシュ可能なオブジェクトとして指定することもできます。 html_safe()
デコレータを使って __html__()
メソッドを追加します。このオブジェクトは HTML の <script>
や <link>
タグの内容を出力します:
>>> from django import forms
>>> from django.utils.html import html_safe
>>>
>>> @html_safe
... class JSPath:
... def __str__(self):
... return '<script src="https://example.org/asset.js" defer>'
...
>>> class SomeWidget(forms.TextInput):
... class Media:
... js = [JSPath()]
...
Media
objects¶
ウィジェットやフォームの media
属性に応答指令信号を送ると、forms.Media
オブジェクトが戻り値となります。すでに見たように、Media
オブジェクトの文字列表現は HTMLで、HTML ページ内の <head>
ブロックに関連ファイルを含める必要があります。
ただし、Media
オブジェクトにはいくつかの面白いプロパティが存在します。
アセットのサブセット¶
特定のタイプのファイルだけが必要な場合は、添え字を使って、欲しいメディアだけをフィルタリングできます。たとえば以下のようにします:
>>> w = CalendarWidget()
>>> print(w.media)
<link href="http://static.example.com/pretty.css" media="all" rel="stylesheet">
<script src="http://static.example.com/animations.js"></script>
<script src="http://static.example.com/actions.js"></script>
>>> print(w.media["css"])
<link href="http://static.example.com/pretty.css" media="all" rel="stylesheet">
添え字演算子を使うと、返される値は、指定したメディアだけを含む新しい Media
オブジェクトです。
Media
オブジェクトを結合する¶
Media
オブジェクトは一緒に追加することもできます。2 つの Media
オブジェクトを追加した場合、結果として生成される Media
オブジェクトには両方のアセットが含まれます:
>>> from django import forms
>>> 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://static.example.com/pretty.css" media="all" rel="stylesheet">
<script src="http://static.example.com/animations.js"></script>
<script src="http://static.example.com/actions.js"></script>
<script src="http://static.example.com/whizbang.js"></script>
アセットの順序¶
DOM に挿入されるアセットの順序が重要になることがあります。例えば、jQuery に依存したスクリプトがあるかもしれません。したがって、結合した Media
オブジェクトは、各 Media
クラス内で定義された順序を保持します。
例えば次のようにします:
>>> from django import forms
>>> class CalendarWidget(forms.TextInput):
... class Media:
... js = ["jQuery.js", "calendar.js", "noConflict.js"]
...
>>> class TimeWidget(forms.TextInput):
... class Media:
... js = ["jQuery.js", "time.js", "noConflict.js"]
...
>>> w1 = CalendarWidget()
>>> w2 = TimeWidget()
>>> print(w1.media + w2.media)
<script src="http://static.example.com/jQuery.js"></script>
<script src="http://static.example.com/calendar.js"></script>
<script src="http://static.example.com/time.js"></script>
<script src="http://static.example.com/noConflict.js"></script>
順序が矛盾した Media
オブジェクトを結合すると MediaOrderConflictWarning
となります。
フォームの Media
¶
ウィジェットだけでなく、フォームにも media
の定義を行うことができます。フォーム上での media
定義のルールはウィジェットと同じです: 宣言は定数および動的に行えます; パスと継承についてのルールもまったく同じです。
media
宣言を定義しているかどうかに関わらず、すべてのフォームオブジェクトは media
プロパティを持ちます。このプロパティのデフォルト値は、フォームの一部であるすべてのウィジェットの media
定義を追加した結果です:
>>> from django import forms
>>> class ContactForm(forms.Form):
... date = DateField(widget=CalendarWidget)
... name = CharField(max_length=40, widget=OtherWidget)
...
>>> f = ContactForm()
>>> f.media
<link href="http://static.example.com/pretty.css" media="all" rel="stylesheet">
<script src="http://static.example.com/animations.js"></script>
<script src="http://static.example.com/actions.js"></script>
<script src="http://static.example.com/whizbang.js"></script>
フォームレイアウト用のCSSなど、フォームに追加のアセットを関連付けたい場合は、フォームに Media
宣言を追加します:
>>> 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://static.example.com/pretty.css" media="all" rel="stylesheet">
<link href="http://static.example.com/layout.css" media="all" rel="stylesheet">
<script src="http://static.example.com/animations.js"></script>
<script src="http://static.example.com/actions.js"></script>
<script src="http://static.example.com/whizbang.js"></script>