フォームアセット (Media クラス)Link to this heading

魅力的で使いやすいウェブのフォームを作るには、HTML だけでは不十分です。CSS スタイルシートも必要です。さらに豪華なウィジェットを使いたければ、JavaScript を各ページに配置する必要もあるでしょう。あるページに必要な CSS と JavaScript の組み合わせは、そのページに使用されるウィジェットによって異なります。

ここでアセット定義の出番です。Django では異なるファイル(例えばスタイルシートとスクリプト)を、これらを必要とするフォームおよびウィジェットと結びつけることができます。例として、DateFields を描画するのにカレンダーを使いたい場合、独自の Calendar ウィジェットを定義したとき、このウィジェットは CSS や JavaScript と紐付けることができます。フォーム上でこの Calendar ウィジェットを使用するとき、Django は必要な CSS および JavaScript のファイルを特定し、Web ページ上で参照できるよう、フォーム内にファイル名のリストを生成します。

定数として定義されたアセットLink to this heading

アセットを定義する最も簡単な方法は、定数とすることです。この方法を利用するには、内部 Media クラスに宣言します。内部クラスのプロパティで必要なものを定義します。

以下に例を示します。

Code
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 インスタンスのアセットリストは、このプロパティから取得できます:

Python console
>>> w = CalendarWidget()
>>> print(w.media)
<link href="https://static.example.com/pretty.css" media="all" rel="stylesheet">
<script src="https://static.example.com/animations.js"></script>
<script src="https://static.example.com/actions.js"></script>

以下は指定可能な Media オプションです。必須のものはありません。

cssLink to this heading

様々な形式の出力メディアに対して、必要な CSSファイルを定義するディクショナリです。

ディクショナリの値はファイル名のタプルかリストで指定します。ファイルのパスを指定する方法は パスの章 を参照してください。

ディクショナリのキーは、出力するメディアタイプを表します。これらは CSS ファイル認識可能なメディアタイプと同じです: 'all'、'aural'、'braille'、'embossed'、'handheld'、'print'、'projection'、'screen'、'tty'、'tv'。メディアタイプに応じたスタイルシートを提供する必要がある場合、各出力メディアに対して CSS ファイルを作成してください。以下の例は 2 つの CSS オプションを提供します -- 1 つはスクリーン用でもう一つは印刷用です:

Code
class Media:
    css = {
        "screen": ["pretty.css"],
        "print": ["newspaper.css"],
    }

1 つのCSS ファイルのグループを複数の出力メディアタイプに適用するには、出力メディアタイプをカンマで区切ってディス書なりのキーに指定します。以下の例では、TV とプロジェクターは同じメディアを参照します:

Code
class Media:
    css = {
        "screen": ["pretty.css"],
        "tv,projector": ["lo_res.css"],
        "print": ["newspaper.css"],
    }

上記のCSS定義をレンダリングすると、次のようなHTMLになります:

Django template
<link href="https://static.example.com/pretty.css" media="screen" rel="stylesheet">
<link href="https://static.example.com/lo_res.css" media="tv,projector" rel="stylesheet">
<link href="https://static.example.com/newspaper.css" media="print" rel="stylesheet">

Stylesheet objectsLink to this heading

class Stylesheet(href, **attributes)Link to this definition

Represents a stylesheet link. The rel attribute is set to "stylesheet".

The first parameter, href, is the string path to the stylesheet file. See the section on paths for details on how to specify paths to these files.

The optional keyword arguments, **attributes, are additional HTML attributes set on the rendered <link> tag.

使用例については、オブジェクトとしてのパス を参照してください。

jsLink to this heading

必要な JavaScript ファイルを定義したタプルです。ファイルのパスを指定する方法は パスの章 を参照してください。

Script オブジェクトLink to this heading

class Script(src, **attributes)Link to this definition

スクリプトファイルを表現します。

