フォームアセット (Media
クラス)¶
魅力的で使いやすいウェブのフォームを作るには、HTML だけでは不十分です - CSS スタイルシートも必要です。さらに豪華な "Web2.0" ウィジェットを使いたければ、JavaScript を各ページに配置する必要もあるでしょう。あるページに必要な CSS と JavaScript の組み合わせは、そのページに使用されるウィジェットによって異なります。
ここでアセット定義の出番です。Django では異なるファイル -- 例えばスタイルシートとスクリプト -- を、これらを必要とするフォームおよびウィジェットと紐付けることができます。例として、DateFields を描画するのにカレンダーを使いたい場合、独自の Calendar ウィジェットを定義したとき、このウィジェットは CSS や JavaScript と紐付けることができます。フォーム上でこの Calendar ウィジェットを使用するとき、Django は必要な CSS および JavaScript のファイルを特定し、Web ページ上で簡単に参照できるよう、フォーム内にファイル名のリストを生成します。
アセットと Django Admin
The Django Admin application defines a number of customized widgets for calendars, filtered selections, and so on. These widgets define asset requirements, and the Django Admin uses the custom widgets in place of the Django defaults. The Admin templates will only include those files that are required to render the widgets on any given page.
If you like the widgets that the Django Admin application uses,
feel free to use them in your own application! They're all stored
in django.contrib.admin.widgets
.
Which JavaScript toolkit?
Many JavaScript toolkits exist, and many of them include widgets (such as calendar widgets) that can be used to enhance your application. Django has deliberately avoided blessing any one JavaScript toolkit. Each toolkit has its own relative strengths and weaknesses - use whichever toolkit suits your requirements. Django is able to integrate with any JavaScript toolkit.
定数として定義されたアセット¶
アセットを定義する最も簡単な方法は、定数とすることです。この方法を利用するには、内部 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" 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://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" type="text/css" media="screen" rel="stylesheet">
<link href="http://static.example.com/lo_res.css" type="text/css" media="tv,projector" rel="stylesheet">
<link href="http://static.example.com/newspaper.css" type="text/css" media="print" rel="stylesheet">
extend
¶
Media
宣言に対する継承動作を定義する真偽値です。
デフォルトでは、定数の Media
定義を使用するすべてのオブジェクトは、親ウィジェットに紐付いたすべてのアセットを継承します。この挙動は、親ウィジェットがどのように自身の要件を定義しているかに関わらず発生します。例えば、上の例にあるベーシックな Calendar ウィジェットを拡張するには:
>>> 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" type="text/css" media="all" rel="stylesheet">
<link href="http://static.example.com/fancy.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://static.example.com/actions.js"></script>
<script type="text/javascript" src="http://static.example.com/whizbang.js"></script>
FancyCalendar ウィジェットは親ウィジェットからすべてのアセットを継承します。これを回避したい場合には 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" type="text/css" media="all" rel="stylesheet">
<script type="text/javascript" src="http://static.example.com/whizbang.js"></script>
継承をより詳細にコントロールするにあh、動的なプロパティ を使ってアセットを定義してください。 動的なプロパティを使えば、どのファイルを継承し、また継承しないかを完全にコントロールできます。
動的プロパティとしての 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
アプリケーションがインストールされている場合、アセットを提供するために使用されます。
mod: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" 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>
もしくは、staticfiles
が ManifestStaticFilesStorage
を使って設定されている場合:
>>> w = CalendarWidget()
>>> print(w.media)
<link href="/css/pretty.css" type="text/css" media="all" rel="stylesheet">
<script type="text/javascript" src="https://static.example.com/animations.27e20196a850.js"></script>
<script type="text/javascript" src="http://othersite.com/actions.js"></script>
Media
objects¶
ウィジェットやフォームの media
属性に応答指令信号を送ると、forms.Media
オブジェクトが戻り値となります。すでに見たように、Media
オブジェクトの文字列表現は HTMLで、HTML ページ内の <head>
ブロックに関連ファイルを含める必要があります。
ただし、Media
オブジェクトにはいくつかの面白いプロパティが存在します。
アセットのサブセット¶
特定のタイプのファイルのみ必要な場合、サブスクリプトオペレーターを使って使用するメディアをフィルタできます。例えば:
>>> w = CalendarWidget()
>>> print(w.media)
<link href="http://static.example.com/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://static.example.com/actions.js"></script>
>>> print(w.media['css'])
<link href="http://static.example.com/pretty.css" type="text/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" 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://static.example.com/actions.js"></script>
<script type="text/javascript" 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 type="text/javascript" src="http://static.example.com/jQuery.js"></script>
<script type="text/javascript" src="http://static.example.com/calendar.js"></script>
<script type="text/javascript" src="http://static.example.com/time.js"></script>
<script type="text/javascript" src="http://static.example.com/noConflict.js"></script>
順序が矛盾した Media
オブジェクトを結合すると MediaOrderConflictWarning
となります。
古いバージョンでは、Media
オブジェクトのアセットは各リストの相対的な順序を保持するため、統合ではなく連結されます。
フォームの Media
¶
ウィジェットだけでなく、フォームにも media
の定義を行うことができます。フォーム上での media
定義のルールはウィジェットと同じです: 宣言は定数および動的に行えます; パスと継承についてのルールもまったく同じです。
media
宣言を定義したかどうかに関わらず、すべての Form オブジェクトは 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" 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://static.example.com/actions.js"></script>
<script type="text/javascript" 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" type="text/css" media="all" rel="stylesheet">
<link href="http://static.example.com/layout.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://static.example.com/actions.js"></script>
<script type="text/javascript" src="http://static.example.com/whizbang.js"></script>