表單是搜集用戶數據信息,實現網頁數據交互的關鍵。Django的表單功能由Form類實現,主要分兩種:django.forms.Form
和django.forms.ModelForm
。前者是一個基礎的表單功能,后者是在前者的基礎上結合模型所生成的數據表單。
雖然在模板文件中直接編寫表單是一種較為簡單的實現方法,但如果表單元素較多,會在無形之中增加模板的代碼量,對日后維護和更新造成極大的不便。為了簡化表單的實現過程和提高表單的靈活性,Django提供了完善的表單功能。先來看個簡單的例子。
在mysite的index中添加一個form.py用于編寫表單的實現功能,然后在templates文件夾中添加模板data_form.html,用于將表單數據顯示到網頁上。
首先在form.py中添加以下代碼:
#index 的 form.py
from django import forms
from .models import *
class ProductForm(forms.Form):
name = forms.CharField(max_length=20, label='名字')
weight = forms.CharField(max_length=50, label='重量')
size = forms.CharField(max_length=50, label='尺寸')
#設置下拉框的值
## choices_list = [(1,'手機'),(2, '平板')]
choices_list = [(i+1, v['type_name']) for i,v in enumerate(Type.objects.values('type_name'))]
type = forms.ChoiceField(choices=choices_list, label='產品類型')
在form.py中定義表單類ProductForm,類屬性就是表單字段,對應HTML里的每一個控件。
然后在視圖函數中導入form.py所定義的ProductForm類,在index函數中實例化生成對象,再將對象傳遞給模板文件。
#index 的 views.py
from django.shortcuts import render
from .form import *
def index(request):
product = ProductForm()
return render(request, 'data_form.html', locals())
最后在模板文件data_form.html中將對象product以HTML的<table>的形式展現在網頁上。
templates 下的 data_form.html
<html>
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="" method="post">
<table>
{{ product.as_table }}
</table>
<input type="submit" value="提交">
</form>
</body>
</html>
保存后運行結果如下:
表單的定義
Django的表單功能主要是通過定義表單類,再由類的實例化生成HTML的表單元素控件,以減少再模板中的硬編碼。上例中的<table>源代碼如下:
<table>
<tr><th><label for="id_name">名字:</label></th><td><input type="text" name="name" maxlength="20" required id="id_name"></td></tr>
<tr><th><label for="id_weight">重量:</label></th><td><input type="text" name="weight" maxlength="50" required id="id_weight"></td></tr>
<tr><th><label for="id_size">尺寸:</label></th><td><input type="text" name="size" maxlength="50" required id="id_size"></td></tr>
<tr><th><label for="id_type">產品類型:</label></th><td>
<select name="type" id="id_type">
<option value="1">手機</option>
<option value="2">平板電腦</option>
<option value="3">智能穿戴</option>
<option value="4">通用配件</option>
</select></td></tr>
</table>
通過對比,可以很容易發現HTML源代碼和定義的ProductForm類中的字段和參數的對應關系。
除了上述表單字段外,Django還提供多種內置的表單字段:
字段 | 說明 |
---|---|
BooleanField | 復選框,如果字段帶有initial=True, 復選框被勾上 |
CharField | 文本框,參數max_length和min_length |
ChoiceField | 下拉框,參數choices設置數據內容 |
TypedChoiceField | 與ChoiceField相似,但多出參數coerce和empty_value,分別代表強制轉換數據類型和用于表示空值,默認為空字符串 |
DateField | 文本框,具有驗證日期格式的功能,參數input_formats設置日期格式 |
EmailField | 文本框,驗證輸入數據是否符合郵箱格式,可選參數max_length和min_length |
FileField | 文件上傳功能,參數max_length和allow_empty_file分別設置最大長度和文件內容是否為空 |
FilePathField | 在特定的目錄選擇并上傳文件,參數path是必需參數,參數recursive、match、allow_files和allow_folders為可選參數 |
FloatField | 驗證數據是否為浮點數 |
ImageField | 驗證文件是否為Pillow庫可識別的圖像格式 |
IntegerField | 驗證數據是否為整型 |
GenericIPAddressField | 驗證數據是否為有效數值 |
SlugField | 驗證數據是否只包含字母、數字、下劃線及連字符 |
TimeField | 驗證數據是否為datetime.time或指定特定時間格式的字符串 |
URLField | 驗證數據是否為有效的URL地址 |
表單字段除了轉換HTML控件之外,還具有一定的數據格式規范,規范主要由字段類型和字段共同實現。每個不同類型的表單字段都由一些自己特殊的參數,但每個表單字段都具有以下的共同參數:
參數 | 說明 |
---|---|
Required | 輸入數據是否為空,默認為True |
Widget | 設置HTML控件的樣式 |
Label | 生成Label標簽或顯示內容 |
Initial | 設置初始值 |
help_text | 設置幫助提示信息 |
error_messages | 設置錯誤信息,以字典格式表示:{‘required’:'不能為空','invalid':'格式錯誤'} |
show_hidden_initial | 值為True或False,是否在當前控件后面再加一個 隱藏的且具有默認值的控件(可用于檢驗兩次輸入值是否一致) |
Validators | 自定義數據驗證規則。以列表格式表示,列表元素為函數名 |
Lacalize | 值為True/False,是否支持本地化,如不同時區顯示相應的時間 |
Disabled | 值為True/False,是否可以編輯 |
label_suffix | Label內容后綴,在Label后添加內容 |
根據以上參數說明,對form.py的表單ProductForm的字段進行優化:
#form.py
from django import forms
from .models import *
from django.core.exceptions import ValidationError
#自定義數據驗證函數
def weight_validate(value):
if not str(value).isdigit():
raise ValidationError('請輸入正確的重量 ')
class ProductForm(forms.Form):
#設置錯誤信息并設置樣式
name = forms.CharField(max_length=20, label='名字',
widget=forms.widgets.TextInput(attrs={'class':'c2'}),
error_messages={'required':'名字不能為空'})
#使用自定義數據驗證函數
weight = forms.CharField(max_length=50, label='重量',
validators=[weight_validate])
size = forms.CharField(max_length=50, label='尺寸')
#設置下拉框的值
## choices_list = [(1,'手機'),(2, '平板')]
choices_list = [(i+1, v['type_name']) for i,v in enumerate(Type.objects.values('type_name'))]
#設置CSS樣式
type = forms.ChoiceField(widget=forms.widgets.Select(attrs={'class':'type','size':3}),
choices=choices_list, label='產品類型')
優化的代碼分別使用了參數widget、label、error_messages和validators,這4個參數是實際開發中常用的參數。為了進一步驗證優化后的表單是否正確運行,還要對views.py的視圖函數index代碼進行優化
#views.py 的 index 函數
from django.http import HttpResponse
from django.shortcuts import render
from .form import *
def index(request):
#GET請求
if request.method == 'GET':
product = ProductForm()
return render(request, 'data_form.html', locals())
#POST請求
else:
product = ProductForm(request.POST)
if product.is_valid():
#獲取網頁控件name的數據
#方法1
## name = product['name']
#方法2
#cleaned_data將控件name的數據進行清洗,轉換成Python數據類型
name = product.cleaned_data['name']
return HttpResponse('提交成功')
else:
#將錯誤信息輸出,error_msg是將錯誤信息以json格式輸出
error_msg = product.errors.as_json()
return render(request, 'data_form.html', locals())
上述代碼,首先判斷了用戶的請求方式,index函數對GET和POST請求做了不同的響應處理。
- 用戶首次訪問url地址時,等于向項目發送一個GET請求,函數index將表單ProductForm實例化并傳遞給模板,由模板引擎生成HTML表單返回給用戶。
- 當用戶在表單中輸入相關信息并提交時,等于向項目發送一個POST請求,函數index首先獲取表單數據對象product,然后由
is_valid()
方法對數據對象product進行數據驗證。 -
如果驗證成功,可以使用product['name']或prodcut.cleaned_data['name']方法來獲取用戶在某個控件上的輸入值,實現表單和模型的信息交互。
下面來驗證一下是否能正常工作,在姓名欄輸入了空格
image.png
點擊提交按鈕后,
image.png
驗證正常工作了。
在上述例子中,模板data_form.html的表單是使用HTML的<table>標簽展現在網頁上,除此之外,表單還可以用其他HTML標簽展現,只需將模板data_form.html的對象product使用以下方法即可生成其他HTML標簽:
將表單生成HTML的 ul 標簽
{{ product.as_ul }}
將表單生成HTML的 p 標簽
{{ product.as_p }}
生成單個HTML元素控件
{{ product.type }}
獲取表單字段的參數 label 屬性值
{{ product.type.label }}
模型與表單
Django的表單分兩種:基礎表單django.forms.Form
和數據表單django.forms.ModelForm
。數據表單是將模型的字段轉換成表單的字段,再從表單的字段生成HTML的元素控件。Django通過ModelForm表單功能模塊實現了表單數據與模型數據之間的交互開發。
首先再form.py中定義表單ProductModelForm。該類可分為三大部分:添加模型外的表單字段、模型與表單設置和自定義表單字段的數據清洗。
from django import forms
from .models import *
from django.core.exceptions import ValidationError
#數據庫表單
class ProductModelForm(forms.ModelForm):
#步驟1:添加模型外的表單字段
productId = forms.CharField(max_length=20, label='產品序號')
#步驟2:模型與表單設置
class Meta:
#綁定模型,必選
model = Product
#設置轉換字段,必選,屬性值為'__all__'時全部轉換
#fields = '__all__'
fields = ['name','weight','size','type']
#禁止模型轉換的字段,可選,若設置了該屬性,fields則可以不設置
exclude = []
#設置HTML元素控件的label標簽,可選
labels = {'name':'產品名稱',
'weight':'重量',
'size':'尺寸',
'type':'產品類型',
}
#設置表單字段的CSS樣式,可選
widgets = {'name':forms.widgets.TextInput(attrs={'class':'c1'}),
}
#定義字段的類型,可選,默認時自動轉換的
field_classes = {
'name': forms.CharField,
}
#設置提示信息
help_texts = {
'name':'',
}
#自定義錯誤信息
error_messages = {
#設置全部錯誤信息
'__all__':{'required':'請輸入內容',
'invalid':'請檢查輸入內容'},
#設置某個字段的錯誤信息
'weight':{'required':'請輸入重量數值',
'invalid':'請檢查數值是否正確'},
}
#步驟3: 自定義表單字段的數據清洗
def clean_weight(self):
#獲取字段weight的值
data = self.cleaned_data['weight']
return data + 'g'
模型字段轉換成表單字段主要在類Meta中實現,由類Meta的屬性實現兩者之間的轉換,其屬性說明如下:
屬性 | 說明 |
---|---|
model | 必需屬性,用于綁定Model對象 |
fields | 必需屬性,設置模型內哪些字段轉換成表單字段。屬性值為"all"時表示整個模型的字段,若設置一個或多個,使用列表或元組表示,列表或元組里的元素是模型的字段名 |
exclude | 可選屬性,與fields相反,表示禁止模型哪些字段轉換成表單字段。若設置了該屬性,則屬性fields可以不用設置 |
labels | 可選屬性,設置表單字段里的參數label。屬性值以字典表示,字典的鍵是模型的字段 |
widgets | 可選屬性,設置表單字段里的參數widget |
field_classes | 可選,將模型的字段類型重新定義為表單字段類型,默認模型字段類型會自動轉換為表單字段類型 |
help_texts | 可選,設置表單字段里的參數help_text |
error_messages | 可選,設置表單字段里的參數error_messages |
需要注意的是,一些較為特殊的模型字段在轉換表單時會有不同的處理方式。例如模型字段的類型為AutoField,該字段在表單中不存在對應的表單字段;模型字段類型為ForeignKey和ManyToManyField,在表單中對應的表單字段為ModelChoiceField和ModelMultipleChoiceField。
在自定義數據清洗函數時,必須以“clean_字段名”的格式作為函數名,而且函數必須有return返回值。如果在函數中設置了ValidationError異常拋出,那么該函數可視為帶有數據驗證的清洗函數。
通過定義表單類ProductModelForm將模型Product與表單相互結合起來后,還要通過視圖函數來使用和展現表單,繼續沿用前面的模板data_form.html,在項目的urls.py和views.py中分別定義新的URL地址和視圖函數:
#urls.py
path('<int:id>.html', views.model_index),
#views.py 的視圖函數 model_index
def model_index(request, id):
if request.method == 'GET':
instance = Product.objects.filter(id=id)
#判斷數據是否存在
if instance:#如果存在,將數據傳遞給參數instance
product = ProductModelForm(instance=instance[0])
else:#如果不存在,為name字段設置一個默認值
product = ProductModelForm(initial={'name':'iphone XS'})
return render(request, 'data_form.html',locals())
else:
product = ProductModelForm(request.POST)
if product.is_valid():
#獲取并清洗weight的數據
weight = product.cleaned_data['weight']
#數據保存方法1:直接保存到數據庫
## product.save()
#數據保存方法2:save方法設置commit=False,將生成數據庫對象product_db,
#然后對該對象的屬性值修改并保存
product_db = product.save(commit=False)
product_db.name = '我的 iphone'
product_db.save()
#數據保存方法3:save_m2m()保存ManyTOMany的數據模型
## product.save_m2m()
return HttpResponse('提交成功!weight清洗后的數據是:'+weight)
else:
#將錯誤信息以json格式輸出
error_msg = product.errors.as_json()
print(error_msg)
return render(request, 'data_form.html', locals())
函數model_index的處理邏輯和上面的index函數大致相同:
- 首先判斷用戶的請求方式,不同的請求方式做不同的處理。
-
若是GET請求,函數根據URL傳遞的變量id來查找模型Product的數據,如果數據存在,以參數的形式傳遞給表單ProductModelForm的參數instance,在生成網頁時,模型數據會填充到對應的元素控件上,如下圖:image.png
- 若時POST請求,函數首先對表單數據進行驗證,如果驗證失敗,返回失敗信息;驗證成功,則使用cleaned_data方法對字段weight進行清洗,最后將表單數據保存到數據庫。
在以上代碼中,還存在一點小問題,比如產品類型的下拉框數據是模型Type對象,如果要將模型Type的字段type_name的數據在下拉框中展示,可以通過定義模型或定義表單兩方面解決:
定義模型是在定義模型Type時,設置該模型的返回值。
#models.py
class Type(models.Model):
id = models.AutoField(primary_key=True)
type_name = models.CharField(max_length=20)
#設置返回值,若不設置,則默認返回Type對象
def __str__(self):
return self.type_name
再運行一次剛才的網址,可以看到產品類型已經變成了名稱。
如果存在多個下拉框,而且每個下拉框的數據分別取同一個模型的不同字段,那么可以在定義表單類的時候重寫初始化函數
__init__()
#forms.py
class ProductModelForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(ProductModelForm, self).__init__(*args, **kwargs)
#設置下拉框的數據
type_obj = Type.objects.values('type_name')
choices_list = [(i+1, v['type_name']) for i,v in enumerate(Type.objects.values('type_name'))]
self.fields['type'].choices = choices_list
#初始化字段name
self.fields['name'].initial = '我的手機'
表單初始化的方法有以下幾種:
- 在視圖函數中對表單實例化時,設置實例化對象的參數initial。如product = ProductModelForm(initial={'name':'iphone XS'}),參數值以字典格式表示,字典鍵為表單的字段名,該方法適用于所有表單類。
- 在表單類中進行實例化時,如果初始化的數據是一個模型對象的數據,可設置參數instance,如product = ProductModelForm(instance=instance[0]),該方法只適用于ModelForm.
- 定義表單字段時,對表單字段設置初始化參數initial,此方法不適用于ModelForm,如name = forms.CharField(initial='我的手機')。
-
重寫表單類的初始化函數
__init__
,適用于所有表單類,如在初始化函數中設置slef.fields['name'].initial='我的手機'.
數據庫的保存實際上只有save()和save_m2m()方法實現。save()只適合將數據保存在非多對多關系的數據表,而save_m2m只適合將數據保存在多對多關系的數據表。