Аксессуары форм (класс Media)
Для создания привлекательной и удобной веб-формы требуется не только HTML, но также и CSS-стили, а для использования сложных виджетов, возможно, потребуется включить JavaScript на каждой странице. Точная комбинация CSS и JavaScript, необходимая для каждой страницы, зависит от виджетов, используемых на этой странице.
Здесь и появляются определения ресурсов. Django позволяет ассоциировать различные файлы — такие как стили и скрипты — с формами и виджетами, которые нуждаются в этих ресурсах. Например, если вы хотите использовать календарь для отображения полей типа DateField, вы можете определить пользовательский виджет Calendar. Этот виджет затем можно ассоциировать с CSS и JavaScript, необходимыми для отображения календаря. Когда виджет Calendar используется в форме, 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. Каждый раз, когда виджет Calendar используется в форме, эта форма будет включена в 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-файлы для различных типов выходных данных.
Значения в словаре должны быть кортежем/списком имён файлов. Подробности о том, как указывать пути к этим файлам, см. в разделе пути.
Ключами в словаре являются типы выходных данных. Это те же типы, которые принимаются 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-файлы. Подробности о том, как указывать пути к этим файлам, см. в разделе пути.
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>
Если вам нужен более тонкий контроль над наследованием, определите свои ресурсы с помощью динамического свойства. Динамические свойства предоставляют полный контроль над тем, какие файлы наследуются, а какие — нет.
Media как динамическое свойство
Если вам нужно выполнить более сложные манипуляции с требованиями к ресурсам, вы можете определить свойство media непосредственно. Это делается путём определения свойства виджета, которое возвращает экземпляр forms.Media. Конструктор forms.Media принимает css и js ключевые аргументы в том же формате, что и в статическом определении ресурсов.
Например, статическое определение нашего виджета Calendar также можно определить динамически:
class CalendarWidget(forms.TextInput):
@property
def media(self):
return forms.Media(
css={"all": ["pretty.css"]}, js=["animations.js", "actions.js"]
)
См. раздел объекты Media для получения дополнительной информации о том, как создавать возвращаемые значения для динамических media свойств.
Пути в определениях ресурсов
Пути как строки
Строковые пути, используемые для указания ресурсов, могут быть относительными или абсолютными. Если путь начинается с /, http:// или https://, он будет интерпретирован как абсолютный путь и останется в неизменном виде. Все остальные пути будут добавлены к значению соответствующего префикса. Если приложение django.contrib.staticfiles установлено, оно будет использоваться для обслуживания ресурсов.
Независимо от использования django.contrib.staticfiles, необходимо указать настройки STATIC_URL и STATIC_ROOT для отображения полной веб-страницы.
Чтобы найти соответствующий префикс, Django проверит, не является ли настройка STATIC_URL пустой и автоматически вернётся к использованию настройки 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 пуста:
>>> 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 имеют и другие интересные свойства.
Подмножества ресурсов
Если вам нужны только файлы определённого типа, вы можете использовать оператор среза, чтобы отфильтровать интересующий носитель. Например:
>>> 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">
При использовании оператора среза возвращаемое значение — новый объект 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>
© Django Software Foundation and individual contributors
Licensed under the BSD License.
https://docs.djangoproject.com/en/5.0/topics/forms/media/