表单资源( 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

这个静态定义在运行时被转换为一个名为 media 的小部件属性。可以通过这个属性获取 CalendarWidget 实例的资产列表:

>>> 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 选项列表。没有一个是必需项。

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"],
    }

如果这个最后的 CSS 定义要被渲染,它将变成以下 HTML:

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

js

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

Script objects

New in Django 5.2.
class Script(src, **attributes)[source]

Represents a script file.

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

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

See 路径作为对象 for usage examples.

extend

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

默认情况下,使用静态 Media 定义的任何对象都会继承与父小部件关联的所有资产。这将发生无论父级如何定义自己的要求。例如,如果我们要扩展上面示例中的基本日历小部件:

>>> 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 声明:

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

如果您需要更多的继承控制,用一个 动态属性 定义你的 。动态属性使您可以完全控制哪些文件是否继承。

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 属性构建返回值的内容,请参阅 媒体对象 章节。

资源定义中的路径

路径作为字符串

用于指定资产的字符串路径可以是相对路径或绝对路径。如果路径以 /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

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

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

或者如果使用 ManifestStaticFilesStorage 配置了 staticfiles

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

路径作为对象

Assets may also be object-based, using Script. Furthermore, these allow you to pass custom HTML attributes:

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

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

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

The object class Script was added.

Media 对象

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

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

资源的子集

如果你只想要特定类型的文件,你可以使用下标运算符来过滤出感兴趣的媒体。例如:

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

Media 对象也可以相互相加。当两个 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="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>

资源的排序

资源插入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="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

表单上的 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="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 声明:

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