Виджеты

Виджет – это представление поля в виде HTML кода. Виджеты обеспечивают генерацию HTML и извлечение соответствующих данных из GET/POST запросов.

Совет

Не следует путать виджеты с полями формы. Поля формы обеспечивают логику проверки вводимой информации и используются в шаблонах. Виджеты же отвечают за рендеринг форм на веб-странице и обработку переданных данных. Тем не менее, виджеты следует назначать на поля формы.

Назначение виджета

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

Тем не менее, если потребуется указать другой виджет для поля, просто используйте аргумент widget при определении поля. Например:

from django import forms

class CommentForm(forms.Form):
    name = forms.CharField()
    url = forms.URLField()
    comment = forms.CharField(widget=forms.Textarea)

Этот код создают форму, у которой полю комментария назначен виджет class:Textarea вместо стандартного TextInput widget.

Аргументы виджетов

Большинство виджетов принимают дополнительные аргументы. Они могут быть назначены при определении виджета для поля. В следующем примере атрибут years передаётся в SelectDateWidget:

from django import forms
from django.forms.extras.widgets import SelectDateWidget

BIRTH_YEAR_CHOICES = ('1980', '1981', '1982')
FAVORITE_COLORS_CHOICES = (('blue', 'Blue'),
                            ('green', 'Green'),
                            ('black', 'Black'))

class SimpleForm(forms.Form):
    birth_year = forms.DateField(widget=SelectDateWidget(years=BIRTH_YEAR_CHOICES))
    favorite_colors = forms.MultipleChoiceField(required=False,
        widget=forms.CheckboxSelectMultiple, choices=FAVORITE_COLORS_CHOICES)

Обратитесь к Встроенные виджеты для получения подробной информации о доступных виджетах и аргументах, которые они принимают.

Виджеты, унаследованные от виджета Select

Виджеты, унаследованные от виджета Select, предназначены для работы с вариантами. Они предоставляют список вариантов, из которых надо сделать выбор. Разные виджеты позволяют сделать выбор по-разному. Базовый виджет Select использует HTML тег <select>, а унаследованный виджет RadioSelect использует радио кнопки.

Виджеты типа Select по умолчанию используют поля ChoiceField. Варианты, отображаемые виджетом, наследуются от ChoiceField и изменение ChoiceField.choices повлияет на Select.choices. Например:

>>> from django import forms
>>> CHOICES = (('1', 'First',), ('2', 'Second',))
>>> choice_field = forms.ChoiceField(widget=forms.RadioSelect, choices=CHOICES)
>>> choice_field.choices
[('1', 'First'), ('2', 'Second')]
>>> choice_field.widget.choices
[('1', 'First'), ('2', 'Second')]
>>> choice_field.widget.choices = ()
>>> choice_field.choices = (('1', 'First and only',),)
>>> choice_field.widget.choices
[('1', 'First and only')]

Виджеты, поддерживающие атрибут choices могут, тем не менее, использоваться совместно с полями, которые не основаны на выборе, – такими как CharField – но рекомендуется использовать поля типа ChoiceField в случаях, когда варианты берутся из модели.

Настройка экземпляров виджета

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

Существует два способа настройки виджета: на уровне экземпляра виджет и на уровне класса виджета.

Настройка экземпляров виджета

Если требуется визуально выделить один виджет от других, потребуется во время его создания указать для него дополнительные атрибуты и назначить его на поле формы (и возможно, добавить несколько правил в ваши CSS файлы).

Например, рассмотрим следующую простую форму:

from django import forms

class CommentForm(forms.Form):
    name = forms.CharField()
    url = forms.URLField()
    comment = forms.CharField()

Эта форма включает в себя три обычных виджета TextInput, которым не назначены ни CSS класс, ни дополнительные атрибуты. Это означает, что визуально они будут выглядеть одинаково:

>>> f = CommentForm(auto_id=False)
>>> f.as_table()
<tr><th>Name:</th><td><input type="text" name="name" /></td></tr>
<tr><th>Url:</th><td><input type="url" name="url"/></td></tr>
<tr><th>Comment:</th><td><input type="text" name="comment" /></td></tr>

На реальной странице сайта, возможно, вам понадобится разнообразить их отображение. Вам может потребоваться сделать поле для ввода комментария побольше или назначить имя виджету, чтобы он начал реагировать на особый CSS класс. Также возможно указать атрибут ‘type’ для поддержки типов HTML5. Для этого надо использовать аргумент Widget.attrs при создании виджета:

class CommentForm(forms.Form):
    name = forms.CharField(
                widget=forms.TextInput(attrs={'class':'special'}))
    url = forms.URLField()
    comment = forms.CharField(
               widget=forms.TextInput(attrs={'size':'40'}))

