Виджеты

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

The HTML generated by the built-in widgets uses HTML5 syntax, targeting <!DOCTYPE html>. For example, it uses boolean attributes such as checked rather than the XHTML style of checked='checked'.

Совет

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

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

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

However, if you want to use a different widget for a field, you can use the widget argument on the field definition. For example:

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

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

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

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

Widgets inheriting from the Select widget

Виджеты, унаследованные от виджета 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 файлы).

For example, take the following form:

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" required></td></tr>
<tr><th>Url:</th><td><input type="url" name="url" required></td></tr>
<tr><th>Comment:</th><td><input type="text" name="comment" required></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'}))

You can also modify a widget in the form definition:

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

    name.widget.attrs.update({'class': 'special'})
    comment.widget.attrs.update(size='40')

Or if the field isn’t declared directly on the form (such as model form fields), you can use the Form.fields attribute:

class CommentForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields['name'].widget.attrs.update({'class': 'special'})
        self.fields['comment'].widget.attrs.update(size='40')

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

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

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

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

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

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

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

Base widget classes

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

Widget

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')
'<input title="Your name" type="text" name="name" value="A name" size="10">'

Если вы укажите True или False атрибуту, он будет отрендерен как HTML5 булев атрибут:

>>> name = forms.TextInput(attrs={'required': True})
>>> name.render('name', 'A name')
'<input name="name" type="text" value="A name" required>'
>>>
>>> name = forms.TextInput(attrs={'required': False})
>>> name.render('name', 'A name')
'<input name="name" type="text" value="A name">'
supports_microseconds

Атрибут по умолчанию равен True. При False микросекунды объекта datetime и time будут сброшены в 0.

format_value(value)

Cleans and returns a value for use in the widget template. value isn’t guaranteed to be valid input, therefore subclass implementations should program defensively.

get_context(name, value, attrs)

Returns a dictionary of values to use when rendering the widget template. By default, the dictionary contains a single key, 'widget', which is a dictionary representation of the widget containing the following keys:

  • 'name': The name of the field from the name argument.
  • 'is_hidden': A boolean indicating whether or not this widget is hidden.
  • 'required': A boolean indicating whether or not the field for this widget is required.
  • 'value': The value as returned by format_value().
  • 'attrs': HTML attributes to be set on the rendered widget. The combination of the attrs attribute and the attrs argument.
  • 'template_name': The value of self.template_name.

Widget subclasses can provide custom context values by overriding this method.

id_for_label(id_)

Возвращает HTML ID атрибут виджета, который можно использовать для <label>, принимая ID поля. Возвращает None, если ID не доступен.

Этот метод необходим т.к. некоторые виджеты состоят из нескольких HTML элеентов с разными ID. В этом случает метод должен вернуть ID первого тега виджета.

render(name, value, attrs=None, renderer=None)

Renders a widget to HTML using the given renderer. If renderer is None, the renderer from the FORM_RENDERER setting is used.

value_from_datadict(data, files, name)

Принимает словарь с данными и имя виджета. files может содержать данные из request.FILES. Возвращает None, если значение не найдено. Обратите внимание, value_from_datadict может вызываться несколько раз при обработке данных в форме. Если вы добавите медленные операции в этот метод, позаботьтесь о кешировании результата.

value_omitted_from_data(data, files, name)

Given data and files dictionaries and this widget’s name, returns whether or not there’s data or files for the widget.

The method’s result affects whether or not a field in a model form falls back to its default.

Special cases are CheckboxInput, CheckboxSelectMultiple, and SelectMultiple, which always return False because an unchecked checkbox and unselected <select multiple> don’t appear in the data of an HTML form submission, so it’s unknown whether or not the user submitted a value.

use_required_attribute(initial)

Given a form field’s initial value, returns whether or not the widget can be rendered with the required HTML attribute. Forms use this method along with Field.required and Form.use_required_attribute to determine whether or not to display the required attribute for each field.

By default, returns False for hidden widgets and True otherwise. Special cases are ClearableFileInput, which returns False when initial is set, and CheckboxSelectMultiple, which always returns False because browser validation would require all checkboxes to be checked instead of at least one.

Override this method in custom widgets that aren’t compatible with browser validation. For example, a WSYSIWG text editor widget backed by a hidden textarea element may want to always return False to avoid browser validation on the hidden field.

MultiWidget

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()]
        return [None, None]

Совет

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

It provides some custom context:

get_context(name, value, attrs)

In addition to the 'widget' key described in Widget.get_context(), MultiValueWidget adds a widget['subwidgets'] key.

These can be looped over in the widget template:

{% for subwidget in widget.subwidgets %}
    {% include subwidget.template_name with widget=subwidget %}
{% endfor %}

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

from datetime import date
from django import forms

