部件

部件是 Django 对 HTML 输入元素的表示。部件处理 HTML 的渲染,以及从对应于部件的 GET/POST 字典中提取数据。

内置部件生成的 HTML 使用 HTML5 语法,目标是 <!DOCTYPE html>。例如,它使用布尔属性,如 checked 而不是 XHTML 风格的 checked='checked'

小技巧

部件不应该与 表单字段 混淆。表单字段处理输入验证的逻辑,直接在模板中使用。小组件处理 HTML 表单输入元素在网页上的渲染和原始提交数据的提取。然而,部件确实需要 分配 到表单字段。

指定部件

每当你在表单中指定一个字段时,Django 会使用一个默认的部件来显示数据类型。要想知道哪个字段使用的是哪个部件,请看 内置 Field 类 的文档。

但是,如果你想为一个字段使用不同的部件,你可以在字段定义中使用 widget 参数。例如:

from django import forms

class CommentForm(forms.Form):
    name = forms.CharField()
    url = forms.URLField()
    comment = forms.CharField(widget=forms.Textarea)

这将指定一个带有注释的表单,该表单使用一个较大的 Textarea 部件,而不是默认的 TextInput 部件。

为部件设置参数

许多部件都有可选的额外参数;它们可以在字段上定义部件时进行设置。在下面的例子中, 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 的例子。

样式化部件类

有了部件,就可以添加静态资源(cssjavascript)并更深入地定制它们的外观和行为。

简而言之,你需要对部件进行子类化,并且 定义一个内部“Media”类 或者 创建一个"media"属性

这些方法涉及到一些高级的 Python 编程,在 表单静态资源 主题指南中有详细描述。

基础部件类

基础部件类 WidgetMultiWidget 被所有的 内置部件 子类化,可以作为自定义部件的基础。

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">'

如果你给一个属性分配一个 TrueFalse 的值,它将被渲染成一个 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">'
supports_microseconds

属性,默认为 True。如果设置为 False,则 datetimetime 值的微秒部分将被设置为 0

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。如果 rendererNone,则使用 FORM_RENDERER 设置中的渲染器。

value_from_datadict(data, files, name)

给定一个数据字典和这个部件的名称,返回这个部件的值。files 可能包含来自 request.FILES 的数据。如果没有提供值,则返回 None。还需要注意的是,在处理表单数据的过程中,value_from_datadict 可能会被调用不止一次,所以如果你自定义它并添加昂贵的处理,你应该自己实现一些缓存机制。

value_omitted_from_data(data, files, name)

给定 datafiles 字典和这个部件的名称,返回该部件是否有数据或文件。

该方法的结果会影响模型表单中的字段 是否回到默认

特殊情况有 CheckboxInputCheckboxSelectMultipleSelectMultiple,它们总是返回 False,因为未选中的复选框和未选择的 <select multiple>,不会出现在 HTML 表单提交的数据中,所以不知道用户是否提交了一个值。

use_required_attribute(initial)

给定一个表单字段的 initial 值,返回是否可以用 required HTML 属性来渲染部件。表单使用这个方法与 Field.requiredForm.use_required_attribute 一起决定是否为每个字段显示 required 属性。

默认情况下,对隐藏的部件返回 False,否则返回 True。特殊情况是 FileInputClearableFileInput,当设置了 initial 时,返回 False;还有 CheckboxSelectMultiple,总是返回 False,因为浏览器验证需要选中所有的复选框,而不是至少一个。

在与浏览器验证不兼容的自定义部件中覆盖此方法。例如,一个由隐藏的 textarea 元素支持的 WSYSIWG 文本编辑部件可能希望总是返回 False 以避免浏览器对隐藏字段的验证。

MultiWidget

class MultiWidget(widgets, attrs=None)

MultiWidgetMultiValueField 携手合作。

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 处理的是 valueNone 的时候,也就是说我们的子部件没有任何默认值。

