表单资源( 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="https://docs.djangoproject.com/zh-hans/6.1/topics/forms/media/%22https://static.example.com/pretty.css%22" media="all" rel="stylesheet">
<script src="https://docs.djangoproject.com/zh-hans/6.1/topics/forms/media/%22https://static.example.com/animations.js%22%3E%3C/script%3E%3C/span">
<script src="https://docs.djangoproject.com/zh-hans/6.1/topics/forms/media/%22https://static.example.com/actions.js%22%3E%3C/script%3E%3C/span">

以下是所有可能的 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">

Stylesheet 对象

Django 6.1 新增。
class Stylesheet(href, **attributes)[source]

表示一个样式表链接。rel 属性被设为 "stylesheet"。

第一个参数 href 是样式表文件的字符串路径。文件路径的指定方法详见资源定义中的路径。

可选关键字参数 **attributes 是设置在渲染后的 <link> 标签上的额外 HTML 属性。

用法示例见路径作为对象。

js

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

Script 对象

class Script(src, **attributes)[source]

表示一个脚本文件。

第一个参数 src 是脚本文件的字符串路径。指定路径的方法详见资源定义中的路径。

可选关键字参数 **attributes 是设置在渲染后的 <script> 标签上的 HTML 属性。

用法示例见路径作为对象。

extend

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

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

>>> class FancyCalendarWidget(CalendarWidget):
...     class Media:
...         css = {
...             "all": ["fancy.css"],
...         }
...         js = ["whizbang.js"]
...

>>> w = FancyCalendarWidget()
>>> print(w.media)
<link href="https://docs.djangoproject.com/zh-hans/6.1/topics/forms/media/%22https://static.example.com/pretty.css%22" media="all" rel="stylesheet">
<link href="https://docs.djangoproject.com/zh-hans/6.1/topics/forms/media/%22https://static.example.com/fancy.css%22" media="all" rel="stylesheet">
<script src="https://docs.djangoproject.com/zh-hans/6.1/topics/forms/media/%22https://static.example.com/animations.js%22%3E%3C/script%3E%3C/span">
<script src="https://docs.djangoproject.com/zh-hans/6.1/topics/forms/media/%22https://static.example.com/actions.js%22%3E%3C/script%3E%3C/span">
<script src="https://docs.djangoproject.com/zh-hans/6.1/topics/forms/media/%22https://static.example.com/whizbang.js%22%3E%3C/script%3E%3C/span">

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://docs.djangoproject.com/zh-hans/6.1/topics/forms/media/%22https://static.example.com/fancy.css%22" media="all" rel="stylesheet">
<script src="https://docs.djangoproject.com/zh-hans/6.1/topics/forms/media/%22https://static.example.com/whizbang.js%22%3E%3C/script%3E%3C/span">

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

把 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 是 '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="https://docs.djangoproject.com/zh-hans/6.1/topics/forms/media/%22/css/pretty.css%22" media="all" rel="stylesheet">
<script src="https://docs.djangoproject.com/zh-hans/6.1/topics/forms/media/%22https://uploads.example.com/animations.js%22%3E%3C/script%3E%3C/span">
<script src="https://docs.djangoproject.com/zh-hans/6.1/topics/forms/media/%22https://othersite.com/actions.js%22%3E%3C/script%3E%3C/span">

但如果 STATIC_URL 是 'https://static.example.com/':

>>> w = CalendarWidget()
>>> print(w.media)
<link href="https://docs.djangoproject.com/zh-hans/6.1/topics/forms/media/%22/css/pretty.css%22" media="all" rel="stylesheet">
<script src="https://docs.djangoproject.com/zh-hans/6.1/topics/forms/media/%22https://static.example.com/animations.js%22%3E%3C/script%3E%3C/span">
<script src="https://docs.djangoproject.com/zh-hans/6.1/topics/forms/media/%22https://othersite.com/actions.js%22%3E%3C/script%3E%3C/span">

或者如果使用 ManifestStaticFilesStorage 配置了 staticfiles:

>>> w = CalendarWidget()
>>> print(w.media)
<link href="https://docs.djangoproject.com/zh-hans/6.1/topics/forms/media/%22/css/pretty.css%22" media="all" rel="stylesheet">
<script src="https://docs.djangoproject.com/zh-hans/6.1/topics/forms/media/%22https://static.example.com/animations.27e20196a850.js%22%3E%3C/script%3E%3C/span">
<script src="https://docs.djangoproject.com/zh-hans/6.1/topics/forms/media/%22https://othersite.com/actions.js%22%3E%3C/script%3E%3C/span">

路径作为对象

资源也可以用 Script 或 Stylesheet 对象表示。它们允许传入自定义 HTML 属性:

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

渲染这个 Media 定义后,会得到以下 HTML:

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

同样,可以通过 Stylesheet 对象添加带有自定义 HTML 属性的样式表链接:

class Media:
    css = {
        "all": [
            Stylesheet(
                "https://cdn.example.com/print.css",
                crossorigin="anonymous",
                media="print",
            ),
        ]
    }

渲染这个 Media 定义后,会得到:

<link href="https://cdn.example.com/print.css"
      crossorigin="anonymous"
      media="print"
      rel="stylesheet">

Media 对象

class Media[source]

表示部件或表单所需的一组媒体资源。

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

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

资源的子集

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

>>> w = CalendarWidget()
>>> print(w.media)
<link href="https://docs.djangoproject.com/zh-hans/6.1/topics/forms/media/%22https://static.example.com/pretty.css%22" media="all" rel="stylesheet">
<script src="https://docs.djangoproject.com/zh-hans/6.1/topics/forms/media/%22https://static.example.com/animations.js%22%3E%3C/script%3E%3C/span">
<script src="https://docs.djangoproject.com/zh-hans/6.1/topics/forms/media/%22https://static.example.com/actions.js%22%3E%3C/script%3E%3C/span">

>>> print(w.media["css"])
<link href="https://docs.djangoproject.com/zh-hans/6.1/topics/forms/media/%22https://static.example.com/pretty.css%22" 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://docs.djangoproject.com/zh-hans/6.1/topics/forms/media/%22https://static.example.com/pretty.css%22" media="all" rel="stylesheet">
<script src="https://docs.djangoproject.com/zh-hans/6.1/topics/forms/media/%22https://static.example.com/animations.js%22%3E%3C/script%3E%3C/span">
<script src="https://docs.djangoproject.com/zh-hans/6.1/topics/forms/media/%22https://static.example.com/actions.js%22%3E%3C/script%3E%3C/span">
<script src="https://docs.djangoproject.com/zh-hans/6.1/topics/forms/media/%22https://static.example.com/whizbang.js%22%3E%3C/script%3E%3C/span">

资源的排序

资源插入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://docs.djangoproject.com/zh-hans/6.1/topics/forms/media/%22https://static.example.com/jQuery.js%22%3E%3C/script%3E%3C/span">
<script src="https://docs.djangoproject.com/zh-hans/6.1/topics/forms/media/%22https://static.example.com/calendar.js%22%3E%3C/script%3E%3C/span">
<script src="https://docs.djangoproject.com/zh-hans/6.1/topics/forms/media/%22https://static.example.com/time.js%22%3E%3C/script%3E%3C/span">
<script src="https://docs.djangoproject.com/zh-hans/6.1/topics/forms/media/%22https://static.example.com/noConflict.js%22%3E%3C/script%3E%3C/span">

合并 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://docs.djangoproject.com/zh-hans/6.1/topics/forms/media/%22https://static.example.com/pretty.css%22" media="all" rel="stylesheet">
<script src="https://docs.djangoproject.com/zh-hans/6.1/topics/forms/media/%22https://static.example.com/animations.js%22%3E%3C/script%3E%3C/span">
<script src="https://docs.djangoproject.com/zh-hans/6.1/topics/forms/media/%22https://static.example.com/actions.js%22%3E%3C/script%3E%3C/span">
<script src="https://docs.djangoproject.com/zh-hans/6.1/topics/forms/media/%22https://static.example.com/whizbang.js%22%3E%3C/script%3E%3C/span">

如果你想将额外的资源与一个表单关联起来,例如,表单布局的 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://docs.djangoproject.com/zh-hans/6.1/topics/forms/media/%22https://static.example.com/pretty.css%22" media="all" rel="stylesheet">
<link href="https://docs.djangoproject.com/zh-hans/6.1/topics/forms/media/%22https://static.example.com/layout.css%22" media="all" rel="stylesheet">
<script src="https://docs.djangoproject.com/zh-hans/6.1/topics/forms/media/%22https://static.example.com/animations.js%22%3E%3C/script%3E%3C/span">
<script src="https://docs.djangoproject.com/zh-hans/6.1/topics/forms/media/%22https://static.example.com/actions.js%22%3E%3C/script%3E%3C/span">
<script src="https://docs.djangoproject.com/zh-hans/6.1/topics/forms/media/%22https://static.example.com/whizbang.js%22%3E%3C/script%3E%3C/span">

来源与许可

原文:Django 6.1 官方中文文档,Django Software Foundation 及贡献者。BSD 3-Clause 许可。本文保留官方中文正文,并补译尚未汉化的段落。Django 6.1 新增 Stylesheet 的版本标记保留。

官方原文 · 许可证与版权声明

原文参考链接

许可证原文

Copyright (c) Django Software Foundation and individual contributors.
All rights reserved.

Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.

3. Neither the name of Django nor the names of its contributors may be used
to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容