部件¶
部件是 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 使用单选按钮。
ChoiceField 字段默认使用 Select 小部件。小部件上显示的选项从 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')]
然而,提供 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()
此表单将包括用于 name 和 comment 字段的 TextInput 小部件,以及用于 url 字段的 URLInput 小部件。每个都有默认的渲染 —— 没有 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)[source]¶
- 这个抽象类不能被渲染,但提供了基本属性 - 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)[source]¶
- 返回渲染部件模板时要使用的值的字典。默认情况下,该字典包含一个键, - 'widget',它是一个包含以下键的部件的字典表示:- 'name':- name参数中的字段名称。
- 'is_hidden':表示该部件是否被隐藏的布尔值。
- 'required':表示该部件是否需要该字段的布尔值。
- 'value':- format_value()返回的值。
- 'attrs':拟在渲染的部件上设置的 HTML 属性。- attrs属性和- attrs参数的组合。
- ''template_name':- self.template_name的值。
 - Widget子类可以通过覆盖该方法提供自定义上下文值。
 - id_for_label(id_)[source]¶
- 根据字段的 ID 返回此小部件的 HTML ID 属性,供 - <label>使用。如果没有可用的 ID,则返回空字符串。- 这个钩子是必要的,因为一些部件有多个 HTML 元素,因此有多个 ID。在这种情况下,这个方法应该返回一个与部件标签中第一个 ID 对应的 ID 值。 
 - render(name, value, attrs=None, renderer=None)[source]¶
- 使用给定的渲染器将部件渲染成 HTML。如果 - renderer为- None,则使用- FORM_RENDERER设置中的渲染器。
 - value_from_datadict(data, files, name)[source]¶
- 给定一个数据字典和这个部件的名称,返回这个部件的值。 - files可能包含来自- request.FILES的数据。如果没有提供值,则返回- None。还需要注意的是,在处理表单数据的过程中,- value_from_datadict可能会被调用不止一次,所以如果你自定义它并添加昂贵的处理,你应该自己实现一些缓存机制。
 - value_omitted_from_data(data, files, name)[source]¶
- 给定 - 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)[source]¶
- 给定一个表单字段的 - initial值,返回是否可以用- requiredHTML 属性来渲染部件。表单使用这个方法与- Field.required和- Form.use_required_attribute一起决定是否为每个字段显示- required属性。- 默认情况下,对隐藏的部件返回 - False,否则返回- True。特殊情况是- FileInput和- ClearableFileInput,当设置了- initial时,返回- False;还有- CheckboxSelectMultiple,总是返回- False,因为浏览器验证需要选中所有的复选框,而不是至少一个。- 在与浏览器验证不兼容的自定义部件中覆盖此方法。例如,一个由隐藏的 - textarea元素支持的 WSYSIWG 文本编辑部件可能希望总是返回- False以避免浏览器对隐藏字段的验证。
 
MultiWidget¶
- class MultiWidget(widgets, attrs=None)[source]¶
- 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)对,将将 key 添加到小部件的- 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)[source]¶
- 这个方法从字段中获取一个“压缩”值,然后返回一个“解压缩”值的列表。可以假定输入值有效,但不一定是非空的。 - 这个方法 必须由子类实现,由于值可能是空的,所以实现必须是防御性的。 - “解压”背后的原理是,需要将表单字段的组合值“拆分”成每个部件的值。 - 一个例子是 - SplitDateTimeWidget如何将一个- datetime值变成一个列表,将日期和时间分成两个独立的值:- from django.forms import MultiWidget class SplitDateTimeWidget(MultiWidget): # ... def decompress(self, value): if value: return [value.date(), value.time()] return [None, None] - Tip - 请注意 - MultiValueField有一个补充方法- compress(),其职责与之相反——将所有成员字段的清理值合并为一个。
 - 它提供了一些自定义上下文: - get_context(name, value, attrs)[source]¶
- 除了 - 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¶
NumberInput¶
EmailInput¶
URLInput¶
PasswordInput¶
DateInput¶
DateTimeInput¶
- class DateTimeInput[source]¶
- 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[source]¶
- input_type:- 'text'
- template_name:- 'django/forms/widgets/time.html'
- 渲染为: - <input type="text" ...>
 - 采用与 - TextInput相同的参数,多一个可选参数:- format¶
- 显示该字段初始值的格式。 
 - 如果没有提供 - format参数,默认的格式是- TIME_INPUT_FORMATS中找到的第一种格式,并且尊重 本地格式化。- 关于微秒的处理,请参见 - DateTimeInput。
Textarea¶
选择器和复选框部件¶
这些部件使用了 HTML 元素 <select>、<input type="checkbox"> 和 <input type="radio">。
呈现多个选择的部件有一个 option_template_name 属性,指定用于渲染每个选择的模板。例如,对于 Select 部件,select_option.html 会为  <select> 渲染 <option>。
CheckboxInput¶
Select¶
NullBooleanSelect¶
SelectMultiple¶
RadioSelect¶
- class RadioSelect[source]¶
- 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[source]¶
- 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。
文件上传部件¶
FileInput¶
ClearableFileInput¶
复合部件¶
SplitDateTimeWidget¶
- class SplitDateTimeWidget[source]¶
- 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[source]¶
- 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"), ), ) 
 
 
          