部件是 Django 对 HTML 输入元素的表示。部件处理 HTML 的渲染,以及从对应于部件的 GET/POST 字典中提取数据。
内置部件生成的 HTML 使用 HTML5 语法,目标是 <!DOCTYPE html>
。例如,它使用布尔属性,如 checked
而不是 XHTML 风格的 checked='checked'
。
每当你在表单中指定一个字段时,Django 会使用一个默认的部件来显示数据类型。要想知道哪个字段使用的是哪个部件,请看 内置 Field 类 的文档。
但是,如果你想为一个字段使用不同的部件,你可以在字段定义中使用 widget
参数。例如:
from django import forms
class CommentForm(forms.Form):
name = forms.CharField()
url = forms.URLField()
comment = forms.CharField(widget=forms.Textarea)
许多部件都有可选的额外参数;它们可以在字段上定义部件时进行设置。在下面的例子中, 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,
)
请参阅 内置部件,了解更多关于哪些部件可用以及它们接受哪些参数的信息。
Select
部件的部件。¶继承自 Select
部件的部件处理选择。它们向用户提供了一个可供选择的选项列表。不同的部件以不同的方式呈现这种选择;Select
部件本身使用 <select>
HTML 列表表示,而 RadioSelect
使用单选按钮。
Select
部件默认用于 ChoiceField
字段。部件上显示的选择是继承自 ChoiceField
,改变 ChoiceField.options
将更新 Select.options
。例如:
>>> 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')]
然而,提供 chips
属性的部件可以与非基于选择的字段一起使用——例如 CharField
——但当选择是模型固有的,而不仅仅是表示部件时,建议使用 ChoiceField
为基础的字段。
When Django renders a widget as HTML, it only renders very minimal markup -
Django doesn't add class names, or any other widget-specific attributes. This
means, for example, that all TextInput
widgets will appear the same
on your web pages.
如果你想让一个部件实例看起来与另一个不同,你需要在实例化部件对象并将其分配给表单字段时指定额外的属性(也许还需要在你的 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" 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>
On a real web page, you probably don't want every widget to look the same. You
might want a larger input element for the comment, and you might want the
'name' widget to have some special CSS class. It is also possible to specify
the 'type' attribute to take advantage of the new HTML5 input types. To do
this, you use the Widget.attrs
argument when creating the widget:
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'}))
你也可以在表单定义中修改一个部件:
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')
或者如果该字段没有直接在表单上声明(比如模型表单字段),可以使用 Form.fields
属性:
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 会将额外的属性包含在渲染的输出中:
>>> 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>
你也可以使用 attrs
设置 HTML id
。参见 BoundField.id_for_label
的例子。
有了部件,就可以添加静态资源(css
和 javascript
)并更深入地定制它们的外观和行为。
简而言之,你需要对部件进行子类化,并且 定义一个内部“Media”类 或者 创建一个"media"属性。
这些方法涉及到一些高级的 Python 编程,在 表单静态资源 主题指南中有详细描述。
基础部件类 Widget
和 MultiWidget
被所有的 内置部件 子类化,可以作为自定义部件的基础。
Widget
¶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">'
format_value
(value)¶清理并返回一个值,供部件模板使用。value
并不能保证是有效的输入,因此子类的实现应该是防御性的。
get_context
(name, value, attrs)¶返回渲染部件模板时要使用的值的字典。默认情况下,该字典包含一个键,'widget'
,它是一个包含以下键的部件的字典表示:
'name'
:name
参数中的字段名称。'is_hidden'
:表示该部件是否被隐藏的布尔值。'required'
:表示该部件是否需要该字段的布尔值。'value'
::meth:`format_value' 返回的值。'attrs'
:拟在渲染的部件上设置的 HTML 属性。attrs
属性和 attrs
参数的组合。''template_name'
:self.template_name
的值。Widget
子类可以通过覆盖该方法提供自定义上下文值。
id_for_label
(id_)¶返回该部件的 HTML ID 属性,供 <label>
,给定字段的 ID 使用。如果 ID 不可用,则返回 None
。
这个钩子是必要的,因为一些部件有多个 HTML 元素,因此有多个 ID。在这种情况下,这个方法应该返回一个与部件标签中第一个 ID 对应的 ID 值。
render
(name, value, attrs=None, renderer=None)¶使用给定的渲染器将部件渲染成 HTML。如果 renderer
为 None
,则使用 FORM_RENDERER
设置中的渲染器。
value_from_datadict
(data, files, name)¶给定一个数据字典和这个部件的名称,返回这个部件的值。files
可能包含来自 request.FILES
的数据。如果没有提供值,则返回 None
。还需要注意的是,在处理表单数据的过程中,value_from_datadict
可能会被调用不止一次,所以如果你自定义它并添加昂贵的处理,你应该自己实现一些缓存机制。
value_omitted_from_data
(data, files, name)¶给定 data
和 files
字典和这个部件的名称,返回该部件是否有数据或文件。
该方法的结果会影响模型表单中的字段 是否回到默认。
特殊情况有 CheckboxInput
、CheckboxSelectMultiple
和 SelectMultiple
,它们总是返回 False
,因为未选中的复选框和未选择的 <select multiple>
,不会出现在 HTML 表单提交的数据中,所以不知道用户是否提交了一个值。
use_required_attribute
(initial)¶给定一个表单字段的 initial
值,返回是否可以用 required
HTML 属性来渲染部件。表单使用这个方法与 Field.required
和 Form.use_required_attribute
一起决定是否为每个字段显示 required
属性。
默认情况下,对隐藏的部件返回 False
,否则返回 True
。特殊情况是 FileInput
和 ClearableFileInput
,当设置了 initial
时,返回 False
;还有 CheckboxSelectMultiple
,总是返回 False
,因为浏览器验证需要选中所有的复选框,而不是至少一个。
在与浏览器验证不兼容的自定义部件中覆盖此方法。例如,一个由隐藏的 textarea
元素支持的 WSYSIWG 文本编辑部件可能希望总是返回 False
以避免浏览器对隐藏字段的验证。
MultiWidget
¶MultiWidget
(widgets, attrs=None)¶MultiWidget
与 MultiValueField
携手合作。
MultiWidget
有一个必要的参数:
widgets
¶一个包含所需部件的迭代器。例如:
>>> from django.forms import MultiWidget, TextInput
>>> widget = MultiWidget(widgets=[TextInput, TextInput])
>>> widget.render('name', ['john', 'paul'])
'<input type="text" name="name_0" value="john"><input type="text" name="name_1" value="paul">'
你可以提供一个字典,以便为每个子部件的 name
属性指定自定义后缀。在这种情况下,对于每个 (key, widget)
对,键将被附加到部件的 name
上,以便生成属性值。你可以为单个键提供空字符串(''
),以便为一个部件压制后缀。例如:
>>> widget = MultiWidget(widgets={'': TextInput, 'last': TextInput})
>>> widget.render('name', ['john', 'paul'])
'<input type="text" name="name" value="john"><input type="text" name="name_last" value="paul">'
还有一个必要的方法:
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()
,其职责与之相反——将所有成员字段的清理值合并为一个。
它提供了一些自定义上下文:
get_context
(name, value, attrs)¶In addition to the 'widget'
key described in
Widget.get_context()
, MultiWidget
adds a
widget['subwidgets']
key.
这些可以在部件模板中循环使用:
{% 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 set up the widget.
所需的方法 decompress()
将一个 datetime.date
的值分解成对应于每个部件的日、月、年的值。如果选择了一个无效的日期,比如不存在的 2 月 30 日,那么 DateField
就会把这个方法传给一个字符串代替,所以需要进行解析。最后的 return
处理的是 value
是 None
的时候,也就是说我们的子部件没有任何默认值。
value_from_datadict()
的默认实现是返回一个与每个 Widget
对应的值列表。这在使用 MultiWidget
与 MultiValueField`
时是合适的。但由于我们想将这个部件与一个 DateField
一起使用,它只取一个值,我们已经覆盖了这个方法。这里的实现将来自子部件的数据组合成一个字符串,其格式为 DateField
所期望的格式。
Django 在 django.forms.widgets
模块中提供了所有基本的 HTML 部件,以及一些常用的部件组,包括 文本输入、各种复选框和选择器、上传文件 和 处理多值输入。
这些部件使用了 HTML 元素 input
和 textarea
。
TextInput
¶TextInput
¶input_type
:'text'
template_name
:'django/forms/widgets/text.html'
<input type="text" ...>
NumberInput
¶EmailInput
¶EmailInput
¶input_type
:'email'
template_name
:'django/forms/widgets/email.html'
<input type="email" ...>
URLInput
¶URLInput
¶input_type
:'url'
template_name
:'django/forms/widgets/url.html'
<input type="url" ...>
PasswordInput
¶DateInput
¶DateInput
¶input_type
:'text'
template_name
:'django/forms/widgets/date.html'
<input type="text" ...>
采用与 TextInput
相同的参数,多一个可选参数:
format
¶显示该字段初始值的格式。
如果没有提供 format
参数,默认的格式是 DATE_INPUT_FORMATS
中找到的第一种格式,并且尊重 本地格式化。
DateTimeInput
¶DateTimeInput
¶input_type
:'text'
template_name
:'django/forms/widgets/datetime.html'
<input type="text" ...>
采用与 TextInput
相同的参数,多一个可选参数:
format
¶显示该字段初始值的格式。
如果没有提供 format
参数,默认的格式是 DATETIME_INPUT_FORMATS
中找到的第一种格式,并且尊重 本地格式化。
默认情况下,时间值的微秒部分总是设置为 0
。如果需要微秒,则使用 supports_microseconds
属性设置为 True
的子类。
TimeInput
¶TimeInput
¶input_type
:'text'
template_name
:'django/forms/widgets/time.html'
<input type="text" ...>
采用与 TextInput
相同的参数,多一个可选参数:
format
¶显示该字段初始值的格式。
如果没有提供 format
参数,默认的格式是 TIME_INPUT_FORMATS
中找到的第一种格式,并且尊重 本地格式化。
关于微秒的处理,请参见 DateTimeInput
。
这些部件使用了 HTML 元素 <select>
、<input type="checkbox">
和 <input type="radio">
。
呈现多个选择的部件有一个 option_template_name
属性,指定用于渲染每个选择的模板。例如,对于 Select
部件,select_option.html
会为 <select>
渲染 <option>
。
CheckboxInput
¶Select
¶NullBooleanSelect
¶NullBooleanSelect
¶template_name
:'django/forms/widgets/select.html'
option_template_name
:'django/forms/widgets/select_option.html'
选择“未知”、“是”和“否”选项的小组件。
SelectMultiple
¶RadioSelect
¶RadioSelect
¶template_name
:'django/forms/widgets/radio.html'
option_template_name
:'django/forms/widgets/radio_option.html'
Similar to Select
, but rendered as a list of radio buttons within
<div>
tags:
<div>
<div><input type="radio" name="..."></div>
...
</div>
So they are announced more concisely by screen readers, radio buttons
were changed to render in <div>
tags.
为了对生成的标记进行更精细的控制,你可以在模板中循环使用单选按钮。假设一个表单 myform
有一个字段 beatles
,使用 RadioSelect
作为它的部件。
<fieldset>
<legend>{{ myform.beatles.label }}</legend>
{% for radio in myform.beatles %}
<div class="myradio">
{{ radio }}
</div>
{% endfor %}
</fieldset>
这将产生以下 HTML:
<fieldset>
<legend>Radio buttons</legend>
<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>
</fieldset>
这包括 <label>
标签。为了得到更多的细节,你可以使用每个单选按钮的 tag
、choice_label
和 id_for_label
属性。例如,这个模板...
<fieldset>
<legend>{{ myform.beatles.label }}</legend>
{% for radio in myform.beatles %}
<label for="{{ radio.id_for_label }}">
{{ radio.choice_label }}
<span class="radio">{{ radio.tag }}</span>
</label>
{% endfor %}
</fieldset>
...将导致以下 HTML:
<fieldset>
<legend>Radio buttons</legend>
<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>
</fieldset>
If you decide not to loop over the radio buttons -- e.g., if your template
includes {{ myform.beatles }}
-- they'll be output in a <div>
with
<div>
tags, as above.
The outer <div>
container receives the id
attribute of the widget,
if defined, or BoundField.auto_id
otherwise.
在循环单选按钮时,label
和 input
标签分别包含 for
和 id
属性。每个单选按钮都有一个 id_for_label
属性来输出元素的 ID。
CheckboxSelectMultiple
¶CheckboxSelectMultiple
¶template_name
:'django/forms/widgets/checkbox_select.html'
option_template_name
:'django/forms/widgets/checkbox_option.html'
类似于 SelectMultiple
,但渲染为一个复选框列表。
<div>
<div><input type="checkbox" name="..." ></div>
...
</div>
The outer <div>
container receives the id
attribute of the widget,
if defined, or BoundField.auto_id
otherwise.
So they are announced more concisely by screen readers, checkboxes were
changed to render in <div>
tags.
像 RadioSelect
一样,你可以循环使用各个复选框来进行部件的选择。与 RadioSelect
不同的是,如果字段是必填的,则复选框不会包含 required
HTML 属性,因为浏览器验证会要求选中所有复选框,而不是至少一个。
在循环复选框时,label
和 input
标签分别包含 for
和 id
属性。每个复选框都有一个 id_for_label
属性来输出元素的 ID。
SplitDateTimeWidget
¶SplitDateTimeWidget
¶template_name
:'django/forms/widgets/splitdatetime.html'
围绕两个小组件的封装器(使用 MultiWidget
): DateInput
代表日期, TimeInput
代表时间。必须使用 SplitDateTimeField
而不是 DateTimeField
。
SplitDateTimeWidget
有几个可选参数:
date_format
¶类似于 DateInput.format
time_format
¶类似于 TimeInput.format
date_attrs
¶time_attrs
¶类似于 Widget.attrs
。一个包含 HTML 属性的字典,要分别在渲染的 DateInput
和 TimeInput
部件上设置。如果没有设置这些属性,则使用 Widget.attrs
代替。
SelectDateWidget
¶SelectDateWidget
¶template_name
:'django/forms/widgets/select_date.html'
围绕三个 Select
部件的封装器:月、日、年各一个。
需要几个可选的参数:
years
¶在“年份”选择框中使用的可选年份列表/年份组。默认值是包含当前年份和未来 9 年的列表。
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
。当使用字符串时,所有的选择框都会有一个带这个标签的空选择。如果 empty_label
是一个由 3 个字符串元素组成的 list
或 tuple
,选择框将有自己的自定义标签。标签的顺序应该是 ('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"),
),
)
12月 13, 2021