部件¶
部件是 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
widgets are used by default on ChoiceField
fields. The
choices displayed on the widget are inherited from the ChoiceField
and
changing ChoiceField.choices
will update Select.choices
. For
example:
>>> 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()
This form will include three default TextInput
widgets, with default
rendering -- no CSS class, no extra attributes. This means that the input boxes
provided for each widget will be rendered exactly the same:
>>> 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">'
If you assign a value of
True
orFalse
to an attribute, it will be rendered as an HTML5 boolean attribute:>>> 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_)¶ 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
¶ An iterable containing the widgets needed. For example:
>>> 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">'
You may provide a dictionary in order to specify custom suffixes for the
name
attribute on each subwidget. In this case, for each(key, widget)
pair, the key will be appended to thename
of the widget in order to generate the attribute value. You may provide the empty string (''
) for a single key, in order to suppress the suffix for one widget. For example:>>> 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
。
TextInput
¶
-
class
TextInput
¶ input_type
:'text'
template_name
:'django/forms/widgets/text.html'
- 渲染为:
<input type="text" ...>
NumberInput
¶
EmailInput
¶
-
class
EmailInput
¶ input_type
:'email'
template_name
:'django/forms/widgets/email.html'
- 渲染为:
<input type="email" ...>
URLInput
¶
-
class
URLInput
¶ input_type
:'url'
template_name
:'django/forms/widgets/url.html'
- 渲染为:
<input type="url" ...>
PasswordInput
¶
DateInput
¶
-
class
DateInput
¶ input_type
:'text'
template_name
:'django/forms/widgets/date.html'
- 渲染为:
<input type="text" ...>
采用与
TextInput
相同的参数,多一个可选参数:-
format
¶ 显示该字段初始值的格式。
如果没有提供
format
参数,默认的格式是DATE_INPUT_FORMATS
中找到的第一种格式,并且尊重 本地格式化。
DateTimeInput
¶
-
class
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
¶
-
class
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
¶
-
class
NullBooleanSelect
¶ template_name
:'django/forms/widgets/select.html'
option_template_name
:'django/forms/widgets/select_option.html'
选择“未知”、“是”和“否”选项的小组件。
SelectMultiple
¶
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>
为了对生成的标记进行更精细的控制,你可以在模板中循环使用单选按钮。假设一个表单
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
。
像 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
-
date_attrs
¶
-
time_attrs
¶ 类似于
Widget.attrs
。一个包含 HTML 属性的字典,要分别在渲染的DateInput
和TimeInput
部件上设置。如果没有设置这些属性,则使用Widget.attrs
代替。
SelectDateWidget
¶
-
class
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"), ), )