Активы формы (класс 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" 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 4.1:

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

js

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

extend

Булево значение, определяющее поведение наследования для объявлений Media.

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

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

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

Пути как строки

Строковые пути, используемые для указания активов, могут быть как относительными, так и абсолютными. Если путь начинается с /, 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" 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 4.1.

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