Статические файлы форм (класс Media)¶
Для создания привлекательной и простой в использовании веб-формы требуется больше, чем просто HTML — для этого также требуются таблицы стилей CSS, и если вы хотите использовать необычные виджеты, вам также может потребоваться включить немного JavaScript на каждую страницу. Точная комбинация CSS и JavaScript, необходимая для каждой конкретной страницы, будет зависеть от виджетов, которые используются на этой странице.
Здесь на помощь приходят определения ресурсов. Django позволяет связывать различные файлы, например таблицы стилей и скрипты, с формами и виджетами, которым требуются эти ресурсы. Например, если вы хотите использовать календарь для отображения DateFields, вы можете определить собственный виджет «Календарь». Затем этот виджет можно связать с CSS и JavaScript, необходимыми для отображения календаря. Когда виджет «Календарь» используется в форме, Django может идентифицировать необходимые файлы CSS и JavaScript и предоставить список имен файлов в форме, подходящей для включения на вашу веб-страницу.
Статические файлы интерфейса администратора Django
Поставляемое с Django приложение интерфейса администратора определяет ряд собственных виджетов для календарей, отфильтрованных выборок и так далее. Эти виджеты имеют свои требования к ресурсам. Шаблоны приложения подключают соответствующие файлы, которые необходимы для работы виджетов на странице.
Если вам понравились виджеты интерфейса администратора, то используйте их в своих приложениях. Они все расположены в 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="https://static.example.com/pretty.css" media="all" rel="stylesheet">
<script src="https://static.example.com/animations.js"></script>
<script src="https://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="https://static.example.com/pretty.css" media="screen" rel="stylesheet">
<link href="https://static.example.com/lo_res.css" media="tv,projector" rel="stylesheet">
<link href="https://static.example.com/newspaper.css" media="print" rel="stylesheet">
js¶
Кортеж, описывающий необходимые JavaScript файлы. Обратитесь к соответствующему разделу для получения информации о правилах определения путей до файлов ресурсов.
Объекты Script¶
- class Script(src, **attributes)¶
Представляет файл сценария.
Первый параметр, src, представляет собой строковый путь к файлу сценария. См. раздел о путях для получения подробной информации о том, как указать пути к этим файлам.
Необязательные аргументы ключевого слова,
**attributes, представляют собой атрибуты HTML, которые устанавливаются в отображаемом теге<script>.См. примеры использования в Пути как объекты.
extend¶
Булево значение, определяющее, производится ли наследование Media базового класса.
По умолчанию любой объект, использующий статическое определение «Медиа», унаследует все ресурсы, связанные с родительским виджетом. Это происходит независимо от того, как родитель определяет свои собственные требования. Например, если бы мы расширили наш базовый виджет «Календарь» из примера выше:
>>> class FancyCalendarWidget(CalendarWidget):
... class Media:
... css = {
... "all": ["fancy.css"],
... }
... js = ["whizbang.js"]
...
>>> w = FancyCalendarWidget()
>>> print(w.media)
<link href="https://static.example.com/pretty.css" media="all" rel="stylesheet">
<link href="https://static.example.com/fancy.css" media="all" rel="stylesheet">
<script src="https://static.example.com/animations.js"></script>
<script src="https://static.example.com/actions.js"></script>
<script src="https://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="https://static.example.com/fancy.css" media="all" rel="stylesheet">
<script src="https://static.example.com/whizbang.js"></script>
Если вам требуется больше контроля над наследованием ресурсов, то ресурсы следует определять с помощью динамических свойств. Динамическое свойства обеспечивают полный контроль над процессом наследования ресурсов.
Динамическое определение ресурсов через Media¶
Если вам нужно выполнить более сложные манипуляции с требованиями к активам, вы можете напрямую определить свойство media. Это делается путем определения свойства виджета, которое возвращает экземпляр form.Media. Конструктор form.Media принимает аргументы ключевых слов css и js в том же формате, который используется в определении статического носителя.
Например, вот как может быть представлено определение ресурсов для нашего виджета календаря в динамическом виде:
class CalendarWidget(forms.TextInput):
@property
def media(self):
return forms.Media(
css={"all": ["pretty.css"]}, js=["animations.js", "actions.js"]
)
Обратитесь к разделу Media objects для получения информации о том, как создать возвращаемые значения для динамических свойств ресурсов.
Определение путей до ресурсов¶
Пути как строки¶
Строковые пути, используемые для указания ресурсов, могут быть относительными или абсолютными. Если путь начинается с /, http:// или https://, он будет интерпретирован как абсолютный путь и оставлен как есть. Все остальные пути будут начинаться со значения соответствующего префикса. Если установлено приложение django.contrib.staticfiles, оно будет использоваться для обслуживания ресурсов.
Независимо от того, используете ли вы django.contrib.staticfiles, для отображения страницы необходимо указать настройки STATIC_URL и STATIC_ROOT.
Чтобы найти подходящий префикс для использования, Django проверит, не установлен ли параметр STATIC_URL на None, и автоматически вернется к использованию MEDIA_URL. Например, если MEDIA_URL для вашего сайта был 'https://uploads.example.com/', а STATIC_URL был None:
>>> from django import forms
>>> class CalendarWidget(forms.TextInput):
... class Media:
... css = {
... "all": ["/css/pretty.css"],
... }
... js = ["animations.js", "https://othersite.com/actions.js"]
...
>>> w = CalendarWidget()
>>> print(w.media)
<link href="/css/pretty.css" media="all" rel="stylesheet">
<script src="https://uploads.example.com/animations.js"></script>
<script src="https://othersite.com/actions.js"></script>
Но если STATIC_URL равен 'https://static.example.com/':
>>> w = CalendarWidget()
>>> print(w.media)
<link href="/css/pretty.css" media="all" rel="stylesheet">
<script src="https://static.example.com/animations.js"></script>
<script src="https://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="https://othersite.com/actions.js"></script>
Пути как объекты¶
Ресурсы также могут быть объектными, используя Script. Кроме того, они позволяют передавать пользовательские атрибуты HTML:
class Media:
js = [
Script(
"https://cdn.example.com/something.min.js",
**{
"crossorigin": "anonymous",
"async": True,
},
),
]
Если бы это определение мультимедиа нужно было отобразить, оно стало бы следующим HTML:
<script src="https://cdn.example.com/something.min.js"
crossorigin="anonymous"
async>
</script>
Был добавлен класс объектов Script.
Объекты ресурсов¶
При обращении к атрибуту media виджета или формы в качестве значения будет возвращён экземпляр forms.Media. Как мы уже выяснили, строковым представлением такого объекта является HTML, который необходимо вставить в блок <head> вашей страницы.
Тем не менее, у объектов Media есть ещё интересные свойства.
Группы файлов¶
Если вам нужны только файлы определенного типа, вы можете использовать оператор индекса, чтобы отфильтровать интересующий носитель. Например:
>>> w = CalendarWidget()
>>> print(w.media)
<link href="https://static.example.com/pretty.css" media="all" rel="stylesheet">
<script src="https://static.example.com/animations.js"></script>
<script src="https://static.example.com/actions.js"></script>
>>> print(w.media["css"])
<link href="https://static.example.com/pretty.css" media="all" rel="stylesheet">
При использовании оператора фильтрации, возвращаемым значением будет новый объект 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="https://static.example.com/pretty.css" media="all" rel="stylesheet">
<script src="https://static.example.com/animations.js"></script>
<script src="https://static.example.com/actions.js"></script>
<script src="https://static.example.com/whizbang.js"></script>
Порядок статических файлов¶
Порядок добавления статических файлов в DOM часто важен. Например, вы можете использовать скрипт, который зависит от jQuery. Поэтому при объединении объектов Media Django пытается сохранить относительный порядок файлов, которые указаны в классе 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="https://static.example.com/jQuery.js"></script>
<script src="https://static.example.com/calendar.js"></script>
<script src="https://static.example.com/time.js"></script>
<script src="https://static.example.com/noConflict.js"></script>
Например, объединяя объекты Media, которые содержат файлы в конфликтующем порядке, вы получите MediaOrderConflictWarning.
Media на формах¶
Виджеты являются не единственными объектами, которые могут содержать 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="https://static.example.com/pretty.css" media="all" rel="stylesheet">
<script src="https://static.example.com/animations.js"></script>
<script src="https://static.example.com/actions.js"></script>
<script src="https://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="https://static.example.com/pretty.css" media="all" rel="stylesheet">
<link href="https://static.example.com/layout.css" media="all" rel="stylesheet">
<script src="https://static.example.com/animations.js"></script>
<script src="https://static.example.com/actions.js"></script>
<script src="https://static.example.com/whizbang.js"></script>