Djando добавить дополнительные атрибуты в генерируемый HTML код:

>>> f = CommentForm(auto_id=False)
>>> f.as_table()
<tr><th>Name:</th><td><input type="text" name="name" class="special"/></td></tr>
<tr><th>Url:</th><td><input type="url" name="url"/></td></tr>
<tr><th>Comment:</th><td><input type="text" name="comment" size="40"/></td></tr>

Вы также можете указать HTML id используя attrs. Смотрите BoundField.id_for_label.

Настройка классов виджета

Для виджетов можно добавлять медиа файлы (css и javascript) для более глубокой настройки их внешнего вида и поведения.

Вкратце, вам потребуется унаследовать виджет и либо определить класс “Media”, либо создать свойство “media”, возвращающее экземпляр этого класса.

Эти методы требуют некоторого объёма продвинутого кода и подробно описаны в руководстве по ресурсам форм.

Основные классы виджетов

Основные классы виджетов Widget и MultiWidget унаследованы всеми встроенными виджетами и могут являться основой для ваших собственных виджетов.

class Widget(attrs=None)

Абстрактный класс не может быть использован для отображения поля, но он предоставляет основной атрибут attrs. Вы также можете реализовать или переопределить метод render() в своём виджете.

attrs

Словарь, которые содержит HTML атрибуты, которые будут назначены сгенерированному виджету.

>>> from django import forms
>>> name = forms.TextInput(attrs={'size': 10, 'title': 'Your name',})
>>> name.render('name', 'A name')
u'<input title="Your name" type="text" name="name" value="A name" size="10" />'
render(name, value, attrs=None)

Возвращает HTML для виджета в виде строки Unicode. Этот метод должен быть реализован в дочерних классах. В противном случае будет выброшено исключение NotImplementedError.

Никто не гарантирует, что полученное значение будет правильным, поэтому реализация метода в дочерних классах должна обеспечивать соответствующую проверку.

value_from_datadict(self, data, files, name)

Принимает словарь данных и имя виджета, возвращает значения для этого виджета. Возвращает None, если значение не предоставлено.

class MultiWidget(widgets, attrs=None)

Виджет, который состоит из множества виджетов. Класс MultiWidget предназначен для поля MultiValueField.

Класс MultiWidget имеет один обязательный аргумент:

widgets

Итератор, содержащий необходимые виджеты.

И один обязательный метод:

decompress(value)

Этот метод принимает единственное “сжато” значение от поля и возвращает список “распакованных” значений. Введённое значение может считаться верным, но не обязательно не пустым.

Этот метод должен быть реализован в дочернем классе и, так как значение может быть пустым, реализация должна проверять этот момент.

Наличие “распаковки” объясняется необходимостью “разделить” объединённое значение поля формы на значения для каждого виджета.

Пример того как SplitDateTimeWidget преобразовывает значение datetime в список с датой и временем:

from django.forms import MultiWidget

class SplitDateTimeWidget(MultiWidget):

    # ...

    def decompress(self, value):
        if value:
            return [value.date(), value.time().replace(microsecond=0)]
        return [None, None]

Совет

Следует отметить, что MultiValueField обладает дополнительным методом compress() с обратной функциональностью – объединять проверенные значения всех элементов поля в единое значение.

Список других методов, которые может быть полезно переопределить:

render(name, value, attrs=None)

Аргумент value обрабатывается по-другому в этом методе в отличие от дочерних классов Widget, потому что он должен знать как именно разделять единое значение для отображения во множестве виджетов.

Аргумент value, используемый при генерации, может быть одним из:

  • Список (list).

  • Единственное значение (т.е., строка), которая является “сжатым” представлением списка значений.

Если value является списком, результатом метода render() будет совокупность отрендеренных виджетов. Если value не является списком, то сначала оно будет обработано методом decompress() для получения списка и затем выполнится рендеринг.

При работе метода render() каждому значени. в списке назначается соответствующий виджет. Первое значение значение назначается первому виджету, второе – второму и так далее.

В отличие от виджетов с единственным значением, метод render() не обязательно должен быть реализован в дочерних классах.

format_output(rendered_widgets)

Переданный список отрендеренных виджетов (в виде строки), возвращает строку Unicode, представляющую HTML код для всего этого.

Этот обработчик позволяет вам форматировать HTML код виджетов в нужном вам виде.

Ниже приведён виджет унаследован от MultiWidget и подходит для отображения даты, разделяя день, месяц и год по различным элементам. Этот виджет предназначен для использования совместно с DateField вместо MultiValueField, следовательно мы должны реализовать метод value_from_datadict():

from datetime import date
from django.forms import widgets