class DateSelectorWidget(forms.MultiWidget):
    def __init__(self, attrs=None):
        days = [(day, day) for day in range(1, 32)]
        months = [(month, month) for month in range(1, 13)]
        years = [(year, year) for year in [2018, 2019, 2020]]
        widgets = [
            forms.Select(attrs=attrs, choices=days),
            forms.Select(attrs=attrs, choices=months),
            forms.Select(attrs=attrs, choices=years),
        ]
        super().__init__(widgets, attrs)

    def decompress(self, value):
        if isinstance(value, date):
            return [value.day, value.month, value.year]
        elif isinstance(value, str):
            year, month, day = value.split('-')
            return [day, month, year]
        return [None, None, None]

    def value_from_datadict(self, data, files, name):
        day, month, year = super().value_from_datadict(data, files, name)
        # DateField expects a single string that it can parse into a date.
        return '{}-{}-{}'.format(year, month, day)

The constructor creates several Select widgets in a list. The super() method uses this list to setup the widget.

The required method decompress() breaks up a datetime.date value into the day, month, and year values corresponding to each widget. If an invalid date was selected, such as the non-existent 30th February, the DateField passes this method a string instead, so that needs parsing. The final return handles when value is None, meaning we don’t have any defaults for our subwidgets.

The default implementation of value_from_datadict() returns a list of values corresponding to each Widget. This is appropriate when using a MultiWidget with a MultiValueField. But since we want to use this widget with a DateField, which takes a single value, we have overridden this method. The implementation here combines the data from the subwidgets into a string in the format that DateField expects.

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

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

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

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

TextInput

class TextInput
  • input_type: 'text'
  • template_name: 'django/forms/widgets/text.html'
  • Renders as: <input type="text" ...>

NumberInput

class NumberInput
  • input_type: 'number'
  • template_name: 'django/forms/widgets/number.html'
  • Renders as: <input type="number" ...>

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

EmailInput

class EmailInput
  • input_type: 'email'
  • template_name: 'django/forms/widgets/email.html'
  • Renders as: <input type="email" ...>

URLInput

class URLInput
  • input_type: 'url'
  • template_name: 'django/forms/widgets/url.html'
  • Renders as: <input type="url" ...>

PasswordInput

class PasswordInput
  • input_type: 'password'
  • template_name: 'django/forms/widgets/password.html'
  • Renders as: <input type="password" ...>

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

render_value

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

HiddenInput

class HiddenInput
  • input_type: 'hidden'
  • template_name: 'django/forms/widgets/hidden.html'
  • Renders as: <input type="hidden" ...>

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

DateInput

class DateInput
  • input_type: 'text'
  • template_name: 'django/forms/widgets/date.html'
  • Renders as: <input type="text" ...>

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

format

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

If no format argument is provided, the default format is the first format found in DATE_INPUT_FORMATS and respects Формат локализации.

DateTimeInput

class DateTimeInput
  • input_type: 'text'
  • template_name: 'django/forms/widgets/datetime.html'
  • Renders as: <input type="text" ...>

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

format

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

If no format argument is provided, the default format is the first format found in DATETIME_INPUT_FORMATS and respects Формат локализации.

По умолчанию микросекунды значения всегда устанавливаются в 0. Если микросекунды необходимы, наследуйтесь от этого класса и установите атрибут supports_microseconds в True.

TimeInput

class TimeInput
  • input_type: 'text'
  • template_name: 'django/forms/widgets/time.html'
  • Renders as: <input type="text" ...>

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

format

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

If no format argument is provided, the default format is the first format found in TIME_INPUT_FORMATS and respects Формат локализации.

Подробности об обработке микросекунд смотрите в описании DateTimeInput.

Textarea

class Textarea
  • template_name: 'django/forms/widgets/textarea.html'
  • Renders as: <textarea>...</textarea>

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

These widgets make use of the HTML elements <select>, <input type="checkbox">, and <input type="radio">.

Widgets that render multiple choices have an option_template_name attribute that specifies the template used to render each choice. For example, for the Select widget, select_option.html renders the <option> for a <select>.

CheckboxInput

class CheckboxInput
  • input_type: 'checkbox'
  • template_name: 'django/forms/widgets/checkbox.html'
  • Renders as: <input type="checkbox" ...>

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

check_test

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

Select

class Select
  • template_name: 'django/forms/widgets/select.html'
  • option_template_name: 'django/forms/widgets/select_option.html'
  • Renders as: <select><option ...>...</select>
choices

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

NullBooleanSelect

class NullBooleanSelect
  • template_name: 'django/forms/widgets/select.html'
  • option_template_name: 'django/forms/widgets/select_option.html'

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

SelectMultiple

class SelectMultiple
  • template_name: 'django/forms/widgets/select.html'
  • option_template_name: 'django/forms/widgets/select_option.html'

Similar to Select, but allows multiple selection: <select multiple>...</select>

RadioSelect

class RadioSelect
  • template_name: 'django/forms/widgets/radio.html'
  • option_template_name: 'django/forms/widgets/radio_option.html'

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

<ul>
  <li><input type="radio" name="..."></li>
  ...
</ul>

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

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

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

<div class="myradio">
    <label for="id_beatles_0"><input id="id_beatles_0" name="beatles" type="radio" value="john" required> John</label>
