表单资源( Media 类)

Rendering an attractive and easy-to-use web form requires more than just HTML - it also requires CSS stylesheets, and if you want to use fancy widgets, you may also need to include some JavaScript on each page. The exact combination of CSS and JavaScript that is required for any given page will depend upon the widgets that are in use on that page.

This is where asset definitions come in. Django allows you to associate different files -- like stylesheets and scripts -- with the forms and widgets that require those assets. For example, if you want to use a calendar to render DateFields, you can define a custom Calendar widget. This widget can then be associated with the CSS and JavaScript that is required to render the calendar. When the Calendar widget is used on a form, Django is able to identify the CSS and JavaScript files that are required, and provide the list of file names in a form suitable for inclusion on your web page.

资源及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="http://static.example.com/pretty.css" type="text/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" 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文件的一个元组。有关如何指定这些文件的路径的详细内容,请参阅 路径章节

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

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

Media 作为动态属性

如果您需要执行一些更复杂的资源需求操作,你可以直接定义 media 属性。这是通过定义一个返回 forms.Media 实例的组件属性来实现的。这个 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:// 开头,则它将被解释为绝对路径,并保持原样。所有其他路径都会以相应值作为前缀。如果在settings中设置了 django.contrib.staticfiles ,就可以用来服务于资源。

无论您是否使用 django.contrib.staticfiles ,都需要设置 STATIC_URLSTATIC_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" type="text/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" type="text/css" media="all" rel="stylesheet">
<script src="http://static.example.com/animations.js"></script>
<script src="http://othersite.com/actions.js"></script>

或者如果 staticfiles 配置使用 ManifestStaticFilesStorage

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

Media 对象

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

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

资源的子集

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

>>> w = CalendarWidget()
>>> print(w.media)
<link href="http://static.example.com/pretty.css" type="text/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" type="text/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" type="text/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 声明,*所有*表单对象都有一个 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" type="text/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" type="text/css" media="all" rel="stylesheet">
<link href="http://static.example.com/layout.css" type="text/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>