表单资源( 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.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文件列表。下面的示例提供了两个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="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:
在旧版本中,CSS 链接中包含 type="text/css"
属性。
js
描述所需JavaScript文件的一个元组。有关如何指定这些文件的路径的详细内容,请参阅 路径章节 。
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
关键字参数,与静态媒体定义中使用的格式相同。
例如,我们也可以以动态的方式定义日历组件的静态定义:
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_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>
或者如果使用 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="http://othersite.com/actions.js"></script>
路径作为对象
New in Django 4.1.
资产路径也可以作为实现了 __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" rel="stylesheet">'
...
>>> class SomeWidget(forms.TextInput):
... class Media:
... js = [JSPath()]
...
Media
对象
当您访问表单或者组件的 media
属性时,返回值是一个 forms.Media
对象。正如我们已经看到的, Media
对象的字符串表示是一段需要在您HTML页面的 <head>
块中包含相关文件的HTML代码。
然而, 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
对象也可以相互相加。当两个 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
声明,所有 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" 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>