Статические файлы форм (класс Media)

Для генерации отзывчивых и удобных форм недостаточно простого HTML. Как минимум, нужен CSS, а современные виджеты нуждаются ещё и в JavaScript. Точная комбинация CSS и JavaScript зависит от виджетов, используемых на этой странице.

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

Поставляемое с Django приложение интерфейса администратора определяет ряд собственных виджетов для календарей, отфильтрованных выборок и так далее. Эти виджеты имеют свои требования к ресурсам. Шаблоны приложения подключают соответствующие файлы, которые необходимы для работы виджетов на странице.

Если вам понравились виджеты интерфейса администратора, то используйте их в своих приложениях. Они все расположены в django.contrib.admin.widgets.

Какую библиотеку JavaScript использовать?

Существует множество библиотек JavaScript, многие из которых представляют виджеты (например, календари), которые могут быть использованы в вашем приложении. Django умышленно избегает использования какой-то одной JavaScript библиотеки. Каждая библиотека имеет свои преимущества и недостатки, выбирайте ту, которая соответствует вашим нуждам. Django можно интегрировать с любой JavaScript библиотекой.

Определение статических файлов

Декларативное определение является самым простым способом определения ресурсов. Этот способ предполагает описание ресурса во внутреннем классе Media. Свойства внутреннего класса определяют требования к ресурсам.

Here’s an example:

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" type="text/css" media="all" rel="stylesheet">
<script type="text/javascript" src="http://static.example.com/animations.js"></script>
<script type="text/javascript" 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',)
    }

Вышеприведённое определение стилей будет преобразовано в следующий код:

<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 type="text/javascript" src="http://static.example.com/animations.js"></script>
<script type="text/javascript" src="http://static.example.com/actions.js"></script>
<script type="text/javascript" src="http://static.example.com/whizbang.js"></script>

Виджет FancyCalendar наследует все ресурсы от базового виджета. Если такое поведение вам не подходит, то добавьте 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" type="text/css" media="all" rel="stylesheet">
<script type="text/javascript" src="http://static.example.com/whizbang.js"></script>

Если вам требуется больше контроля над наследованием ресурсов, то ресурсы следует определять с помощью динамических свойств. Динамическое свойства обеспечивают полный контроль над процессом наследования ресурсов.

Динамическое определение ресурсов через Media

Если над требованиями к ресурсам необходимо производить более тонкие манипуляции, то можно сделать свойство media вычисляемым динамически. Это реализуется с помощью определения свойства виджета, которое возвращает экземпляр forms.Media. Конструктор forms.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 для сайта имеет значение '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 type="text/javascript" src="http://uploads.example.com/animations.js"></script>
<script type="text/javascript" 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 type="text/javascript" src="http://static.example.com/animations.js"></script>
<script type="text/javascript" src="http://othersite.com/actions.js"></script>

Или, если активировано приложение staticfiles, используется ~django.contib.staticfiles.storage.ManifestStaticFilesStorage:

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

Объекты ресурсов

При обращении к атрибуту media виджета или формы в качестве значения будет возвращён экземпляр forms.Media. Как мы уже выяснили, строковым представлением такого объекта является HTML, который необходимо вставить в блок <head> вашей страницы.

Тем не менее, у объектов Media есть ещё интересные свойства.

Группы файлов

Если вам требуется ресурс определённого типа, то вы можете использовать оператор для фильтрации ненужных ресурсов. Например:

>>> w = CalendarWidget()
>>> print(w.media)
<link href="http://static.example.com/pretty.css" type="text/css" media="all" rel="stylesheet">
<script type="text/javascript" src="http://static.example.com/animations.js"></script>
<script type="text/javascript" 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, содержащий ресурсы обоих объектов:

>>> 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 type="text/javascript" src="http://static.example.com/animations.js"></script>
<script type="text/javascript" src="http://static.example.com/actions.js"></script>
<script type="text/javascript" src="http://static.example.com/whizbang.js"></script>

Порядок статических файлов

The order in which assets are inserted into the DOM is often important. For example, you may have a script that depends on jQuery. Therefore, combining Media objects attempts to preserve the relative order in which assets are defined in each Media class.

Например:

>>> 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 type="text/javascript" src="http://static.example.com/jQuery.js"></script>
<script type="text/javascript" src="http://static.example.com/calendar.js"></script>
<script type="text/javascript" src="http://static.example.com/time.js"></script>
<script type="text/javascript" src="http://static.example.com/noConflict.js"></script>

Например, объединяя объекты Media, которые содержат файлы в конфликтующем порядке, вы получите MediaOrderConflictWarning.

Media на формах

Виджеты являются не единственными объектами, которые могут содержать media. Формы тоже могут требовать наличия media для своей работы. Правила определения media для форм аналогичны правилам для виджетов: определения могут быть статическими или динамическими. Пути и правила наследования для этих определений тоже не отличаются.

Независимо от того, определили вы внешние ресурсы или нет, всё объекты Form обладают свойством 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 type="text/javascript" src="http://static.example.com/animations.js"></script>
<script type="text/javascript" src="http://static.example.com/actions.js"></script>
<script type="text/javascript" src="http://static.example.com/whizbang.js"></script>

If you want to associate additional assets with a form – for example, CSS for form layout – add a Media declaration to the form:

>>> 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 type="text/javascript" src="http://static.example.com/animations.js"></script>
<script type="text/javascript" src="http://static.example.com/actions.js"></script>
<script type="text/javascript" src="http://static.example.com/whizbang.js"></script>