Widgets¶
Widget 是 Django 對 HTML 輸入元素的表示。Widget 處理 HTML 的渲染,以及從對應於該 Widget 的 GET/POST 字典中提取資料。
內建 Widget 產生的 HTML 使用 HTML5 語法,目標為 <!DOCTYPE html>
。例如,它使用布林屬性,如 checked
而不是 XHTML 風格的 checked='checked'
。
提示
Widget 不應與表單欄位混淆。表單欄位處理輸入驗證的邏輯,並直接在範本中使用。Widget 處理網頁上 HTML 表單輸入元素的渲染和原始提交資料的提取。但是,Widget 需要指定給表單欄位。
指定 Widget¶
當您在表單上指定欄位時,Django 將使用適合要顯示的資料類型的預設 Widget。若要找出哪個欄位使用哪個 Widget,請參閱關於內建欄位類別的文件。
但是,如果您想要為欄位使用不同的 Widget,您可以使用欄位定義上的widget
引數。例如
from django import forms
class CommentForm(forms.Form):
name = forms.CharField()
url = forms.URLField()
comment = forms.CharField(widget=forms.Textarea)
這會指定一個表單,其中包含使用較大的Textarea
Widget 的註解,而不是預設的TextInput
Widget。
設定 Widget 的引數¶
許多 Widget 都有可選的額外引數;它們可以在定義欄位上的 Widget 時設定。在以下範例中,會為SelectDateWidget
設定years
屬性
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,
)
請參閱內建 Widget,以取得關於哪些 Widget 可用及其接受哪些引數的更多資訊。
從 Select
Widget 繼承的 Widget¶
從Select
Widget 繼承的 Widget 會處理選項。它們會向使用者顯示一個選項列表供選擇。不同的 Widget 會以不同的方式呈現此選擇;Select
Widget 本身使用 <select>
HTML 列表表示法,而RadioSelect
則使用單選按鈕。
Select
Widget 預設用於ChoiceField
欄位。Widget 上顯示的選項繼承自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
屬性的 Widget 也可以用於不基於選項的欄位,例如CharField
,但建議在選項是模型固有的,而不僅僅是表示性 Widget 時,使用基於ChoiceField
的欄位。
自訂 Widget 實例¶
當 Django 將 Widget 渲染為 HTML 時,它只會渲染非常少的標記 - Django 不會新增類別名稱或任何其他 Widget 特定的屬性。這表示,例如,所有TextInput
Widget 在您的網頁上看起來都相同。
有兩種方法可以自訂 Widget:每個 Widget 實例和每個 Widget 類別。
設定 Widget 實例的樣式¶
如果您想要讓一個 Widget 實例看起來與另一個不同,您需要在實例化 Widget 物件並將其指派給表單欄位時,指定其他屬性(或許還要在您的 CSS 檔案中新增一些規則)。
例如,以下列表單為例
from django import forms
class CommentForm(forms.Form):
name = forms.CharField()
url = forms.URLField()
comment = forms.CharField()
此表單將包含用於名稱和註解欄位的TextInput
Widget,以及用於 url 欄位的URLInput
Widget。每個都有預設渲染 - 沒有 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' Widget 具有一些特殊的 CSS 類別。也可以指定 'type' 屬性以使用不同的 HTML5 輸入類型。若要執行此操作,請在使用建立 Widget 時使用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"}))
您也可以在表單定義中修改 Widget
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
。
設定 Widget 類別的樣式¶
使用 Widget,可以新增資產 (css
和 javascript
) 並更深入地自訂其外觀和行為。
簡而言之,您需要建立 Widget 的子類別,並定義「Media」內部類別或建立「media」屬性。
這些方法涉及一些進階的 Python 程式設計,並在表單資產主題指南中詳細說明。
基本 Widget 類別¶
所有內建 Widget都是從基本 Widget 類別Widget
和MultiWidget
建立子類別而來的,並且可以作為自訂 Widget 的基礎。
Widget
¶
- class Widget(attrs=None)[來源]¶
此抽象類別無法渲染,但提供基本屬性
attrs
。您也可以在自訂 Widget 上實作或覆寫render()
方法。- attrs¶
包含要設定在渲染 Widget 上的 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'
:由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¶
一個屬性,用於識別當渲染時,小部件是否應該在
<fieldset>
中與<legend>
分組。預設為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">'
您可以提供一個字典,以便為每個子 widget 的
name
屬性指定自訂後綴。在這種情況下,對於每個(key, widget)
配對,key 將會附加到 widget 的name
屬性上,以產生屬性值。您可以為單一 key 提供空字串 (''
),以抑制某個 widget 的後綴。例如:>>> 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)[原始碼]¶
這個方法會從欄位中取得單一「壓縮」的值,並回傳一個「解壓縮」值的列表。輸入值可以假設為有效的,但不一定是空的。
這個方法**必須由子類別實作**,而且由於值可能是空的,因此實作必須具有防禦性。
「解壓縮」背後的原理是,它有必要將表單欄位的組合值「分割」成每個 widget 的值。
一個範例是
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']
鍵。這些可以在 widget 範本中迴圈使用
{% for subwidget in widget.subwidgets %} {% include subwidget.template_name with widget=subwidget %} {% endfor %}
這是一個範例 widget,它繼承了
MultiWidget
,以在不同的選取方塊中顯示日期,包含日、月和年。這個 widget 預期會搭配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
widget。super()
方法會使用此列表來設定 widget。必要的方法
decompress()
會將datetime.date
值分解為對應於每個 widget 的日、月和年值。如果選取了無效的日期,例如不存在的 2 月 30 日,DateField
會傳遞一個字串給這個方法,因此需要進行剖析。最後的return
會處理value
為None
的情況,這表示我們的子 widget 沒有任何預設值。value_from_datadict()
的預設實作會回傳與每個Widget
對應的值的列表。當搭配MultiValueField
使用MultiWidget
時,這是適當的。但是,由於我們想要搭配DateField
使用這個 widget,而它會取得單一值,因此我們覆寫了這個方法。這裡的實作會將來自子 widget 的資料組合成一個字串,其格式符合DateField
預期的格式。
內建 widget¶
Django 在 django.forms.widgets
模組中提供了所有基本 HTML widget 的表示法,以及一些常用的 widget 群組,包括 文字輸入、各種核取方塊和選取器、上傳檔案和 多值輸入的處理。
處理文字輸入的 Widget¶
這些 widget 會使用 HTML 元素 input
和 textarea
。
TextInput
¶
NumberInput
¶
EmailInput
¶
URLInput
¶
PasswordInput
¶
DateInput
¶
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
。
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[原始碼]¶
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。
檔案上傳小部件¶
FileInput
¶
ClearableFileInput
¶
複合小部件¶
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"), ), )