表单资产(Media类)

渲染一个有吸引力且易于使用的Web表单需要的不仅仅是HTML - 它还需要CSS样式表,如果你想使用花哨的“Web2.0”小部件,你可能还需要在每个页面上包含一些JavaScript。 任何给定页面所需的CSS和JavaScript的确切组合将取决于该页面上正在使用的小部件。

这是资产定义的来源。 Django允许您将不同的文件(如样式表和脚本)与需要这些资源的表单和小部件相关联。 例如,如果要使用日历呈现DateField,则可以定义一个自定义日历小部件。 然后,这个小部件可以与呈现日历所需的CSS和JavaScript相关联。 在表单上使用Calendar小部件时,Django能够识别所需的CSS和JavaScript文件,并以适合于容易包含在Web页面中的形式提供文件名列表。

资产和Django管理员

Django Admin应用程序为日历定义了一些定制的窗口小部件,过滤的选择等等。 这些小部件定义资产需求,Django Admin使用自定义小部件来代替Django的默认值。 管理员模板将只包含在任何给定页面上呈现小部件所需的文件。

如果您喜欢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="http://static.example.com/pretty.css" type="text/css" media="all" rel="stylesheet" />
<script type="text/javascript" src="http://static.example.com/animations.js"></script>
<script type="text/javascript" src="http://static.example.com/actions.js"></script>

以下是所有可能的Media选项的列表。 没有必要的选项。

css

描述各种形式的输出媒体所需的CSS文件的字典。

字典中的值应该是一个元组/文件名列表。 有关如何指定这些文件路径的详细信息,请参阅the section on paths

字典中的键是输出媒体类型。 这些与CSS文件在媒体声明中所接受的类型相同:“全部”,“听觉”,“盲文”,“压印”,“掌上”,“打印”,“投影”,“屏幕”,“电视'。 如果您需要针对不同媒体类型使用不同的样式表,请为每个输出媒体提供一个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" type="text/css" media="screen" rel="stylesheet" />
<link href="http://static.example.com/lo_res.css" type="text/css" media="tv,projector" rel="stylesheet" />
<link href="http://static.example.com/newspaper.css" type="text/css" media="print" rel="stylesheet" />

js

描述所需JavaScript文件的元组。 有关如何指定这些文件路径的详细信息,请参阅the section on paths

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" type="text/css" media="all" rel="stylesheet" />
<link href="http://static.example.com/fancy.css" type="text/css" media="all" rel="stylesheet" />
<script type="text/javascript" src="http://static.example.com/animations.js"></script>
<script type="text/javascript" src="http://static.example.com/actions.js"></script>
<script type="text/javascript" src="http://static.example.com/whizbang.js"></script>

FancyCalendar小部件从其父部件继承所有资产。 如果您不希望以这种方式继承Media,请将extend=False声明添加到Media声明中:

>>> 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" type="text/css" media="all" rel="stylesheet" />
<script type="text/javascript" src="http://static.example.com/whizbang.js"></script>

如果您需要对继承进行更多的控制,请使用dynamic property定义您的资产。 动态属性使您可以完全控制哪些文件被继承,哪些不是。

Media作为动态属性

如果您需要执行一些更复杂的资产需求操作,则可以直接定义media属性。 这是通过定义一个返回forms.Media实例的widget属性来完成的。 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://开始,则会被解释为绝对路径,并保留为-is。 所有其他路径将被预先设置适当前缀的值。 如果安装了django.contrib.staticfiles应用程序,它将用于提供资产。

无论您是否使用django.contrib.staticfiles,都需要使用STATIC_URLSTATIC_ROOT设置来呈现完整的网页。

为了找到合适的前缀,Django将检查STATIC_URL设置是否不是None,并自动回退到使用MEDIA_URL 例如,如果您网站的MEDIA_URL'http://uploads.example.com/'STATIC_URLNone

>>> 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" type="text/css" media="all" rel="stylesheet" />
<script type="text/javascript" src="http://uploads.example.com/animations.js"></script>
<script type="text/javascript" src="http://othersite.com/actions.js"></script>

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

>>> w = CalendarWidget()
>>> print(w.media)
<link href="/css/pretty.css" type="text/css" media="all" rel="stylesheet" />
<script type="text/javascript" src="http://static.example.com/animations.js"></script>
<script type="text/javascript" src="http://othersite.com/actions.js"></script>

或者,如果使用ManifestStaticFilesStorage配置staticfiles

>>> w = CalendarWidget()
>>> print(w.media)
<link href="/css/pretty.css" type="text/css" media="all" rel="stylesheet" />
<script type="text/javascript" src="https://static.example.com/animations.27e20196a850.js"></script>
<script type="text/javascript" src="http://othersite.com/actions.js"></script>

Media对象

当您查询小部件或窗体的media属性时,返回的值是forms.Media对象。 正如我们已经看到的,一个Media对象的字符串表示形式是将相关文件包含在HTML页面的<head>块中所需的HTML。

However, Media objects have some other interesting properties.

资产的子集

如果您只想要特定类型的文件,则可以使用下标运算符筛选出感兴趣的介质。 例如:

>>> w = CalendarWidget()
>>> print(w.media)
<link href="http://static.example.com/pretty.css" type="text/css" media="all" rel="stylesheet" />
<script type="text/javascript" src="http://static.example.com/animations.js"></script>
<script type="text/javascript" src="http://static.example.com/actions.js"></script>

>>> print(w.media['css'])
<link href="http://static.example.com/pretty.css" type="text/css" media="all" rel="stylesheet" />

当您使用下标运算符时,返回的值是一个新的Media对象 - 但只包含感兴趣的媒体。

结合Media对象

Media objects can also be added together. 当添加两个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" type="text/css" media="all" rel="stylesheet" />
<script type="text/javascript" src="http://static.example.com/animations.js"></script>
<script type="text/javascript" src="http://static.example.com/actions.js"></script>
<script type="text/javascript" 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 type="text/javascript" src="http://static.example.com/jQuery.js"></script>
<script type="text/javascript" src="http://static.example.com/calendar.js"></script>
<script type="text/javascript" src="http://static.example.com/time.js"></script>
<script type="text/javascript" src="http://static.example.com/noConflict.js"></script>

Media对象与冲突顺序的资产组合会导致MediaOrderConflictWarning

在Django 2.0中更改:

在较早的版本中,Media对象的资源被连接在一起,而不是以试图保持每个列表中元素的相对顺序的方式合并。

Media表格

小部件不是唯一可以具有media定义的对象 - 表单也可以定义media 表单上media定义的规则与小部件的规则相同:声明可以是静态的也可以是动态的;这些声明的路径和继承规则是完全一样的。

Regardless of whether you define a media declaration, all Form objects have a media property. 此属性的默认值是为属于表单的所有小部件添加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" type="text/css" media="all" rel="stylesheet" />
<script type="text/javascript" src="http://static.example.com/animations.js"></script>
<script type="text/javascript" src="http://static.example.com/actions.js"></script>
<script type="text/javascript" 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" type="text/css" media="all" rel="stylesheet" />
<link href="http://static.example.com/layout.css" type="text/css" media="all" rel="stylesheet" />
<script type="text/javascript" src="http://static.example.com/animations.js"></script>
<script type="text/javascript" src="http://static.example.com/actions.js"></script>
<script type="text/javascript" src="http://static.example.com/whizbang.js"></script>