表单资源(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.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 文件的元组。有关如何指定这些文件路径的详细信息,请参见 路径部分

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

例如,我们 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.staticfilesSTATIC_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>

或者如果 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>
返回顶部