class DateSelectorWidget(widgets.MultiWidget):
    def __init__(self, attrs=None):
        # create choices for days, months, years
        # example below, the rest snipped for brevity.
        years = [(year, year) for year in (2011, 2012, 2013)]
        _widgets = (
            widgets.Select(attrs=attrs, choices=days),
            widgets.Select(attrs=attrs, choices=months),
            widgets.Select(attrs=attrs, choices=years),
        )
        super(DateSelectorWidget, self).__init__(_widgets, attrs)

    def decompress(self, value):
        if value:
            return [value.day, value.month, value.year]
        return [None, None, None]

    def format_output(self, rendered_widgets):
        return u''.join(rendered_widgets)

    def value_from_datadict(self, data, files, name):
        datelist = [
            widget.value_from_datadict(data, files, name + '_%s' % i)
            for i, widget in enumerate(self.widgets)]
        try:
            D = date(day=int(datelist[0]), month=int(datelist[1]),
                    year=int(datelist[2]))
        except ValueError:
            return ''
        else:
            return str(D)

Конструктор создаёт кортеж из нескольких виджетов Select. Класс, от которого был унаследован виджет, использует этот кортеж для настройки виджета.

Метод format_output() не особо нужен здесь (действительно, он похож на стандартную реализацию этого метода в MultiWidget), но идея в том, что вы можете в этой точке можете сделать нужную настройку виджетов.

Обязательный метод decompress() разбивает значение datetime.date на значение дня, месяца и года для каждого виджета. Следует отметить, что метод обрабатывает случай, когда value равно None.

Стандартная реализация метода value_from_datadict() возвращает список значений для каждого Widget. Это полезно при использовании MultiWidget совместно с MultiValueField, но так как мы желаем использовать этот виджет совместно с DateField, который принимает единственное значение, мы переопределили этот метод для соединения данных всех подчинённых виджетов в datetime.date. Метод получает данные из словаря POST, создаёт и проверяет дату. Если она проходит проверку, мы возвращаем строку, в противном случае возвращаем пустую строку, что заставляет form.is_valid вернуть False.

Встроенные виджеты

Django с помощью модуля django.forms.widgets обеспечивает представление для всех базовых HTML виджетов, а также некоторые часто используемые группы виджетов, включая виджеты для ввода текста, различные чекбоксы и селекторы, загрузку файлов и обработку сложных значений.

Виджеты для ввода текста

Эти виджеты используют HTML элементы input и textarea.

TextInput

class TextInput

Ввод текста: <input type="text" ...>

NumberInput

class NumberInput
New in Django 1.6.

Ввод текста: <input type="number" ...>

Будьте осторожны, не все браузеры поддерживают локализированные числа в полях типа number. Django, не надеясь на браузер, использует для localize значение True.

EmailInput

class EmailInput
New in Django 1.6.

Ввод текста: <input type="email" ...>

URLInput

class URLInput
New in Django 1.6.

Ввод текста: <input type="url" ...>

PasswordInput

class PasswordInput

Ввод пароля: <input type='password' ...>

Принимает один необязательный аргумент:

render_value

Определеяет, надо ли заполнять этот виджет при повторном отображении формы при ошибке (по умолчанию False).

HiddenInput

class HiddenInput

Скрытый ввод: <input type='hidden' ...>

Следует отметить, что также существует виджет MultipleHiddenInput, который объединяет элементы скрытого ввода.

DateInput

class DateInput

Ввод даты в простое текстовое поле: <input type='text' ...>

Принимает аргументы, аналогичные TextInput, лишь с одним необязательным аргументом:

format

Формат, в котором отображается начальное значение поля.

Если аргумент format не предоставлен, то стандартный формат берётся из DATE_INPUT_FORMATS с учётом Формат локализации.

DateTimeInput

class DateTimeInput

Ввод даты/времени в простое текстовое поле: <input type='text' ...>

Принимает аргументы, аналогичные TextInput, лишь с одним необязательным аргументом:

format

Формат, в котором отображается начальное значение поля.

Если аргумент format не предоставлен, то стандартный формат берётся из DATETIME_INPUT_FORMATS с учётом Формат локализации.

TimeInput

class TimeInput

Ввод времени в простое текстовое поле: <input type='text' ...>

Принимает аргументы, аналогичные TextInput, лишь с одним необязательным аргументом:

format

Формат, в котором отображается начальное значение поля.

Если аргумент format не предоставлен, то стандартный формат берётся из TIME_INPUT_FORMATS с учётом Формат локализации.

Textarea

class Textarea

Текстовая область: <textarea>...</textarea>

Виджеты выбора и отметки

CheckboxInput

class CheckboxInput

Чекбокс: <input type='checkbox' ...>

Принимает один необязательный аргумент:

check_test

