Ресурсы форм

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

Именно здесь используются ресурсы формы. Django позволяет привязать различные ресурсы к форме и виджетам, которым нужны эти ресурсы. Например, если вы желаете использовать календарь для полей с датами, вы можете определить собственный виджет календаря. Этот виджет может требовать для своей работы CSS и JavaScript. При использовании этого виджета на форме, Django может определить необходимые .css и .js файлы и предоставить список имён этих файлов для их включения в код страницы.

Media and Django Admin

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

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

Which JavaScript toolkit?

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

Статическое определение ресурсов

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

Рассмотрим простой пример:

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

Ниже приведён список всех возможных вариантов для Media. Ни один из них не является обязательным.

css

Словарь, описывающий CSS файлы, необходимые для различных устройств.

Значения словаря должны быть кортежами или списками имён файлов. Обратитесь к the section on media 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',)
    }

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

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

js

Кортеж, описывающий необходимые JavaScript файлы. Обратитесь к the section on media path для получения информации о правилах определения путей до файлов ресурсов.

extend

Булево значение, определяющее производится ли наследование ресурсов базового класса.

По умолчанию, любой объект, использующий статическое определение ресурсов, будет наследовать все ресурсы от базового класса. Это будет происходить вне зависимости от того, как базовый класс определяет свои зависимости от ресурсов. Пример, расширения функционала нашего простого календаря:

>>> class FancyCalendarWidget(CalendarWidget):
...     class Media:
...         css = {
...             'all': ('fancy.css',)
...         }
...         js = ('whizbang.js',)

>>> w = FancyCalendarWidget()
>>> print w.media
<link href="http://media.example.com/pretty.css" type="text/css" media="all" rel="stylesheet" />
<link href="http://media.example.com/fancy.css" type="text/css" media="all" rel="stylesheet" />
<script type="text/javascript" src="http://media.example.com/animations.js"></script>
<script type="text/javascript" src="http://media.example.com/actions.js"></script>
<script type="text/javascript" src="http://media.example.com/whizbang.js"></script>

Виджет FancyCalendar наследует все ресурсы от базового виджета. Если такое поведение вам не подходит, то добавьте extend=False к своему определению ресурсов:

>>> class FancyCalendarWidget(CalendarWidget):
...     class Media:
...         extend = False
...         css = {
...             'all': ('fancy.css',)
...         }
...         js = ('whizbang.js',)

>>> w = FancyCalendarWidget()
>>> print w.media
<link href="http://media.example.com/fancy.css" type="text/css" media="all" rel="stylesheet" />
<script type="text/javascript" src="http://media.example.com/whizbang.js"></script>

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

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

Если над требованиями к ресурсам необходимо производить более тонкие манипуляции, то можно сделать свойство media вычисляемым динамически. Это реализуется с помощью определения свойства виджета, которое возвращает экземпляр forms.Media. Конструктор forms.Media принимает именованные аргументы css и js, в том же формате, что используется при статическом определении ресурсов.

Например, вот как может быть представлено определение ресурсов для нашего виджета календаря в динамическом виде:

class CalendarWidget(forms.TextInput):
    def _media(self):
        return forms.Media(css={'all': ('pretty.css',)},
                           js=('animations.js', 'actions.js'))
    media = property(_media)

Обратитесь к разделу `Media objects`_ для получения информации о том, как создать возвращаемые значения для динамических свойств ресурсов.

Определение путей до ресурсов

Изменено в Django 1.3.

Пути, используемые для определения ресурсов, могут быть как относительными, так и абсолютными. Если путь начинается с ‘/’, ‘http://‘ или ‘https://‘, то он будет интерпретирован как абсолютный и оставлен в неизменном виде. Все остальные пути будут дополнены соответствующим префиксом.

Как часть приложения staticfiles добавлены два новых параметра конфигурации, которые относятся к «статическим файлам» (изображения, CSS, Javascript и т.д.), необходимым для отображения полной страницы: STATIC_URL и STATIC_ROOT.

Для того, чтобы найти правильный префикс, Django проверит параметр STATIC_URL на равенство с None и, если это так, то автоматически воспользуется параметром MEDIA_URL. Например, если параметр MEDIA_URL для сайта имеет значение 'http://uploads.example.com/', а параметр STATIC_URL равен None:

>>> 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>

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

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

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

Подмножества Media

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

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

>>> print w.media['css']
<link href="http://media.example.com/pretty.css" type="text/css" media="all" rel="stylesheet" />

При использовании оператора фильтрации, возвращаемым значением будет новый объект Media, но содержать он будет только указанные ресурсы.

Сочетание ресурсных объектов

Ресурсные объекты также можно объединять друг с другом. При объединении двух таких объектов, результатом будет объект Media, содержащий ресурсы обоих объектов:

>>> 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://media.example.com/pretty.css" type="text/css" media="all" rel="stylesheet" />
<script type="text/javascript" src="http://media.example.com/animations.js"></script>
<script type="text/javascript" src="http://media.example.com/actions.js"></script>
<script type="text/javascript" src="http://media.example.com/whizbang.js"></script>

Ресурсы на формах

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

Независимо от того, определили вы внешние ресурсы или нет, всё объекты Form обладают свойством media. Значением по умолчанию для этого свойства является результат сочетания ресурсов всех виджетов формы:

>>> class ContactForm(forms.Form):
...     date = DateField(widget=CalendarWidget)
...     name = CharField(max_length=40, widget=OtherWidget)

>>> f = ContactForm()
>>> f.media
<link href="http://media.example.com/pretty.css" type="text/css" media="all" rel="stylesheet" />
<script type="text/javascript" src="http://media.example.com/animations.js"></script>
<script type="text/javascript" src="http://media.example.com/actions.js"></script>
<script type="text/javascript" src="http://media.example.com/whizbang.js"></script>

Если требуется добавить ещё ресурс к форме, например CSS для разметки формы, просто добавьте его определение к форме:

>>> 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://media.example.com/pretty.css" type="text/css" media="all" rel="stylesheet" />
<link href="http://media.example.com/layout.css" type="text/css" media="all" rel="stylesheet" />
<script type="text/javascript" src="http://media.example.com/animations.js"></script>
<script type="text/javascript" src="http://media.example.com/actions.js"></script>
<script type="text/javascript" src="http://media.example.com/whizbang.js"></script>