value_from_datadict() 的默认实现是返回一个与每个 Widget 对应的值列表。这在使用 MultiWidgetMultiValueField` 时是合适的。但由于我们想将这个部件与一个 DateField 一起使用,它只取一个值,我们已经覆盖了这个方法。这里的实现将来自子部件的数据组合成一个字符串,其格式为 DateField 所期望的格式。

内置部件

Django 在 django.forms.widgets 模块中提供了所有基本的 HTML 部件,以及一些常用的部件组,包括 文本输入各种复选框和选择器上传文件处理多值输入

处理文本输入的部件

这些部件使用了 HTML 元素 inputtextarea

TextInput

class TextInput
  • input_type'text'
  • template_name'django/forms/widgets/text.html'
  • 渲染为:<input type="text" ...>

NumberInput

class NumberInput
  • input_type'number'
  • template_name'django/forms/widgets/number.html'
  • 渲染为:<input type="number" ...>

请注意,并不是所有的浏览器都支持在 number 输入类型中输入本地化的数字。Django 本身就避免在 localize 属性设置为 True 的字段中使用它们。

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

class PasswordInput
  • input_type'password'
  • template_name'django/forms/widgets/password.html'
  • 渲染为:<input type="password" ...>

需要一个可选的参数:

render_value

确定当验证错误后重新显示表格时,部件是否会有一个值被填入(默认为 False)。

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

显示该字段初始值的格式。

如果没有提供 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

Textarea

class Textarea
  • template_name'django/forms/widgets/textarea.html'
  • 渲染为:<textarea>...</textarea>

选择器和复选框部件

这些部件使用了 HTML 元素 <select><input type="checkbox"><input type="radio">

呈现多个选择的部件有一个 option_template_name 属性,指定用于渲染每个选择的模板。例如,对于 Select 部件,select_option.html 会为 <select> 渲染 <option>

CheckboxInput

class CheckboxInput
  • input_type'checkbox'
  • template_name'django/forms/widgets/checkbox.html'
  • 渲染为:<input type="checkbox" ...>

需要一个可选的参数:

check_test

一个可调用对象,它接受 CheckboxInput 的值,并返回 True,如果该复选框应检查该值。

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 属性更新时,它将覆盖你在这里设置的任何属性。

NullBooleanSelect

class NullBooleanSelect
  • template_name'django/forms/widgets/select.html'
  • option_template_name'django/forms/widgets/select_option.html'

选择“未知”、“是”和“否”选项的小组件。

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'

Similar to Select, but rendered as a list of radio buttons within <div> tags:

<div>
  <div><input type="radio" name="..."></div>
  ...
</div>
Changed in Django 4.0:

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> 标签。为了得到更多的细节,你可以使用每个单选按钮的 tagchoice_labelid_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.

在循环单选按钮时,labelinput 标签分别包含 forid 属性。每个单选按钮都有一个 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>

The outer <div> container receives the id attribute of the widget, if defined, or BoundField.auto_id otherwise.

Changed in Django 4.0:

So they are announced more concisely by screen readers, checkboxes were changed to render in <div> tags.

RadioSelect 一样,你可以循环使用各个复选框来进行部件的选择。与 RadioSelect 不同的是,如果字段是必填的,则复选框不会包含 required HTML 属性,因为浏览器验证会要求选中所有复选框,而不是至少一个。

在循环复选框时,labelinput 标签分别包含 forid 属性。每个复选框都有一个 id_for_label 属性来输出元素的 ID。

文件上传部件

FileInput

class FileInput
  • template_name'django/forms/widgets/file.html'
  • 渲染为:<input type="file" ...>

ClearableFileInput

class ClearableFileInput
  • template_name'django/forms/widgets/clearable_file_input.html'
  • 渲染为 <input type="file" ...>,如果该字段不需要且有初始数据,则增加一个复选框输入,以清除该字段的值。

复合部件

MultipleHiddenInput

class MultipleHiddenInput
  • template_name'django/forms/widgets/multiple_hidden.html'
  • 渲染为:多个 <input type="hidden" ...> 标签

一个处理具有值列表的字段的多个隐藏部件。

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 属性的字典,要分别在渲染的 DateInputTimeInput 部件上设置。如果没有设置这些属性,则使用 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 部件的封装器:月、日、年各一个。

需要几个可选的参数:

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 可以是 stringlist 或者 tuple。当使用字符串时,所有的选择框都会有一个带这个标签的空选择。如果 empty_label 是一个由 3 个字符串元素组成的 listtuple,选择框将有自己的自定义标签。标签的顺序应该是 ('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"),
    ),
)