Django forms
Django
中的forms
元件能夠為前端提交的資料進行驗證,同時也能生成HTML
程式碼,在全棧開發中非常的方便。
但是如果你的專案是前後端分離的,那麼就沒必要使用forms
的渲染HTML
程式碼功能了。
學習forms
元件將分為三步:
1.如何對資料進行驗證
2.如何渲染
HTML
程式碼3.如何將錯誤資訊反饋給頁面
資料驗證
forms
提供的資料驗證非常強大,你可以自定義你的資料驗證格式,也可以使用內建的很多校驗方法。
測試環境
還是先準備好測試環境,在APP
下的tests
中進行如下設定:
from django.test import TestCase
# Create your tests here.
import os
import sys
sys.path.append(
os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
)
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "Djangoforms.settings")
import django
django.setup()
驗證類
forms
元件應該放在一個單獨的檔案中,我在APP
下命建立了一個dataforms.py
檔案,並在裡面書寫了forms
驗證類。
from django import forms
class F1(forms.Form):
username = forms.CharField(max_length=16, min_length=6, label="使用者名稱")
password = forms.CharField(max_length=16, min_length=6, label="密碼")
email = forms.EmailField(label="郵箱") # label是渲染HTML時使用的名字
大概意思非常明瞭:
1.
username
為字元型別,最長為16(字元),最短為6(字元)。2.
password
為字元型別,最長為16(字元),最短為6(字元)。3.
校驗資料
校驗資料時,使用dict
進行傳入並且例項化F1
類。
使用is_valid()
檢視是否全部驗證通過。
使用cleand_data
獲取驗證成功的欄位。
使用errors
獲取驗證失敗的欄位與錯誤資訊。(錯誤資訊是HTML
形式,這個是往頁面上渲染用的)
1.例項化
F1
類時,傳入引數必須與欄位一一對應。可以多傳但是絕對不能少傳,除非欄位限制設定為可以為空。
from dataforms import F1
data = {
"username": "雲崖先生",
"password": "12345678",
"email": "c2323182108@gmail.com",
}
verify_data = F1(data) # 注意!這裡是例項化出了F1的物件
if verify_data.is_valid():
print("全部驗證通過...")
print(verify_data.cleaned_data) # 獲取驗證通過的欄位與資料
else:
print("某個欄位資料沒能通過驗證...")
print(verify_data.errors)
# <ul class="errorlist"><li>username<ul class="errorlist"><li>Ensure this value has at least 6 characters (it has 4).</li></ul></li></ul>
那麼校驗資料就說到這裡。
渲染頁面
forms
元件可以自動生成HTML
程式碼,並且做了make_safe()
處理,因此可以直接在頁面上進行顯示。
檢視函式
還是使用F1
進行驗證,書寫一個檢視函式。這裡面有1個細節點。
from django.shortcuts import render
from django.shortcuts import redirect
from app01 import dataforms
def test(request):
verify_obj = dataforms.F1() # 注意!這裡是傳了一個空物件
if request.method == "POST":
verify_obj = dataforms.F1(request.POST) # request.POST 本身就是一個字典 注意!這裡又例項化出了一個物件
if verify_obj.is_valid():
pass # 寫入資料庫的操作,然後執行redirect跳轉
return redirect("http://www.google.com")
else:
print(verify_obj.errors)
return render(request,"test.html",locals()) # 無論如何都會返回這個頁面
自動生成
自動生成的方式有好幾種,推薦使用手動的方式。
其實每一個
forms
裡面的欄位都是一個例項化物件,而填入的引數則是其中的屬性。我們可以通過.
給他拿出來。
以下是自動生成的方式。
<body>
{{verify_obj.as_ul}} <!-- ul套li -->
{{verify_obj.as_p}} <!-- p標籤 -->
{{verify_obj.as_table}} <!-- table -->
</body>
推薦使用手動生成的方式。
<body>
<!-- 迴圈驗證物件的欄位,label即為其名字 -->
<!-- form表單上使用novalidate屬性將會禁止瀏覽器的檢測 -->
<!-- 如果input外邊又label標籤,可位for屬性設定filed.auto_id 這將自動關聯到input的id -->
<form action="" method="POST" novalidate>
{% for field in verify_obj %}
<p> {{field.label}} : {{field}} {{field.errors.0}}</p>
{% endfor %}
<button type="submit">提交資訊</button>
</form>
</body>
錯誤資訊
我們可以在頁面上對錯誤資訊進行渲染。
經過上面檢視函式的兩次對F1
的例項化,使我們可以做到在重新整理頁面的情況下保留原本輸入的資料。
渲染資訊
由於某個欄位的驗證條件可能有多種,所以錯誤資訊預設是以<ul>
套著<li>
出現的。
所以我們使用{{field.errors.0}}
拿出第一條資訊即可。
<body>
<!-- 迴圈驗證物件的欄位,label即為其名字 -->
<!-- form表單上使用novalidate屬性將會禁止瀏覽器的檢測 -->
<form action="" method="POST" novalidate>
{% for field in verify_obj %}
<p> {{field.label}} : {{field}} {{field.errors.0}}</p>
{% endfor %}
<button type="submit">提交資訊</button>
</form>
</body>
自定錯誤
可以看到,預設的它的錯誤資訊是英文的。
這個時候可以自定製錯誤,針對不同的規則定製不同的錯誤。
class F1(forms.Form):
username = forms.CharField(max_length=16, min_length=6, label="使用者名稱",
error_messages={
"max_length":"使用者名稱不能超過16位",
"min_length":"使用者名稱不能小於6位",
"required":"不能為空",
"invalid":"格式出錯", # 代表全部錯誤
}
)
password = forms.CharField(max_length=16, min_length=6, label="密碼",
error_messages={
"max_length":"密碼不能超過16位",
"min_length":"密碼不能小於6位",
"required":"不能為空",
}
)
email = forms.EmailField(label="郵箱",
error_messages={
"required":"不能為空",
"invalid":"必須是郵箱格式",
}
)
樣式外掛
預設的欄位樣式都不太好看,我們可以利用widget
來使用一些自帶的外掛。
內建外掛
使用內建外掛前要先進行匯入:
from django.forms import widgets
外掛名稱 | 對應input |
---|---|
TextInput | input type="text" |
PasswordInput | input type="password" |
HiddenInput | input type="hidden" |
NumberInput | input type="number" |
EmailInput | input type="email" |
URLInput | input type="url" |
Textarea | textarea |
DateInput | input type="data" |
DateTimeInput | input type="datetime" |
TimeInput | 只獲取時分秒 |
CheckboxInput | input type="checkbox" |
CheckboxSelectMultiple | 多選按鈕 |
Select | select框 |
NullBooleanSelect | select框 三個選項 0 1 null |
SelectMultiple | 可多選的select框 |
RadioSelect | input type="radion" |
FileInput | 可以檢視當前目錄下的所有檔案。作用不大 |
ClearableFileInput | 同上 |
MultipleHiddenInput | 作用不大 |
SplitDateTimeWidget | 作用不大 |
SplitHiddenDateTimeWidget | 作用不大 |
SelectDateWidget | 作用不大 |
常用外掛
以下例舉一些常用的外掛。
# 單radio,值為字串
# user = forms.CharField(
# initial=2, # initial 預設選擇的值
# widget=widgets.RadioSelect(choices=((1,'上海'),(2,'北京'),))
# )
# 單radio,值為字串
# user = forms.ChoiceField(
# choices=((1, '上海'), (2, '北京'),),
# initial=2,
# widget=widgets.RadioSelect
# )
# 單select,值為字串
# user = forms.CharField(
# initial=2,
# widget=widgets.Select(choices=((1,'上海'),(2,'北京'),))
# )
# 單select,值為字串
# user = forms.ChoiceField(
# choices=((1, '上海'), (2, '北京'),),
# initial=2,
# widget=widgets.Select
# )
# 多選select,值為列表
# user = forms.MultipleChoiceField(
# choices=((1,'上海'),(2,'北京'),),
# initial=[1,],
# widget=widgets.SelectMultiple
# )
# 單checkbox
# user = forms.CharField(
# widget=widgets.CheckboxInput()
# )
# 多選checkbox,值為列表
# user = forms.MultipleChoiceField(
# initial=[2, ],
# choices=((1, '上海'), (2, '北京'),),
# widget=widgets.CheckboxSelectMultiple
# )
外掛樣式
如果使用了widget
外掛,我們可以利用attrs
跟上一個字典,來定製外掛的樣式。
注意:不同的類之間,用空格進行分割。
from django import forms
from django.forms import widgets
class F1(forms.Form):
username = forms.CharField(max_length=16, min_length=6, label="使用者名稱",
error_messages={
"max_length":"使用者名稱不能超過16位",
"min_length":"使用者名稱不能小於6位",
"required":"不能為空",
"invalid":"格式出錯", # 代表全部錯誤
},
widget=widgets.TextInput(attrs={"class":"form-control","style":"color:red;font-size:16px;"}),
)
password = forms.CharField(max_length=16, min_length=6, label="密碼",
error_messages={
"max_length":"密碼不能超過16位",
"min_length":"密碼不能小於6位",
"required":"不能為空",
},
widget=widgets.PasswordInput(attrs={"class":"form-control"}),
)
email = forms.EmailField(label="郵箱",
error_messages={
"required":"不能為空",
"invalid":"必須是郵箱格式",
},
widget=widgets.EmailInput(attrs={"class":"form-control"})
)
內建欄位
介紹完外掛後,還需要介紹一下內建欄位。
內建欄位的作用是定義驗證規則,而外掛則是對生成的標籤樣式做美化功能的。
注意不要被相同的欄位名搞混淆了。
通用引數
引數名 | 描述 |
---|---|
required | 是否允許為空,預設False |
label | 生成Label標籤或顯示內容 |
initial | 初始值,常用於SELECT或RADION以及CHECKBOX中 |
help_text | 幫助資訊(在標籤旁邊顯示),沒啥用 |
error_messages | 自定義錯誤資訊 |
validators | 這是一個列表,可以存放多個正則用於驗證 |
localize | 是否支援本地化,對於時間型別的可以選擇為True |
disabled | 是否可以編輯,預設是True |
label_suffix | Lable的字尾,沒什麼用,主要是自動生成HTML程式碼是才能用得上 |
show_hidden_initial | 這個比較有用,一個隱藏的input框,可用於存放使用者的id資訊 |
widget | HTML外掛 |
欄位大全
注意區分,這裡的欄位是forms.欄位名
,不是外掛。
CharField(Field)
max_length=None, 最大長度
min_length=None, 最小長度
strip=True 是否移除使用者輸入空白
IntegerField(Field)
max_value=None, 最大值
min_value=None, 最小值
FloatField(IntegerField)
...
DecimalField(IntegerField)
max_value=None, 最大值
min_value=None, 最小值
max_digits=None, 總長度
decimal_places=None, 小數位長度
BaseTemporalField(Field)
input_formats=None 時間格式化
DateField(BaseTemporalField) 格式:2015-09-01
TimeField(BaseTemporalField) 格式:11:12
DateTimeField(BaseTemporalField)格式:2015-09-01 11:12
DurationField(Field) 時間間隔:%d %H:%M:%S.%f
...
RegexField(CharField)
regex, 自定製正規表示式
max_length=None, 最大長度
min_length=None, 最小長度
error_message=None, 忽略,錯誤資訊使用 error_messages={'invalid': '...'}
EmailField(CharField)
...
FileField(Field)
allow_empty_file=False 是否允許空檔案 沒啥用
ImageField(FileField)
...
注:需要PIL模組,pip3 install Pillow
以上兩個字典使用時,需要注意兩點:
- form表單中 enctype="multipart/form-data"
- view函式中 obj = MyForm(request.POST, request.FILES)
URLField(Field) 必須是URL型別
...
BooleanField(Field) 布林型別
...
NullBooleanField(BooleanField) 可以為空的布林型別
...
ChoiceField(Field)
...
choices=(), 選項,如:choices = ((0,'上海'),(1,'北京'),)
required=True, 是否必填
widget=None, 外掛,預設select外掛
label=None, Label內容
initial=None, 初始值
help_text='', 幫助提示
ModelChoiceField(ChoiceField)
... django.forms.models.ModelChoiceField
queryset, # 查詢資料庫中的資料
empty_label="---------", # 預設空顯示內容
to_field_name=None, # HTML中value的值對應的欄位
limit_choices_to=None # ModelForm中對queryset二次篩選
ModelMultipleChoiceField(ModelChoiceField)
... django.forms.models.ModelMultipleChoiceField
TypedChoiceField(ChoiceField)
coerce = lambda val: val 對選中的值進行一次轉換
empty_value= '' 空值的預設值
MultipleChoiceField(ChoiceField)
...
TypedMultipleChoiceField(MultipleChoiceField)
coerce = lambda val: val 對選中的每一個值進行一次轉換
empty_value= '' 空值的預設值
ComboField(Field)
fields=() 使用多個驗證,如下:即驗證最大長度20,又驗證郵箱格式
fields.ComboField(fields=[fields.CharField(max_length=20), fields.EmailField(),])
MultiValueField(Field)
PS: 抽象類,子類中可以實現聚合多個字典去匹配一個值,要配合MultiWidget使用
SplitDateTimeField(MultiValueField)
input_date_formats=None, 格式列表:['%Y--%m--%d', '%m%d/%Y', '%m/%d/%y']
input_time_formats=None 格式列表:['%H:%M:%S', '%H:%M:%S.%f', '%H:%M']
FilePathField(ChoiceField) 檔案選項,目錄下檔案顯示在頁面中
path, 資料夾路徑
match=None, 正則匹配
recursive=False, 遞迴下面的資料夾
allow_files=True, 允許檔案
allow_folders=False, 允許資料夾
required=True,
widget=None,
label=None,
initial=None,
help_text=''
GenericIPAddressField
protocol='both', both,ipv4,ipv6支援的IP格式
unpack_ipv4=False 解析ipv4地址,如果是::ffff:192.0.2.1時候,可解析為192.0.2.1, PS:protocol必須為both才能啟用
SlugField(CharField) 數字,字母,下劃線,減號(連字元)
...
UUIDField(CharField) uuid型別
鉤子函式
區域性鉤子
在forms
驗證類中,可以定義區域性鉤子。
可以作為二次驗證。
區域性鉤子的名字為clean_欄位名
。
注意,當區域性鉤子函式處理完資料後,應該將資料
return
回去。
# 針對username欄位的提交資料進行二次驗證
def clean_username(self):
username = self.cleaned_data.get("username")
prohibit = {"蛤蟆","維尼熊","跳跳虎"}
for name in prohibit:
if name in username:
self.add_error("username","含有違禁詞彙!") # 新增一個頁面錯誤資訊
return username
全域性鉤子
全域性鉤子可以拿到所有欄位提交的資料資訊,因此非常適合用來做資料庫驗證,驗證提交的資料資訊是否存在於資料庫中。
當然你如果是一個註冊頁面,可以用來驗證兩次密碼是否輸入一致。
全域性鉤子的名稱為clean
資料庫結構:
from django.db import models
class User(models.Model):
username = models.CharField(max_length=16)
password = models.CharField(max_length=16)
email = models.EmailField(max_length=32)
def __str__(self):
return self.username
全域性鉤子函式:
def clean(self):
res = models.User.objects.filter(**self.cleaned_data).exists()
if not res:
self.ALLERROR = "資料庫中不存在該資料" # 自定義一個例項屬性
raise ValidationError(self.ALLERROR) # 先匯入該異常,再新增上自定義的異常資訊
return self.cleaned_data # 無論如何都進行返回
丟擲異常
丟擲異常分為欄位異常和全域性異常。
如我們再區域性鉤子中發現某個欄位資料有一些問題,則使用欄位異常即可。
self.add_error("username","含有違禁詞彙!") # 新增一個頁面錯誤資訊
同時前端獲取的時候可以直接獲取到,因為0
就是這個異常。
<p> {{verify_obj.username.label}} : {{verify_obj.username}} {{verify_obj.username.errors.0}}</p>
如果丟擲的是全域性異常,那麼頁面上就需要另外的顯示手段了,我這裡直接把它做成一個例項屬性就可以進行取出。
from django.forms import ValidationError # 先匯入全域性異常
self.ALLERROR = "資料庫中不存在該資料" # 自定義一個例項屬性
raise ValidationError(self.ALLERROR) # 觸發異常
前端獲取的話就可以直接用這個物件常量進行獲取。
{{verify_obj.ALLERROR}}
全部程式碼
urls.py
from django.conf.urls import url
from django.contrib import admin
from app01 import views
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^test/', views.test,name="test"),
]
app01/views.py
from django.shortcuts import render
from django.shortcuts import redirect
from app01 import dataforms
def test(request):
verify_obj = dataforms.F1() # 注意!這裡是傳了一個空物件
if request.method == "POST":
verify_obj = dataforms.F1(request.POST) # request.POST 本身就是一個字典 注意!這裡又例項化出了一個物件
if verify_obj.is_valid():
pass # 寫入資料庫的操作,然後執行redirect跳轉
return redirect("http://www.google.com")
else:
print(verify_obj.errors)
return render(request,"test.html",locals())
app01/dataforms.py
from django import forms
from django.forms import widgets
from django.forms import ValidationError
from app01 import models
class F1(forms.Form):
username = forms.CharField(max_length=16, min_length=6, label="使用者名稱",
error_messages={
"max_length":"使用者名稱不能超過16位",
"min_length":"使用者名稱不能小於6位",
"required":"不能為空",
"invalid":"格式出錯", # 代表全部錯誤
},
widget=widgets.TextInput(attrs={"class":"form-control","style":"color:red;font-size:16px;"}),
)
password = forms.CharField(max_length=16, min_length=6, label="密碼",
error_messages={
"max_length":"密碼不能超過16位",
"min_length":"密碼不能小於6位",
"required":"不能為空",
},
widget=widgets.PasswordInput(attrs={"class":"form-control"}),
)
email = forms.EmailField(label="郵箱",
error_messages={
"required":"不能為空",
"invalid":"必須是郵箱格式",
},
widget=widgets.EmailInput(attrs={"class":"form-control"})
)
def clean_username(self):
username = self.cleaned_data.get("username")
prohibit = {"蛤蟆","維尼熊","跳跳虎"}
for name in prohibit:
if name in username:
self.add_error("username","含有違禁詞彙!") # 新增一個頁面錯誤資訊
return username
def clean(self):
res = models.User.objects.filter(**self.cleaned_data).exists()
if not res:
self.ALLERROR = "資料庫中不存在該資料" # 自定義一個例項屬性
raise ValidationError(self.ALLERROR) # 先匯入該異常
return self.cleaned_data # 無論如何都進行返回
app01/models.py
from django.db import models
# Create your models here.
class User(models.Model):
username = models.CharField(max_length=16)
password = models.CharField(max_length=16)
email = models.EmailField(max_length=32)
def __str__(self):
return self.username
templates/test.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link href='https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/css/bootstrap.min.css' rel='stylesheet'>
<script src='https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.min.js'></script>
<script src='https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/3.4.1/js/bootstrap.min.js'></script>
</head>
<body>
<!-- 迴圈驗證物件的欄位,label即為其名字 -->
<!-- form表單上使用novalidate屬性將會禁止瀏覽器的檢測 -->
<form action="" method="POST" class="form" novalidate>
{% for field in verify_obj %}
<p> {{field.label}} : {{field}} {{field.errors.0}}</p>
{% endfor %}
{{verify_obj.ALLERROR}}
<button type="submit">提交資訊</button>
</form>
</body>
</html>
正則驗證
forms
元件允許自定義正則驗證。
如果你的業務需求很刁鑽,則可嘗試使用正則驗證。
方式一
from django.forms import Form
from django.forms import widgets
from django.forms import fields
from django.core.validators import RegexValidator
class MyForm(Form):
user = fields.CharField(
validators=[RegexValidator(r'^[0-9]+$', '請輸入數字'), RegexValidator(r'^159[0-9]+$', '數字必須以159開頭')],
) # 可以寫多個正則,一個一個對欄位資料進行驗證
方式二
import re
from django.forms import Form
from django.forms import widgets
from django.forms import fields
from django.core.exceptions import ValidationError
# 自定義驗證規則
def mobile_validate(value):
mobile_re = re.compile(r'^(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$')
if not mobile_re.match(value):
raise ValidationError('手機號碼格式錯誤')
class PublishForm(Form):
title = fields.CharField(max_length=20,
min_length=5,
error_messages={'required': '標題不能為空',
'min_length': '標題最少為5個字元',
'max_length': '標題最多為20個字元'},
widget=widgets.TextInput(attrs={'class': "form-control",
'placeholder': '標題5-20個字元'}))
# 使用自定義驗證規則
phone = fields.CharField(validators=[mobile_validate, ],
error_messages={'required': '手機不能為空'},
widget=widgets.TextInput(attrs={'class': "form-control",
'placeholder': u'手機號碼'}))
email = fields.EmailField(required=False,
error_messages={'required': u'郵箱不能為空','invalid': u'郵箱格式錯誤'},
widget=widgets.TextInput(attrs={'class': "form-control", 'placeholder': u'郵箱'}))
方式三:自定義方法
from django import forms
from django.forms import fields
from django.forms import widgets
from django.core.exceptions import ValidationError
from django.core.validators import RegexValidator
class FInfo(forms.Form):
username = fields.CharField(max_length=5,
validators=[RegexValidator(r'^[0-9]+$', 'Enter a valid extension.', 'invalid')], )
email = fields.EmailField()
def clean_username(self):
"""
Form中欄位中定義的格式匹配完之後,執行此方法進行驗證
:return:
"""
value = self.cleaned_data['username']
if "666" in value:
raise ValidationError('666已經被玩爛了...', 'invalid')
return value
方式四:同時生成多個標籤進行驗證
from django.forms import Form
from django.forms import widgets
from django.forms import fields
from django.core.validators import RegexValidator
############## 自定義欄位 ##############
class PhoneField(fields.MultiValueField):
def __init__(self, *args, **kwargs):
# Define one message for all fields.
error_messages = {
'incomplete': 'Enter a country calling code and a phone number.',
}
# Or define a different message for each field.
f = (
fields.CharField(
error_messages={'incomplete': 'Enter a country calling code.'},
validators=[
RegexValidator(r'^[0-9]+$', 'Enter a valid country calling code.'),
],
),
fields.CharField(
error_messages={'incomplete': 'Enter a phone number.'},
validators=[RegexValidator(r'^[0-9]+$', 'Enter a valid phone number.')],
),
fields.CharField(
validators=[RegexValidator(r'^[0-9]+$', 'Enter a valid extension.')],
required=False,
),
)
super(PhoneField, self).__init__(error_messages=error_messages, fields=f, require_all_fields=False, *args,
**kwargs)
def compress(self, data_list):
"""
當使用者驗證都通過後,該值返回給使用者
:param data_list:
:return:
"""
return data_list
############## 自定義外掛 ##############
class SplitPhoneWidget(widgets.MultiWidget):
def __init__(self):
ws = (
widgets.TextInput(),
widgets.TextInput(),
widgets.TextInput(),
)
super(SplitPhoneWidget, self).__init__(ws)
def decompress(self, value):
"""
處理初始值,當初始值initial不是列表時,呼叫該方法
:param value:
:return:
"""
if value:
return value.split(',')
return [None, None, None]
實時更新
如果我們頁面上展示的一個select
框中的資料,是從資料庫實時獲取的,那麼是沒辦法做到資料新插入一條訊息頁面也會立即顯示出來這樣的效果的。
定義欄位為類屬性,只會載入一次。
每次例項化物件用到的欄位資料,都是用的類屬性,故不能實時與資料庫更新。
我們把它做成例項屬性即可。
from django import forms
from django.forms import widgets
from app01 import models
class F1(forms.Form):
user_id = forms.CharField(
label="選擇使用者",
)
def __init__(self,*args,**kwargs):
super(F1,self).__init__(*args,**kwargs)
self.fields["user_id"].widget = widgets.Select(
choices=(models.User.objects.values_list("id","username"))
)
# 做成例項,每次進入檢視函式時都會觸發例項物件,故此能動態獲取資料庫中的資料
# values_list [(1,"user1"),(2,"user2")]