Активы формы (класс Media)

Rendering an attractive and easy-to-use web form requires more than just HTML - it also requires CSS stylesheets, and if you want to use fancy widgets, you may also need to include some JavaScript on each page. The exact combination of CSS and JavaScript that is required for any given page will depend upon the widgets that are in use on that page.

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 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" 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" 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">
Changed in Django Development version:

В старых версиях атрибут type="text/css" включается в ссылки CSS.

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

Если вам требуется еще больший контроль над наследованием, определите свои активы с помощью 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.

Пути в определениях активов

Paths as strings

String paths used to specify assets can be either relative or absolute. If a path starts with /, http:// or https://, it will be interpreted as an absolute path, and left as-is. All other paths will be prepended with the value of the appropriate prefix. If the django.contrib.staticfiles app is installed, it will be used to serve assets.

Независимо от того, используете ли вы 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" 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" 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>

Пути как объекты

New in Django Development version.

Пути активов также могут быть заданы в виде хэшируемых объектов, реализующих метод __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 есть и другие интересные свойства.

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

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

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

При использовании оператора 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" 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>
Вернуться на верх