部件是 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
为基础的字段。
自定义部件实例
当 Django 将一个部件渲染成 HTML 时,它只渲染了非常少的标记——Django 不会添加类名,或任何其他部件的特定属性。这意味着,例如,所有的 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" 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'}))
你也可以在表单定义中修改一个部件:
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
-
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">'
-
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_) Returns the HTML ID attribute of this widget for use by a
<label>
, given the ID of the field. Returns an empty string if an ID isn't available.这个钩子是必要的,因为一些部件有多个 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_fieldset
- New in Django 4.1.
An attribute to identify if the widget should be grouped in a
<fieldset>
with a<legend>
when rendered. Defaults toFalse
but isTrue
when the widget contains multiple<input>
tags such asCheckboxSelectMultiple
,RadioSelect
,MultiWidget
,SplitDateTimeWidget
, andSelectDateWidget
.
-
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
-
class
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) 除了:meth:Widget.get_context 中描述的
'widget'
键之外,MultiWidget
还增加了一个widget['subwidgets']
键。这些可以在部件模板中循环使用:
{% 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)
构造函数在一个列表中创建了几个
Select
部件。super()
方法使用这个列表来建立部件。所需的方法
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
。
NumberInput
-
class
NumberInput
input_type
:'number'
template_name
:'django/forms/widgets/number.html'
- 渲染为:
<input type="number" ...>
请注意,并不是所有的浏览器都支持在
number
输入类型中输入本地化的数字。Django 本身就避免在localize
属性设置为True
的字段中使用它们。
HiddenInput
-
class
HiddenInput
input_type
:'hidden'
template_name
:'django/forms/widgets/hidden.html'
- 渲染为:
<input type="hidden" ...>
请注意,还有一个
MultipleHiddenInput
部件,封装了一组隐藏的输入元素。
DateInput
-
class
DateInput
input_type
:'text'
template_name
:'django/forms/widgets/date.html'
- 渲染为:
<input type="text" ...>
采用与
TextInput
相同的参数,多一个可选参数:如果没有提供
format
参数,默认的格式是DATE_INPUT_FORMATS
中找到的第一种格式,并且尊重 本地格式化。
DateTimeInput
-
class
DateTimeInput
input_type
:'text'
template_name
:'django/forms/widgets/datetime.html'
- 渲染为:
<input type="text" ...>
采用与
TextInput
相同的参数,多一个可选参数:如果没有提供
format
参数,默认的格式是DATETIME_INPUT_FORMATS
中找到的第一种格式,并且尊重 本地格式化。默认情况下,时间值的微秒部分总是设置为
0
。如果需要微秒,则使用supports_microseconds
属性设置为True
的子类。
TimeInput
-
class
TimeInput
input_type
:'text'
template_name
:'django/forms/widgets/time.html'
- 渲染为:
<input type="text" ...>
采用与
TextInput
相同的参数,多一个可选参数:如果没有提供
format
参数,默认的格式是TIME_INPUT_FORMATS
中找到的第一种格式,并且尊重 本地格式化。关于微秒的处理,请参见
DateTimeInput
。
选择器和复选框部件
这些部件使用了 HTML 元素 <select>
、<input type="checkbox">
和 <input type="radio">
。
呈现多个选择的部件有一个 option_template_name
属性,指定用于渲染每个选择的模板。例如,对于 Select
部件,select_option.html
会为 <select>
渲染 <option>
。
Select
-
class
Select
template_name
:'django/forms/widgets/select.html'
option_template_name
:'django/forms/widgets/select_option.html'
- 渲染为:
<select><option ...>...</select>
-
choices
当表单字段没有
choices
属性时,这个属性是可选的。如果有,当Field
属性更新时,它将覆盖你在这里设置的任何属性。
SelectMultiple
-
class
SelectMultiple
template_name
:'django/forms/widgets/select.html'
option_template_name
:'django/forms/widgets/select_option.html'
类似于
Select
,但允许多选:<select multiple>...</select>
。
RadioSelect
-
class
RadioSelect
template_name
:'django/forms/widgets/radio.html'
option_template_name
:'django/forms/widgets/radio_option.html'
类似于
Select
,但在<div>
标签中呈现为一个单选按钮的列表:<div> <div><input type="radio" name="..."></div> ... </div>
Changed in Django 4.0:为了让屏幕阅读器更简明地宣布它们,单选按钮被改为在
<div>
标签中呈现。为了对生成的标记进行更精细的控制,你可以在模板中循环使用单选按钮。假设一个表单
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>
如果你决定不对单选按钮进行循环处理——例如,如果你的模板包括
{{ myform.beatles }}
——它们将在一个<div>
中输出,并带有<div>
标签,如上所述。外部
<div>
容器接收部件的id
属性(如果定义了),否则是BoundField.auto_id
。在循环单选按钮时,
label
和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'
类似于
SelectMultiple
,但渲染为一个复选框列表。<div> <div><input type="checkbox" name="..." ></div> ... </div>
外部
<div>
容器接收部件的id
属性(如果定义了),否则是BoundField.auto_id
。Changed in Django 4.0:为了让屏幕阅读器更简明地宣布它们,复选框被改为在
<div>
标签中呈现。
像 RadioSelect
一样,你可以循环使用各个复选框来进行部件的选择。与 RadioSelect
不同的是,如果字段是必填的,则复选框不会包含 required
HTML 属性,因为浏览器验证会要求选中所有复选框,而不是至少一个。
在循环复选框时,label
和 input
标签分别包含 for
和 id
属性。每个复选框都有一个 id_for_label
属性来输出元素的 ID。
复合部件
SplitDateTimeWidget
-
class
SplitDateTimeWidget
template_name
:'django/forms/widgets/splitdatetime.html'
围绕两个小组件的封装器(使用
MultiWidget
):DateInput
代表日期,TimeInput
代表时间。必须使用SplitDateTimeField
而不是DateTimeField
。SplitDateTimeWidget
有几个可选参数:-
date_format
类似于
DateInput.format
-
time_format
类似于
TimeInput.format
-
time_attrs
类似于
Widget.attrs
。一个包含 HTML 属性的字典,要分别在渲染的DateInput
和TimeInput
部件上设置。如果没有设置这些属性,则使用Widget.attrs
代替。
SplitHiddenDateTimeWidget
-
class
SplitHiddenDateTimeWidget
template_name
:'django/forms/widgets/splithiddendatetime.html'
类似于
SplitDateTimeWidget
,但对日期和时间使用HiddenInput
。
SelectDateWidget
-
class
SelectDateWidget
template_name
:'django/forms/widgets/select_date.html'
围绕三个
Select
部件的封装器:月、日、年各一个。需要几个可选的参数:
-
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"), ), )
讨论区