</div>
<div class="myradio">
    <label for="id_beatles_1"><input id="id_beatles_1" name="beatles" type="radio" value="paul" required> Paul</label>
</div>
<div class="myradio">
    <label for="id_beatles_2"><input id="id_beatles_2" name="beatles" type="radio" value="george" required> George</label>
</div>
<div class="myradio">
    <label for="id_beatles_3"><input id="id_beatles_3" name="beatles" type="radio" value="ringo" required> Ringo</label>
</div>

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

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

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

<label for="id_beatles_0">
    John
    <span class="radio"><input id="id_beatles_0" name="beatles" type="radio" value="john" required></span>
</label>

<label for="id_beatles_1">
    Paul
    <span class="radio"><input id="id_beatles_1" name="beatles" type="radio" value="paul" required></span>
</label>

<label for="id_beatles_2">
    George
    <span class="radio"><input id="id_beatles_2" name="beatles" type="radio" value="george" required></span>
</label>

<label for="id_beatles_3">
    Ringo
    <span class="radio"><input id="id_beatles_3" name="beatles" type="radio" value="ringo" required></span>
</label>

If you decide not to loop over the radio buttons – e.g., if your template includes {{ myform.beatles }} – they’ll be output in a <ul> with <li> tags, as above.

The outer <ul> container receives the id attribute of the widget, if defined, or BoundField.auto_id otherwise.

При генерации радио кнопок теги label and input содержат атрибуты for и id соответственно. Каждая радио кнопка использует атрибут id_for_label для генерации ID элемента.

CheckboxSelectMultiple

class CheckboxSelectMultiple
  • template_name: 'django/forms/widgets/checkbox_select.html'
  • option_template_name: 'django/forms/widgets/checkbox_option.html'

Similar to SelectMultiple, but rendered as a list of checkboxes:

<ul>
  <li><input type="checkbox" name="..." ></li>
  ...
</ul>

The outer <ul> container receives the id attribute of the widget, if defined, or BoundField.auto_id otherwise.

Like RadioSelect, you can loop over the individual checkboxes for the widget’s choices. Unlike RadioSelect, the checkboxes won’t include the required HTML attribute if the field is required because browser validation would require all checkboxes to be checked instead of at least one.

При генерации чекбоксов теги label and input содержат атрибуты for и id соответственно. Каждый чекбокс использует атрибут id_for_label для генерации ID элемента.

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

FileInput

class FileInput
  • template_name: 'django/forms/widgets/file.html'
  • Renders as: <input type="file" ...>

ClearableFileInput

class ClearableFileInput
  • template_name: 'django/forms/widgets/clearable_file_input.html'
  • Renders as: <input type="file" ...> with an additional checkbox input to clear the field’s value, if the field is not required and has initial data.

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

MultipleHiddenInput

class MultipleHiddenInput
  • template_name: 'django/forms/widgets/multiple_hidden.html'
  • Renders as: multiple <input type="hidden" ...> tags

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

choices

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

SplitDateTimeWidget

class SplitDateTimeWidget
  • template_name: 'django/forms/widgets/splitdatetime.html'

Wrapper (using MultiWidget) around two widgets: DateInput for the date, and TimeInput for the time. Must be used with SplitDateTimeField rather than DateTimeField.

SplitDateTimeWidget has several optional arguments:

date_format

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

time_format

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

date_attrs
time_attrs

Similar to Widget.attrs. A dictionary containing HTML attributes to be set on the rendered DateInput and TimeInput widgets, respectively. If these attributes aren’t set, Widget.attrs is used instead.

SplitHiddenDateTimeWidget

class SplitHiddenDateTimeWidget
  • template_name: 'django/forms/widgets/splithiddendatetime.html'

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

SelectDateWidget

class SelectDateWidget
  • template_name: 'django/forms/widgets/select_date.html'

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

Принимает несколько необязательных аргументов:

years

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

months

Необязательный словарь списка месяцев.

Ключ словаря соответствует номеру месяца (начиная с 1), значения – отображаемое название:

MONTHS = {
    1:_('jan'), 2:_('feb'), 3:_('mar'), 4:_('apr'),
    5:_('may'), 6:_('jun'), 7:_('jul'), 8:_('aug'),
    9:_('sep'), 10:_('oct'), 11:_('nov'), 12:_('dec')
}
empty_label

Если поле DateField не обязательно, SelectDateWidget будет содержать пустое значение в начале списка (по умолчанию ---). Вы можете поменять название элемента с помощью атрибута empty_label. empty_label принимает string, list, или tuple. Если указана строка, каждый <select> будет содержать пустое значение. Если empty_label содержит list или tuple из 3-х строк – для каждого <select> будет использоваться свое значение. Элементы используются в следующем порядке: ('year_label', 'month_label', 'day_label').

# A custom empty label with string
field1 = forms.DateField(widget=SelectDateWidget(empty_label="Nothing"))

# A custom empty label with tuple
field1 = forms.DateField(
    widget=SelectDateWidget(
        empty_label=("Choose Year", "Choose Month", "Choose Day"),
    ),
)