部件是 Django 對 HTML 輸入元素的表示。部件處理 HTML 的渲染,以及從對應于部件的 GET/POST 字典中提取數據。
內置部件生成的 HTML 使用 HTML5 語法,目標是?<!DOCTYPE?html>
。例如,它使用布爾屬性,如?checked
?而不是 XHTML 風格的?checked='checked'
。
一、指定部件
每當你在表單中指定一個字段時,Django 會使用一個默認的部件來顯示數據類型。要想知道哪個字段使用的是哪個部件,請看 內置 Field 類 的文檔。
但是,如果你想為一個字段使用不同的部件,你可以在字段定義中使用 widget 參數。例如:
from django import formsclass CommentForm(forms.Form):name = forms.CharField()url = forms.URLField()comment = forms.CharField(widget=forms.Textarea)
這將指定一個帶有注釋的表單,該表單使用一個較大的 Textarea 部件,而不是默認的 TextInput 部件。
二、為部件設置參數
許多部件都有可選的額外參數;它們可以在字段上定義部件時進行設置。在下面的例子中, years 屬性被設置為 SelectDateWidget:
?
from django import formsBIRTH_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 部件在你的網頁上看起來都是一樣的。
有兩種方法來定制部件: 每個部件實例 和 每個部件類。
1、樣式化部件實例
如果你想讓一個部件實例看起來與另一個不同,你需要在實例化部件對象并將其分配給表單字段時指定額外的屬性(也許還需要在你的 CSS 文件中添加一些規則)。
例如,采取以下表單:
from django import formsclass 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>
2、樣式化部件類
有了部件,就可以添加靜態資源(css 和 javascript)并更深入地定制它們的外觀和行為。
簡而言之,你需要對部件進行子類化,并且 定義一個內部“Media”類 或者 創建一個"media"屬性。
這些方法涉及到一些高級的 Python 編程,在 表單靜態資源 主題指南中有詳細描述。
五、基礎部件類
基礎部件類 Widget 和 MultiWidget 被所有的 內置部件 子類化,可以作為自定義部件的基礎。
1、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">'
supports_microseconds
屬性,默認為 True。如果設置為 False,則 datetime 和 time 值的微秒部分將被設置為 0。
format_value(value)[source]
清理并返回一個值,供部件模板使用。value 并不能保證是有效的輸入,因此子類的實現應該是防御性的。
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 值,返回是否可以用 required HTML 屬性來渲染部件。表單使用這個方法與 Field.required 和 Form.use_required_attribute 一起決定是否為每個字段顯示 required 屬性。
默認情況下,對隱藏的部件返回 False,否則返回 True。特殊情況是 FileInput 和 ClearableFileInput,當設置了 initial 時,返回 False;還有 CheckboxSelectMultiple,總是返回 False,因為瀏覽器驗證需要選中所有的復選框,而不是至少一個。
在與瀏覽器驗證不兼容的自定義部件中覆蓋此方法。例如,一個由隱藏的 textarea 元素支持的 WSYSIWG 文本編輯部件可能希望總是返回 False 以避免瀏覽器對隱藏字段的驗證。
2、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 MultiWidgetclass SplitDateTimeWidget(MultiWidget):# ...def decompress(self, value):if value:return [value.date(), value.time()]return [None, None]
它提供了一些自定義上下文:
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 formsclass 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 所期望的格式。
?