小部件¶
小部件是 Django 对 HTML 输入元素的表示。小部件负责处理 HTML 的渲染以及从与小部件对应的 GET/POST 字典中提取数据。
内置小部件生成的 HTML 使用 HTML5 语法,目标是 <!DOCTYPE html>
。例如,它使用布尔属性,例如 checked
,而不是 XHTML 风格的 checked='checked'
。
指定小部件¶
每当您在表单上指定字段时,Django 将使用适合要显示的数据类型的默认小部件。要查找哪个字段使用哪个小部件,请参阅有关 内置字段类 的文档。
但是,如果您想为字段使用不同的窗口小部件,可以使用 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.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 时,它只渲染非常少的标记——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)
>>> print(f)
<div>Name:<input type="text" name="name" required></div>
<div>Url:<input type="url" name="url" required></div>
<div>Comment:<input type="text" name="comment" required></div>
在真实的网页上,您可能不希望每个小部件看起来都一样。您可能希望为注释提供一个更大的输入元素,并且您可能希望“name”小部件具有某些特殊的 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)
>>> print(f)
<div>Name:<input type="text" name="name" class="special" required></div>
<div>Url:<input type="url" name="url" required></div>
<div>Comment:<input type="text" name="comment" size="40" required></div>
您还可以使用 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">'
-
format_value
(value)¶ 清理并返回一个值,用于小部件模板。
value
不保证是有效的输入,因此子类实现应该进行防御性编程。
-
get_context
(name, value, attrs)¶ 返回一个字典,其中包含渲染小部件模板时要使用的值。默认情况下,字典包含一个键,
'widget'
,它是一个包含以下键的小部件字典表示形式'name'
: 来自name
参数的字段名称。'is_hidden'
: 一个布尔值,指示此小部件是否隐藏。'required'
: 一个布尔值,指示此小部件的字段是否必填。'value'
: 由format_value()
返回的值。'attrs'
: 要在渲染的小部件上设置的 HTML 属性。attrs
属性和attrs
参数的组合。'template_name'
:self.template_name
的值。
Widget
子类可以通过覆盖此方法提供自定义上下文值。
-
id_for_label
(id_)¶ 返回此小部件的 HTML ID 属性,供
<label>
使用,给定字段的 ID。如果 ID 不可用,则返回空字符串。此钩子是必要的,因为某些小部件具有多个 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
¶ 一个属性,用于标识小部件在渲染时是否应与
<legend>
一起分组在<fieldset>
中。默认为False
,但当小部件包含多个<input>
标签(如CheckboxSelectMultiple
、RadioSelect
、MultiWidget
、SplitDateTimeWidget
和SelectDateWidget
)时,为True
。
-
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)¶ 除了在
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
中找到的第一个格式,并尊重格式本地化。%U
、%W
和%j
格式不受此小部件支持。
DateTimeInput
¶
-
class
DateTimeInput
¶ input_type
:'text'
template_name
:'django/forms/widgets/datetime.html'
- 呈现为:
<input type="text" ...>
接受与
TextInput
相同的参数,还有一个可选参数-
format
¶ 此字段的初始值将以该格式显示。
如果没有提供
format
参数,则默认格式是DATETIME_INPUT_FORMATS
中找到的第一个格式,并尊重格式本地化。%U
、%W
和%j
格式不受此小部件支持。默认情况下,时间值的微秒部分始终设置为
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
,它有一个使用RadioSelect
作为其小部件的字段beatles
<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
。一个包含要设置在渲染的DateInput
和TimeInput
小部件上的 HTML 属性的字典。如果未设置这些属性,则使用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"), ), )