Функция, которая принимает значение элемента и возвращает True, если указанное значение было отмечено на элементе.

Changed in Django 1.5:

Exceptions from check_test used to be silenced by its caller, this is no longer the case, they will propagate upwards.

Select

class Select

Виджет выбора: <select><option ...>...</select>

choices

Этот атрибут является необязательным, если поле не имеет атрибут choices. В противном случае, его содержимое имеет преимущество над атрибутами Field.

NullBooleanSelect

class NullBooleanSelect

Виджет выбора с вариантами ‘Неизвестно’, ‘Да’ и ‘Нет’.

SelectMultiple

class SelectMultiple

Аналогично Select, но разрешён множественный выбор: <select multiple='multiple'>...</select>.

RadioSelect

class RadioSelect

Аналогично Select, но отображается в виде списка радио кнопок с помощью тегов <li>:

<ul>
  <li><input type='radio' ...></li>
  ...
</ul>

Для более тонкого управления процессом генерации HTML кода, вы можете выводить радио кнопки в шаблон в цикле. Рассмотрим форму myform с полем beatles, которое использует RadioSelect в качестве виджета:

{% for radio in myform.beatles %}
<div class="myradio">
    {{ radio }}
</div>
{% endfor %}

Она будет представлена в виде следующего HTML кода:

<div class="myradio">
    <label><input type="radio" name="beatles" value="john" /> John</label>
</div>
<div class="myradio">
    <label><input type="radio" name="beatles" value="paul" /> Paul</label>
</div>
<div class="myradio">
    <label><input type="radio" name="beatles" value="george" /> George</label>
</div>
<div class="myradio">
    <label><input type="radio" name="beatles" value="ringo" /> Ringo</label>
</div>

Код содержит теги <label>. Также вы можете использовать атрибуты tag и choice_label для управления отображением каждой радио кнопки. Например, этот шаблон...

{% for radio in myform.beatles %}
    <label>
        {{ radio.choice_label }}
        <span class="radio">{{ radio.tag }}</span>
    </label>
{% endfor %}

... преобразуется в следующий HTML:

<label>
    John
    <span class="radio"><input type="radio" name="beatles" value="john" /></span>
</label>
<label>
    Paul
    <span class="radio"><input type="radio" name="beatles" value="paul" /></span>
</label>
<label>
    George
    <span class="radio"><input type="radio" name="beatles" value="george" /></span>
</label>
<label>
    Ringo
    <span class="radio"><input type="radio" name="beatles" value="ringo" /></span>
</label>

Если вы просто выведите поле в шаблоне с помощью {{ myform.beatles }}, то кнопки будут выведены с помощью тега <ul> с <li>, как было показано ранее.

Changed in Django 1.6.

Контейнер <ul> теперь получает атрибут id указанный в виджете.

CheckboxSelectMultiple

class CheckboxSelectMultiple

Аналогичен SelectMultiple, но отображается в виде списка чекбоксов.

<ul>
  <li><input type='checkbox' ...></li>
  ...
</ul>
Changed in Django 1.6.

Контейнер <ul> теперь получает атрибут id указанный в виджете.

Как и для RadioSelect, теперь можно использовать цикл по чекбоксам. Смотрите описание RadioSelect.

Виджеты для загрузки файлов

FileInput

class FileInput

Загрузка файла: <input type='file' ...>

ClearableFileInput

class ClearableFileInput

Загрузка файла: <input type='file' ...>, с дополнительным чекбоксом для очистки значения поля, если оно не является обязательным и имеет начальные данные.

Сложные виджеты

MultipleHiddenInput

class MultipleHiddenInput

Множество виджетов``<input type=’hidden’ ...>``.

Виджет, который обрабатывает множество скрытых виджетов для полей, которые содержат список значений.

choices

Этот атрибут является необязательным, если поле не имеет атрибут choices. В противном случае, его содержимое имеет преимущество над атрибутами Field.

SplitDateTimeWidget

class SplitDateTimeWidget

Обёртка (MultiWidget с помощью) вокруг двух виджетов: DateInput для даты и TimeInput для времени.

SplitDateTimeWidget имеет два необязательных атрибута:

date_format

Аналогичен DateInput.format.

time_format

Аналогичен TimeInput.format.

SplitHiddenDateTimeWidget

class SplitHiddenDateTimeWidget

Аналогичен SplitDateTimeWidget, но используется HiddenInput для даты и времени.

SelectDateWidget

class SelectDateWidget

Обёртка вокруг трёх виджетов Select – месяц, день и год. Следует отметить, что этот виджет располагается отдельно от стандартных виджетов.

Принимает один необязательный аргумент:

years

Необязательный список/кортеж с вариантами года. По умолчанию содержит текущий год и девять последующих.