表单资源(Media
类)¶
渲染一个美观易用的 Web 表单不仅仅需要 HTML - 还需要 CSS 样式表,并且如果您想使用花哨的小部件,您可能还需要在每个页面上包含一些 JavaScript。任何给定页面所需的 CSS 和 JavaScript 的确切组合将取决于该页面上使用的小部件。
这就是资源定义的用武之地。Django 允许您将不同的文件(如样式表和脚本)与需要这些资源的表单和小部件关联。例如,如果您想使用日历来渲染 DateFields,您可以定义一个自定义的 Calendar 小部件。然后,此小部件可以与渲染日历所需的 CSS 和 JavaScript 相关联。当 Calendar 小部件在表单上使用时,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="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 文件的元组。有关如何指定这些文件路径的详细信息,请参见 路径部分。
extend
¶
一个布尔值,定义 Media
声明的继承行为。
默认情况下,任何使用静态 Media
定义的对象都将继承与父小部件关联的所有资源。无论父级如何定义其自身需求,都会发生这种情况。例如,如果我们要从上面示例中的基本 Calendar 小部件扩展
>>> 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
的构造函数接受 css
和 js
关键字参数,其格式与静态媒体定义中使用的格式相同。
例如,我们 Calendar 小部件的静态定义也可以以动态方式定义
class CalendarWidget(forms.TextInput):
@property
def media(self):
return forms.Media(
css={"all": ["pretty.css"]}, js=["animations.js", "actions.js"]
)
有关如何为动态 media
属性构造返回值的更多详细信息,请参见 Media 对象 部分。
资源定义中的路径¶
作为字符串的路径¶
用于指定资源的字符串路径可以是相对路径或绝对路径。如果路径以 /
、http://
或 https://
开头,它将被解释为绝对路径,并保持原样。所有其他路径都将在前面加上相应前缀的值。如果安装了 django.contrib.staticfiles
应用程序,它将用于提供资源。
无论您是否使用 django.contrib.staticfiles
,STATIC_URL
和 STATIC_ROOT
设置对于渲染完整的网页是必需的。
为了找到要使用的适当前缀,Django 将检查 STATIC_URL
设置是否不为 None
并自动回退到使用 MEDIA_URL
。例如,如果站点的 MEDIA_URL
为 'https://uploads.example.com/'
且 STATIC_URL
为 None
>>> 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>
或者如果 staticfiles
使用 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="https://othersite.com/actions.js"></script>
作为对象的路径¶
资源路径也可以作为实现 __html__()
方法的可散列对象给出。__html__()
方法通常使用 html_safe()
装饰器添加。该对象负责输出完整的 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" defer>'
...
>>> class SomeWidget(forms.TextInput):
... class Media:
... js = [JSPath()]
...
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
定义的规则与小部件的规则相同:声明可以是静态的或动态的;这些声明的路径和继承规则完全相同。
无论您是否定义 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>