最初の引数 src は、スクリプトファイルへのパスを表す文字列です。 このパスの指定方法については、 パスの指定に関するセクション を参照してください。

任意のキーワード引数 **attributes は、生成される <script> タグに設定される HTML 属性です。

使用例については、オブジェクトとしてのパス を参照してください。

extendLink to this heading

Media 宣言に対する継承動作を定義する真偽値です。

デフォルトでは、静的な Media 定義を使用しているオブジェクトは、親ウィジェットに関連するすべてのアセットを継承します。これは、親ウィジェットが自身の要件をどのように定義しているかによりません。例えば、上の例の基本的なカレンダーウィジェットを拡張するとします:

Python console
>>> class FancyCalendarWidget(CalendarWidget):
...     class Media:
...         css = {
...             "all": ["fancy.css"],
...         }
...         js = ["whizbang.js"]
...

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

FancyCalendar ウィジェットは親ウィジェットからすべてのアセットを継承します。このように Media を継承させたくない場合は、Media の宣言に extend=False 宣言を追加してください:

Python console
>>> class FancyCalendarWidget(CalendarWidget):
...     class Media:
...         extend = False
...         css = {
...             "all": ["fancy.css"],
...         }
...         js = ["whizbang.js"]
...

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

継承をさらにコントロールしたい場合は、 動的なプロパティ を使ってアセットを定義してください。動的なプロパティはどのファイルを継承し、どのファイルを継承しないかを完全にコントロールできます。

動的プロパティとしての MediaLink to this heading

If you need to perform some more sophisticated manipulation of asset requirements, you can define the media property directly. This is done by defining a widget property that returns an instance of forms.Media. The constructor for forms.Media accepts css and js keyword arguments in the same format as that used in a static media definition.

例えば、上記例で扱ってきた Calendar Widget に対する定数の定義は、動的な方法では以下のように定義できます:

Code
class CalendarWidget(forms.TextInput):
    @property
    def media(self):
        return forms.Media(
            css={"all": ["pretty.css"]}, js=["animations.js", "actions.js"]
        )

動的な media プロパティに対する戻り値を構成する方法については、 Media objects を参照してください。

アセット定義内のパスLink to this heading

文字列のパスLink to this heading

アセットを指定する文字列パスは相対パスと絶対パスがあります。パスが /, http://, https:// で始まる場合、絶対パスとして解釈され、そのまま残されます。それ以外のパスは適切なプレフィックスの値が先頭に付加されます。 django.contrib.staticfiles アプリがインストールされている場合、アセットを配信するために使用されます。

django.contrib.staticfiles を使うかどうかに関わらず、ウェブページを完全に表示するために STATIC_URLSTATIC_ROOT の設定が必要となります。

適切なプレフィックスを見つけるために、Django は STATIC_URL 設定が None でないかを確認し、自動的に MEDIA_URL を使用するようフォールバックします。例えば、サイトの MEDIA_URL'https://uploads.example.com/' で、STATIC_URLNone の場合は次のようになります。

Python console
>>> from django import forms
>>> class CalendarWidget(forms.TextInput):
...     class Media:
...         css = {
...             "all": ["/css/pretty.css"],
...         }
...         js = ["animations.js", "https://othersite.com/actions.js"]
...

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

しかし、STATIC_URL'https://static.example.com/' の場合は、次のようになります。

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

または staticfilesManifestStaticFilesStorage を使って設定されている場合、下記のようになります:

Python console
>>> 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="https://othersite.com/actions.js"></script>

オブジェクトとしてのパスLink to this heading

Assets may also be object-based, using Script or Stylesheet. These allow you to pass custom HTML attributes:

Code
class Media:
    js = [
        Script(
            "https://cdn.example.com/something.min.js",
            **{
                "crossorigin": "anonymous",
                "async": True,
            },
        ),
    ]

この Media 定義をレンダリングすると、次のような HTML になります:

Django template
<script src="https://cdn.example.com/something.min.js"
        crossorigin="anonymous"
        async>
