表单资源( 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 类。此内部类的属性定义了这个需求。

这有个例子:

  1. from django import forms
  2. class CalendarWidget(forms.TextInput):
  3. class Media:
  4. css = {
  5. 'all': ('pretty.css',)
  6. }
  7. js = ('animations.js', 'actions.js')

这段代码定义了一个 CalendarWidget ,它继承自 TextInput 。每次CalendarWidget在表单上使用时,该表单都会包含CSS文件 pretty.css ,以及JavaScript文件 animations.jsactions.js

这个静态定义在运行时被转换成名为 media 的组件属性。 CalendarWidget 实例的资源列表可以通过这个属性获得:

  1. >>> w = CalendarWidget()
  2. >>> print(w.media)
  3. <link href="http://static.example.com/pretty.css" media="all" rel="stylesheet">
  4. <script src="http://static.example.com/animations.js"></script>
  5. <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选项——一个用于屏幕,一个用于打印:

  1. class Media:
  2. css = {
  3. 'screen': ('pretty.css',),
  4. 'print': ('newspaper.css',)
  5. }

如果一组CSS文件适用于多种输出媒体类型,字典的键可以是以逗号分隔的输出媒体类型列表。在下面的例子中,电视和投影机将具有相同的媒体需求:

  1. class Media:
  2. css = {
  3. 'screen': ('pretty.css',),
  4. 'tv,projector': ('lo_res.css',),
  5. 'print': ('newspaper.css',)
  6. }

如果最后的这个CSS定义被渲染,它将成为下面的HTML:

  1. <link href="http://static.example.com/pretty.css" media="screen" rel="stylesheet">
  2. <link href="http://static.example.com/lo_res.css" media="tv,projector" rel="stylesheet">
  3. <link href="http://static.example.com/newspaper.css" media="print" rel="stylesheet">

Changed in Django 4.1:

In older versions, the type="text/css" attribute is included in CSS links.

js

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

extend

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

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

  1. >>> class FancyCalendarWidget(CalendarWidget):
  2. ... class Media:
  3. ... css = {
  4. ... 'all': ('fancy.css',)
  5. ... }
  6. ... js = ('whizbang.js',)
  7. >>> w = FancyCalendarWidget()
  8. >>> print(w.media)
  9. <link href="http://static.example.com/pretty.css" media="all" rel="stylesheet">
  10. <link href="http://static.example.com/fancy.css" media="all" rel="stylesheet">
  11. <script src="http://static.example.com/animations.js"></script>
  12. <script src="http://static.example.com/actions.js"></script>
  13. <script src="http://static.example.com/whizbang.js"></script>

FancyCalendar组件从其父组件继承所有资源。如果您不想用这种方式继承 Media ,要在 Media 声明中添加一个 extend=False 声明:

  1. >>> class FancyCalendarWidget(CalendarWidget):
  2. ... class Media:
  3. ... extend = False
  4. ... css = {
  5. ... 'all': ('fancy.css',)
  6. ... }
  7. ... js = ('whizbang.js',)
  8. >>> w = FancyCalendarWidget()
  9. >>> print(w.media)
  10. <link href="http://static.example.com/fancy.css" media="all" rel="stylesheet">
  11. <script src="http://static.example.com/whizbang.js"></script>

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

Media 作为动态属性

如果您需要执行一些更复杂的资源需求操作,你可以直接定义 media 属性。这是通过定义一个返回 forms.Media 实例的组件属性来实现的。这个 forms.Media 的构造函数接受 cssjs 关键字参数,与静态媒体定义中使用的格式相同。

例如,我们也可以以动态的方式定义日历组件的静态定义:

  1. class CalendarWidget(forms.TextInput):
  2. @property
  3. def media(self):
  4. return forms.Media(css={'all': ('pretty.css',)},
  5. js=('animations.js', 'actions.js'))

更多有关如何为动态 media 属性构建返回值的内容,请参阅 媒体对象 章节。

资源定义中的路径

Paths as strings

String paths used to specify assets can be either relative or absolute. If a path starts with /, http:// or https://, it will be interpreted as an absolute path, and left as-is. All other paths will be prepended with the value of the appropriate prefix. If the django.contrib.staticfiles app is installed, it will be used to serve assets.

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

为了找到相应的前缀来使用,Django会去检查 STATIC_URL 是否不为 None ,并自动回退使用 MEDIA_URL 。例如,您的网站的 MEDIA_URL 设置为 'http://uploads.example.com/'STATIC_URL 设置是 None

  1. >>> from django import forms
  2. >>> class CalendarWidget(forms.TextInput):
  3. ... class Media:
  4. ... css = {
  5. ... 'all': ('/css/pretty.css',),
  6. ... }
  7. ... js = ('animations.js', 'http://othersite.com/actions.js')
  8. >>> w = CalendarWidget()
  9. >>> print(w.media)
  10. <link href="/css/pretty.css" media="all" rel="stylesheet">
  11. <script src="http://uploads.example.com/animations.js"></script>
  12. <script src="http://othersite.com/actions.js"></script>

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

  1. >>> w = CalendarWidget()
  2. >>> print(w.media)
  3. <link href="/css/pretty.css" media="all" rel="stylesheet">
  4. <script src="http://static.example.com/animations.js"></script>
  5. <script src="http://othersite.com/actions.js"></script>

或者如果 staticfiles 配置使用 ManifestStaticFilesStorage

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

Paths as objects

New in Django 4.1.

Asset paths may also be given as hashable objects implementing an __html__() method. The __html__() method is typically added using the html_safe() decorator. The object is responsible for outputting the complete HTML <script> or <link> tag content:

  1. >>> from django import forms
  2. >>> from django.utils.html import html_safe
  3. >>>
  4. >>> @html_safe
  5. >>> class JSPath:
  6. ... def __str__(self):
  7. ... return '<script src="https://example.org/asset.js" rel="stylesheet">'
  8. >>> class SomeWidget(forms.TextInput):
  9. ... class Media:
  10. ... js = (JSPath(),)

Media 对象

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

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

资源的子集

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

  1. >>> w = CalendarWidget()
  2. >>> print(w.media)
  3. <link href="http://static.example.com/pretty.css" media="all" rel="stylesheet">
  4. <script src="http://static.example.com/animations.js"></script>
  5. <script src="http://static.example.com/actions.js"></script>
  6. >>> print(w.media['css'])
  7. <link href="http://static.example.com/pretty.css" media="all" rel="stylesheet">

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

合并 Media 对象

Media 对象也可以添加到一起。当添加两个 Media 对象时,生成的 Media 对象包含两者指定的资源的并集:

  1. >>> from django import forms
  2. >>> class CalendarWidget(forms.TextInput):
  3. ... class Media:
  4. ... css = {
  5. ... 'all': ('pretty.css',)
  6. ... }
  7. ... js = ('animations.js', 'actions.js')
  8. >>> class OtherWidget(forms.TextInput):
  9. ... class Media:
  10. ... js = ('whizbang.js',)
  11. >>> w1 = CalendarWidget()
  12. >>> w2 = OtherWidget()
  13. >>> print(w1.media + w2.media)
  14. <link href="http://static.example.com/pretty.css" media="all" rel="stylesheet">
  15. <script src="http://static.example.com/animations.js"></script>
  16. <script src="http://static.example.com/actions.js"></script>
  17. <script src="http://static.example.com/whizbang.js"></script>

资源的排序

资源插入DOM的顺序一般来说很重要。例如,您可能有一个依赖于jQuery的脚本。因此,合并 Media 对象会尝试保持资源在每个 Media 类中定义的相对顺序。

例如:

  1. >>> from django import forms
  2. >>> class CalendarWidget(forms.TextInput):
  3. ... class Media:
  4. ... js = ('jQuery.js', 'calendar.js', 'noConflict.js')
  5. >>> class TimeWidget(forms.TextInput):
  6. ... class Media:
  7. ... js = ('jQuery.js', 'time.js', 'noConflict.js')
  8. >>> w1 = CalendarWidget()
  9. >>> w2 = TimeWidget()
  10. >>> print(w1.media + w2.media)
  11. <script src="http://static.example.com/jQuery.js"></script>
  12. <script src="http://static.example.com/calendar.js"></script>
  13. <script src="http://static.example.com/time.js"></script>
  14. <script src="http://static.example.com/noConflict.js"></script>

合并 Media 对象时,如果资源排序冲突,会导致警告提示: MediaOrderConflictWarning

表单上的 Media

组件不是唯一可以具有 media 定义的对象——表单也可以。表单上 media 定义的规则与组件的规则相同:声明可以是静态的或动态的;声明的路径和继承规则也一模一样。

无论您是否定义了 media 声明,*所有*表单对象都有一个 media 属性。该属性的默认值是这个表单的所有组件添加 media 定义的结果:

  1. >>> from django import forms
  2. >>> class ContactForm(forms.Form):
  3. ... date = DateField(widget=CalendarWidget)
  4. ... name = CharField(max_length=40, widget=OtherWidget)
  5. >>> f = ContactForm()
  6. >>> f.media
  7. <link href="http://static.example.com/pretty.css" media="all" rel="stylesheet">
  8. <script src="http://static.example.com/animations.js"></script>
  9. <script src="http://static.example.com/actions.js"></script>
  10. <script src="http://static.example.com/whizbang.js"></script>

如果您想将其他资源与表单关联起来——例如,表单布局的CSS——只要向表单添加 Media 声明:

  1. >>> class ContactForm(forms.Form):
  2. ... date = DateField(widget=CalendarWidget)
  3. ... name = CharField(max_length=40, widget=OtherWidget)
  4. ...
  5. ... class Media:
  6. ... css = {
  7. ... 'all': ('layout.css',)
  8. ... }
  9. >>> f = ContactForm()
  10. >>> f.media
  11. <link href="http://static.example.com/pretty.css" media="all" rel="stylesheet">
  12. <link href="http://static.example.com/layout.css" media="all" rel="stylesheet">
  13. <script src="http://static.example.com/animations.js"></script>
  14. <script src="http://static.example.com/actions.js"></script>
  15. <script src="http://static.example.com/whizbang.js"></script>