表单资源( Media 类)

呈现一个有吸引力的、易于使用的网络表格需要的不仅仅是 HTML,它还需要 CSS 样式表,如果你想使用花哨的部件,你可能还需要在每个页面上包含一些 JavaScript。任何特定页面所需的 CSS 和 JavaScript 的确切组合将取决于该页面上使用的部件。

这就是资源定义的作用。Django 允许你将不同的文件——如样式表和脚本——与需要这些资产的表单和部件联系起来。例如,如果你想用一个日历来渲染 DateFields,你可以定义一个自定义的日历部件。然后这个部件可以与渲染日历所需的 CSS 和 JavaScript 相关联。当日历部件在表单上使用时,Django 能够识别需要的 CSS 和 JavaScript 文件,并以适合包含在你的网页上的形式提供文件名列表。

资源及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.jsactions.js

This static definition is converted at runtime into a widget property named media. The list of assets for a CalendarWidget instance can be retrieved through this property:

>>> 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文件列表。下面的示例提供了两个CSS选项——一个用于屏幕,一个用于打印:

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

如果一组CSS文件适用于多种输出媒体类型,字典的键可以是以逗号分隔的输出媒体类型列表。在下面的例子中,电视和投影机将具有相同的媒体需求:

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

If this last CSS definition were to be rendered, it would become the following 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">
Changed in Django 4.1:

In older versions, the type="text/css" attribute is included in CSS links.

js

描述所需JavaScript文件的一个元组。有关如何指定这些文件的路径的详细内容,请参阅 路径章节

extend

定义了 Media 声明继承行为的一个布尔值。

By default, any object using a static Media definition will inherit all the assets associated with the parent widget. This occurs regardless of how the parent defines its own requirements. For example, if we were to extend our basic Calendar widget from the example above:

>>> 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>

The FancyCalendar widget inherits all the assets from its parent widget. If you don't want Media to be inherited in this way, add an extend=False declaration to the Media declaration:

>>> 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 的构造函数接受 cssjs 关键字参数,与静态媒体定义中使用的格式相同。

例如,我们也可以以动态的方式定义日历组件的静态定义:

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

更多有关如何为动态 media 属性构建返回值的内容,请参阅 媒体对象 章节。

资源定义中的路径

Paths as strings

String paths used to specify assets can be either relative or absolute. If a path starts with /, http:// or https://, it will be interpreted as an absolute path, and left as-is. All other paths will be prepended with the value of the appropriate prefix. If the django.contrib.staticfiles app is installed, it will be used to serve assets.

无论您是否使用 django.contrib.staticfiles ,都需要设置 STATIC_URLSTATIC_ROOT 来渲染一张完整的网页。

To find the appropriate prefix to use, Django will check if the STATIC_URL setting is not None and automatically fall back to using MEDIA_URL. For example, if the MEDIA_URL for your site was 'http://uploads.example.com/' and STATIC_URL was 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>

But if STATIC_URL is '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>

Or if staticfiles is configured using the 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>

Paths as objects

New in Django 4.1.

Asset paths may also be given as hashable objects implementing an __html__() method. The __html__() method is typically added using the html_safe() decorator. The object is responsible for outputting the complete HTML <script> or <link> tag content:

>>> 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" rel="stylesheet">'
...

>>> class SomeWidget(forms.TextInput):
...     class Media:
...         js = [JSPath()]
...

Media 对象

当您访问表单或者组件的 media 属性时,返回值是一个 forms.Media 对象。正如我们已经看到的, Media 对象的字符串表示是一段需要在您HTML页面的 <head> 块中包含相关文件的HTML代码。

然而, Media 对象还有其他一些有趣的属性。

资源的子集

If you only want files of a particular type, you can use the subscript operator to filter out a medium of interest. For example:

>>> 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 objects can also be added together. When two Media objects are added, the resulting Media object contains the union of the assets specified by both:

>>> 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 定义的规则与组件的规则相同:声明可以是静态的或动态的;声明的路径和继承规则也一模一样。

Regardless of whether you define a media declaration, all Form objects have a media property. The default value for this property is the result of adding the media definitions for all widgets that are part of the form:

>>> 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>

If you want to associate additional assets with a form -- for example, CSS for form layout -- add a Media declaration to the form:

>>> 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>