</script>

Similarly, a Stylesheet object can be used to add a stylesheet link with custom HTML attributes:

Code
class Media:
    css = {
        "all": [
            Stylesheet(
                "https://cdn.example.com/print.css",
                crossorigin="anonymous",
                media="print",
            ),
        ]
    }

If this Media definition were to be rendered, it would become:

Django template
<link href="https://cdn.example.com/print.css"
      crossorigin="anonymous"
      media="print"
      rel="stylesheet">

Media objectsLink to this heading

class MediaLink to this definition

Represents a collection of media assets required by a widget or form.

ウィジェットやフォームの media 属性に応答指令信号を送ると、forms.Media オブジェクトが戻り値となります。すでに見たように、Media オブジェクトの文字列表現は HTMLで、HTML ページ内の <head> ブロックに関連ファイルを含める必要があります。

ただし、Media オブジェクトにはいくつかの面白いプロパティが存在します。

アセットのサブセットLink to this heading

特定のタイプのファイルだけが必要な場合は、添え字を使って、欲しいメディアだけをフィルタリングできます。たとえば以下のようにします:

Python console
>>> w = CalendarWidget()
>>> print(w.media)
<link href="https://static.example.com/pretty.css" media="all" rel="stylesheet">
<script src="https://static.example.com/animations.js"></script>
<script src="https://static.example.com/actions.js"></script>

>>> print(w.media["css"])
<link href="https://static.example.com/pretty.css" media="all" rel="stylesheet">

添え字演算子を使うと、返される値は、指定したメディアだけを含む新しい Media オブジェクトです。

Media オブジェクトを結合するLink to this heading

Media オブジェクトは一緒に追加することもできます。2 つの Media オブジェクトを追加した場合、結果として生成される Media オブジェクトには両方のアセットが含まれます:

Python console
>>> 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="https://static.example.com/pretty.css" media="all" rel="stylesheet">
<script src="https://static.example.com/animations.js"></script>
<script src="https://static.example.com/actions.js"></script>
<script src="https://static.example.com/whizbang.js"></script>

アセットの順序Link to this heading

DOM に挿入されるアセットの順序が重要になることがあります。例えば、jQuery に依存したスクリプトがあるかもしれません。したがって、結合した Media オブジェクトは、各 Media クラス内で定義された順序を保持します。

例えば次のようにします:

Python console
>>> 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="https://static.example.com/jQuery.js"></script>
<script src="https://static.example.com/calendar.js"></script>
<script src="https://static.example.com/time.js"></script>
<script src="https://static.example.com/noConflict.js"></script>

順序が矛盾した Media オブジェクトを結合すると MediaOrderConflictWarning となります。

フォームの MediaLink to this heading

ウィジェットだけでなく、フォームにも media の定義を行うことができます。フォーム上での media 定義のルールはウィジェットと同じです: 宣言は定数および動的に行えます; パスと継承についてのルールもまったく同じです。

media 宣言を定義しているかどうかに関わらず、すべてのフォームオブジェクトは media プロパティを持ちます。このプロパティのデフォルト値は、フォームの一部であるすべてのウィジェットの media 定義を追加した結果です:

Python console
>>> 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="https://static.example.com/pretty.css" media="all" rel="stylesheet">
<script src="https://static.example.com/animations.js"></script>
<script src="https://static.example.com/actions.js"></script>
<script src="https://static.example.com/whizbang.js"></script>

フォームレイアウト用のCSSなど、フォームに追加のアセットを関連付けたい場合は、フォームに Media 宣言を追加します:

Python console
>>> 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="https://static.example.com/pretty.css" media="all" rel="stylesheet">
<link href="https://static.example.com/layout.css" media="all" rel="stylesheet">
<script src="https://static.example.com/animations.js"></script>
<script src="https://static.example.com/actions.js"></script>
<script src="https://static.example.com/whizbang.js"></script>