Активы формы (класс Media
)¶
Для создания привлекательной и простой в использовании веб-формы требуется не только HTML, но и таблицы стилей CSS, а если вы хотите использовать причудливые виджеты, то вам также может потребоваться включить JavaScript на каждой странице. Точное сочетание CSS и JavaScript, необходимое для каждой конкретной страницы, зависит от того, какие виджеты используются на этой странице.
Именно здесь на помощь приходят определения активов. Django позволяет вам связывать различные файлы - например, таблицы стилей и скрипты - с формами и виджетами, которые требуют этих активов. Например, если вы хотите использовать календарь для отображения DateFields, вы можете определить пользовательский виджет Calendar. Этот виджет может быть связан с CSS и JavaScript, необходимыми для отображения календаря. Когда виджет Calendar используется на форме, Django может определить необходимые CSS и JavaScript файлы и предоставить список имен файлов в форме, подходящей для включения на вашу веб-страницу.
Активы и администратор Django
Приложение 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="http://static.example.com/pretty.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, необходимые для различных форм средств вывода.
Значения в словаре должны представлять собой кортеж/список имен файлов. Смотрите the section on paths для получения подробной информации о том, как указать пути к этим файлам.
Ключи в словаре - это типы выводимых медиа. Это те же типы, которые принимаются файлами 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" media="screen" rel="stylesheet">
<link href="http://static.example.com/lo_res.css" media="tv,projector" rel="stylesheet">
<link href="http://static.example.com/newspaper.css" media="print" rel="stylesheet">
js
¶
Кортеж, описывающий необходимые файлы JavaScript. Смотрите the section on paths для получения подробной информации о том, как указать пути к этим файлам.
extend
¶
Булево значение, определяющее поведение наследования для объявлений Media
.
По умолчанию любой объект, использующий статическое определение Media
, наследует все активы, связанные с родительским виджетом. Это происходит независимо от того, как родитель определяет свои собственные требования. Например, если мы расширим наш базовый виджет Calendar из приведенного выше примера:
>>> 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" media="all" rel="stylesheet">
<link href="http://static.example.com/fancy.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
наследовался таким образом, добавьте объявление 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" media="all" rel="stylesheet">
<script src="http://static.example.com/whizbang.js"></script>
Если вам требуется еще больший контроль над наследованием, определите свои активы с помощью dynamic property. Динамические свойства дают вам полный контроль над тем, какие файлы наследуются, а какие нет.
Media
как динамическое свойство¶
Если вам необходимо выполнить более сложные манипуляции с требованиями к активам, вы можете определить свойство media
напрямую. Это делается путем определения свойства виджета, которое возвращает экземпляр forms.Media
. Конструктор для forms.Media
принимает css
и js
аргументы ключевых слов в том же формате, который используется в статическом определении медиа.
Например, статическое определение для нашего виджета Calendar Widget может быть также определено динамически:
class CalendarWidget(forms.TextInput):
@property
def media(self):
return forms.Media(
css={"all": ["pretty.css"]}, js=["animations.js", "actions.js"]
)
Более подробно о том, как строить возвращаемые значения для динамических свойств Media objects см. раздел media
.
Пути в определениях активов¶
Пути как строки¶
Строковые пути, используемые для указания активов, могут быть как относительными, так и абсолютными. Если путь начинается с /
, http://
или https://
, он будет интерпретирован как абсолютный путь и оставлен как есть. Ко всем остальным путям будет добавлено значение соответствующего префикса. Если установлено приложение django.contrib.staticfiles
, оно будет использоваться для обслуживания активов.
Независимо от того, используете ли вы django.contrib.staticfiles
, параметры STATIC_URL
и STATIC_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" 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" 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" media="all" rel="stylesheet">
<script src="https://static.example.com/animations.27e20196a850.js"></script>
<script src="http://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" rel="stylesheet">'
...
>>> class SomeWidget(forms.TextInput):
... class Media:
... js = [JSPath()]
...
Media
объекты¶
Когда вы опрашиваете атрибут media
виджета или формы, возвращаемое значение - это объект forms.Media
. Как мы уже видели, строковое представление объекта Media
- это HTML, необходимый для включения соответствующих файлов в блок <head>
вашей HTML-страницы.
Однако у объектов Media
есть и другие интересные свойства.
Подмножества активов¶
Если вам нужны файлы только определенного типа, вы можете использовать оператор subscript для отсеивания интересующего вас носителя. Например:
>>> w = CalendarWidget()
>>> print(w.media)
<link href="http://static.example.com/pretty.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" media="all" rel="stylesheet">
При использовании оператора subscript возвращаемое значение представляет собой новый объект 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" 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
, все объекты 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="http://static.example.com/pretty.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" media="all" rel="stylesheet">
<link href="http://static.example.com/layout.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>