Активы формы (класс 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" type="text/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" 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. Смотрите the section on paths для получения подробной информации о том, как указать пути к этим файлам.

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

Media объекты

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

Однако у объектов Media есть и другие интересные свойства.

Подмножества активов

Если вам нужны файлы только определенного типа, вы можете использовать оператор subscript, чтобы отфильтровать интересующий вас носитель. Например:

>>> w = CalendarWidget()
>>> print(w.media)
<link href="http://static.example.com/pretty.css" type="text/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" type="text/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" type="text/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" type="text/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" type="text/css" media="all" rel="stylesheet">
<link href="http://static.example.com/layout.css" type="text/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>
Вернуться на верх