Django的Form驗證
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="logintest.html" method="post">
<p>用戶名稱:<input type="text" name="username" placeholder="請輸入用戶名"></p>
<p>用戶郵箱:<input type="text" name="email" placeholder="請輸入用戶郵箱"></p>
<p>用戶郵箱:<input type="password" name="pwd" placeholder="請輸入用戶密碼"></p>
{% csrf_token %}
<input type="submit" value="Form提交">
<input id="ajax_submit" type="button" value="Ajax提交">
</form>
<script src="/static/jquery-3.6.0.js"></script>
<script>
$(function () {
$("#ajax_submit").click(function () {
$.ajax({
url: "logintest.html",
data: {username:'root',email:'[email protected]',pwd:'123123',csrfmiddlewaretoken: '{
{ csrf_token }}'},
type:'POST',
success:function (arg) {
console.log(arg);
}
})
})
})
</script>
</body>
</html>
def logintest(req):
if req.method == "GET":
return render(req,'logintest.html')
elif req.method == "POST":
u = req.POST.get("username")
e = req.POST.get("email")
p = req.POST.get("pwd")
print("對獲取的數據進行處理:1,校驗;2,數據庫操作",u,e,p)
return render(req,'logintest.html',{})
存在的幾個問題:
1、對用戶提交的數據進行驗證,要求提示信息准確,即哪個字段的輸入不符合要求,在哪個字段進行提示錯誤信息;
2、如果提交的數據項比較多,後台req.POST.get()會大量出現;
3、如果要進行數據庫操作,如使用filter()或create(),參數中要寫大量如username=,email=,pwd=。。。等長的參數;
4、對於前端,如果數據項驗證失敗,即通過form提交了表單,此時前端的所有數據都清空了,而我們期望正確的輸入框數據還在(當然,ajax提交不涉及這個問題,ajax提交後數據依然存在);
用戶提交數據的驗證:
長度、類型、格式驗證,重用性要高。
驗證分為前端後後端
後端,使用一個模板:
- 郵箱格式
- 用戶,名稱長度>4
- 密碼,長度>7
Django提供了Form驗證類模板:
# 定義模板
from django import forms
class LoginForm(forms.Form):
# 模板中的元素
username = forms.CharField(min_length=4)
email = forms.EmailField()
使用驗證類進行驗證:
def logintest(req):
if req.method == "GET":
return render(req,'logintest.html')
elif req.method == "POST":
obj = LoginForm(req.POST)
# 驗證
status = obj.is_valid()
value_right = obj.clean()
print('value_right:',value_right)
value_error = obj.errors
print('value_error:',value_error)
print('status:',status)
if obj.is_valid():
value_right = obj.clean()
# create(**value_right) 數據庫增加記錄,參數直接使用**value_right
else:
value_error = obj.errors.as_json()
print('value_error_asjson:',value_error)
return render(req,'logintest.html',{})
先定義一個模板,這個模板要繼承自django的forms中的Form類,然後定義元素,注意,這裡的變量名,如username、email不是隨意取的,必須與前台form表單中各提交數據標簽的name值一致,然後就是具有特定驗證功能的CharField()、EmailField()等。在視圖函數中使用這個模板
進行驗證,先實例化一個模板對象,將req.POST作為參數,這樣就會自動獲取POST中的對應值進行驗證。生成對象並不能驗證,還需要調用is_valid()方法,這時才會進行驗證,結果為True或False,驗證無措,才會返回True,只要有一個字段驗證錯誤,就是False。clean()方法是獲取驗證正確的字段的一個字典,errors則是錯誤字段及錯誤信息的一個無序列表字符串,使用as_json()轉換為json字符串。
因為email字段輸入的不符合emailed格式,驗證錯誤,value_error,即errors顯示email的錯誤列表信息,轉換為json字符串顯示格式,is_valid()結果為false。clean()只是正確字段的字典。
這裡有一個問題,就是如果模板中沒有定義的字段,在clean()中不能獲取,如這裡的pwd字段,還需要使用POST.get()獲取。
通過錯誤信息的json格式,可以看到錯誤的種類,即code的值,這裡有invalid——格式不符合,min_length——最小長度不夠,required——字段需要值,即字段為空了等。相應的message就是對應錯誤代碼的說明信息,可以是漢字說明。
# 定義模板
from django import forms
class LoginForm(forms.Form):
# 模板中的元素
username = forms.CharField(min_length=4,error_messages={"min_length":"用戶名長度不能小於4","required":"用戶名不能為空"})
email = forms.EmailField(error_messages={"invalid":"郵箱名格式不符合要求","required":"郵箱不能為空"})
運行打印結果:
value_right: {}
value_error: <ul class="errorlist"><li>username<ul class="errorlist"><li>用戶名不能為空</li></ul></li><li>email<ul class="errorlist"><li>郵箱不能為空</li></ul></li></ul>
status: False
value_error_asjson: {"username": [{"message": "\u7528\u6237\u540d\u4e0d\u80fd\u4e3a\u7a7a", "code": "required"}], "email": [{"message": "\u90ae\u7bb1\u4e0d\u80fd\u4e3a\u7a7a", "code": "required"}]}
轉換為json時,漢字轉換為Unicode編碼了。
對於errors屬性,通過打印結果看,好像是一個字符串,實際是什麼呢?通過type(obj.errors),打印出類型:<class 'django.forms.utils.ErrorDict'>,是一個錯誤字典,看一下這個類的源代碼:
@html_safe
class ErrorDict(dict):
"""
A collection of errors that knows how to display itself in various formats.
The dictionary keys are the field names, and the values are the errors.
"""
def as_data(self):
return {f: e.as_data() for f, e in self.items()}
def get_json_data(self, escape_html=False):
return {f: e.get_json_data(escape_html) for f, e in self.items()}
def as_json(self, escape_html=False):
return json.dumps(self.get_json_data(escape_html))
def as_ul(self):
if not self:
return ''
return format_html(
'<ul class="errorlist">{}</ul>',
format_html_join('', '<li>{}{}</li>', self.items())
)
def as_text(self):
output = []
for field, errors in self.items():
output.append('* %s' % field)
output.append('\n'.join(' * %s' % e for e in errors))
return '\n'.join(output)
def __str__(self):
return self.as_ul()
繼承自字典dict,我們看到了as_json(),返回的是json.dumps(),即轉換為json格式字符串。而其__str__()是返回as_ul(),看as_ul(),其格式就是我們看到的打印的結果。
因為errors是一個<class 'django.forms.utils.ErrorDict'>,使用obj.errors['email']訪問一下,即
print(obj.errors['email'])結果為:<ul class="errorlist"><li>郵箱不能為空</li></ul>,這是不是一個字符串呢?打印類型print(type(obj.errors['email'])),結果為:<class 'django.forms.utils.ErrorList'>,是一個列表,可以通過下標獲取:print('value_error:',value_error['email'][0]),結果:value_error: 郵箱不能為空。
也就是說,在生成obj對象時,相關的錯誤信息就存在對象中了,可以將此對象傳遞給前端:
def logintest(req):
if req.method == "GET":
return render(req,'logintest.html')
elif req.method == "POST":
obj = LoginForm(req.POST)
# 驗證
status = obj.is_valid()
value_right = obj.clean()
print('value_right:',value_right)
value_error = obj.errors
print('value_error:',value_error['email'][0])
print('status:',status)
if obj.is_valid():
value_right = obj.clean()
# create(**value_right) 數據庫增加記錄,參數直接使用**value_right
else:
value_error = obj.errors.as_json()
print('value_error_asjson:',value_error)
return render(req,'logintest.html',{'oo':obj})
<form action="logintest.html" method="post">
<p>用戶名稱:<input type="text" name="username" placeholder="請輸入用戶名"><span>{
{ oo.errors.username.0 }}</span></p>
<p>用戶郵箱:<input type="text" name="email" placeholder="請輸入用戶郵箱"><span>{
{ oo.errors.email.0 }}</span></p>
<p>用戶郵箱:<input type="password" name="pwd" placeholder="請輸入用戶密碼"></p>
前端只需顯示一個錯誤信息,所以只取索引0的值。對於第一個get請求,沒有傳遞oo對象,對於django來說,沒有的對象,返回的就是null,但對於其他語言,有可能出錯。
現在的還有個問題是,form提交後,如果有字段出錯,希望字段還保留輸入的信息,要實現這個功能,就不能我們自己寫input標簽,需要Form來實現。
obj = LoginForm() print(obj['username']) print(obj['email'])
結果為:
<input type="text" name="username" minlength="4" required id="id_username">
<input type="email" name="email" required id="id_email">
對於不傳參數的對象,obj['username']是生成一個input標簽。
傳遞一個參數:obj=LoginForm({'username':'qwert','email':'[email protected]}),則結果為
<input type="text" name="username" value="qwert" minlength="4" required id="id_username">
<input type="email" name="email" value="[email protected]" required id="id_email">
在前端,可以使用這種方式自動生成input標簽:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="logintest.html" method="post">
<p>用戶名稱:{
{ oo.username }}<span>{
{ oo.errors.username.0 }}</span></p>
<p>用戶郵箱:{
{ oo.email }}<span>{
{ oo.errors.email.0 }}</span></p>
<p>用戶郵箱:<input type="password" name="pwd" placeholder="請輸入用戶密碼"></p>
{% csrf_token %}
<input type="submit" value="Form提交">
<input id="ajax_submit" type="button" value="Ajax提交">
</form>
<script src="/static/jquery-3.6.0.js"></script>
<script>
$(function () {
$("#ajax_submit").click(function () {
$.ajax({
url: "logintest.html",
data: {username:'root',email:'[email protected]',pwd:'123123',csrfmiddlewaretoken: '{
{ csrf_token }}'},
type:'POST',
success:function (arg) {
console.log(arg);
}
})
})
})
</script>
</body>
</html>
def logintest(req):
if req.method == "GET":
obj = LoginForm()
return render(req,'logintest.html',{'oo':obj})
elif req.method == "POST":
obj = LoginForm(req.POST)
if obj.is_valid():
value_right = obj.clean()
# create(**value_right) 數據庫增加記錄,參數直接使用**value_right
else:
return render(req,'logintest.html',{'oo':obj})
這樣就能在輸入錯誤後保留原數據。
需要先運行is_valid()然後才能clean()
ajax實現提交驗證,保留原輸入值無需實現,ajax本身不刷新頁面,輸入值一直保持
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
.error-msg{
color: red;
font-size: 16px;
}
</style>
</head>
<body>
<form action="loginajax.html" method="post">
<p>用戶名稱:<input type="text" name="username" placeholder="請輸入用戶名"><span></span></p>
<p>用戶郵箱:<input type="text" name="email" placeholder="請輸入用戶郵箱"></p><span></span></p>
<p>用戶密碼:<input type="password" name="pwd" placeholder="請輸入用戶密碼"></p>
{% csrf_token %}
<input type="submit" value="Form提交">
<input id="ajax_submit" type="button" value="Ajax提交">
</form>
<script src="/static/jquery-3.6.0.js"></script>
<script>
$(function () {
$("#ajax_submit").click(function () {
$.ajax({
url: "loginajax.html",
data: $('form').serialize(),
type:'POST',
success:function (arg) {
$('.error-msg').remove(); // 將具有class=error-msg的所有標簽刪除
var v1 = JSON.parse(arg);
//v1是一個json對象,內容:{status: false, error: '{"email": [{"message": "\\u90ae\\u7bb1\\u540d\\u683c\\u…0d\\u7b26\\u5408\\u8981\\u6c42", "code": "invalid"}]}', data: null}
//注意:其error的值為字符串
if(!v1.status){
var error_obj = JSON.parse(v1.error); //這裡要對v1.error再次進行解析,因為其是字符串,否則下面的each出錯
$.each(error_obj,function (k,v) {
var tag = document.createElement('span');
tag.className = 'error-msg';
tag.innerHTML = v[0].message;
$("input[name='" + k +"']").after(tag);
})
}else {
location.href = 'loginajax.html';
}
}
})
})
})
</script>
</body>
</html>
前端:獲取表單的數據,可以使用serialize()函數,這個獲取到的數據可以直接作為ajax的data值使用。
def loginajax(req):
if req.method == "GET":
return render(req,'loginajax.html')
elif req.method == "POST":
ret = {'status':True,'error':None,'data':None}
obj = LoginForm(req.POST)
if obj.is_valid():
print(obj.clean())
print(req.POST.get('pwd'))
else:
res_str = obj.errors.as_json()
ret['status'] = False
ret['error'] = res_str
return HttpResponse(json.dumps(ret))
後端ajax提交需要直接HttpResponse返回一個json格式的字符串,使用了dumps()方法。
上面的實現方式,對於前端,在錯誤信息處理上,進行了兩次JSON.parse(),有些繁瑣。
對後端返回的錯誤信息進行處理:
def loginajax(req):
if req.method == "GET":
return render(req,'loginajax.html')
elif req.method == "POST":
ret = {'status':True,'error':None,'data':None}
obj = LoginForm(req.POST)
if obj.is_valid():
print(obj.clean())
print(req.POST.get('pwd'))
else:
# res_str = obj.errors.as_json()
# ret['status'] = False
# ret['error'] = res_str
ret['status'] = False
ret['error'] = obj.errors.as_data()
# obj.errors.as_data()的值為:{'username': [ValidationError(['用戶名不能為空'])], 'email': [ValidationError(['郵箱名格式不符合要求'])]}
# 其value中是如下類的實例:django.core.exceptions.ValidationError
# 將其進行一次反序列化,即將ValidationError(['用戶名不能為空'])進行一次反序列化
# 最後將ret反序列化,這樣一共進行了兩次反序列化
# ValidationError(['用戶名不能為空'])進行一次反序列化,因為其在ret中,所以可以在反序列化ret時,使用cls=參數,指定一個進行反序列化的類
return HttpResponse(json.dumps(ret,cls=JsonCustomEncode))
from django.core.validators import ValidationError
class JsonCustomEncode(json.JSONEncoder):
def default(self, field):
if isinstance(field,ValidationError):
return {'code':field.code,'message':field.message}
else:
return json.JSONEncoder.default(self,field)
前端
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
.error-msg{
color: red;
font-size: 16px;
}
</style>
</head>
<body>
<form action="loginajax.html" method="post">
<p>用戶名稱:<input type="text" name="username" placeholder="請輸入用戶名"><span></span></p>
<p>用戶郵箱:<input type="text" name="email" placeholder="請輸入用戶郵箱"></p><span></span></p>
<p>用戶密碼:<input type="password" name="pwd" placeholder="請輸入用戶密碼"></p>
{% csrf_token %}
<input type="submit" value="Form提交">
<input id="ajax_submit" type="button" value="Ajax提交">
</form>
<script src="/static/jquery-3.6.0.js"></script>
<script>
$(function () {
$("#ajax_submit").click(function () {
$.ajax({
url: "loginajax.html",
data: $('form').serialize(),
type:'POST',
success:function (arg) {
$('.error-msg').remove(); // 將具有class=error-msg的所有標簽刪除
var v1 = JSON.parse(arg);
//v1是一個json對象,內容:{status: false, error: '{"email": [{"message": "\\u90ae\\u7bb1\\u540d\\u683c\\u…0d\\u7b26\\u5408\\u8981\\u6c42", "code": "invalid"}]}', data: null}
//注意:其error的值為字符串
if(!v1.status){
// var error_obj = JSON.parse(v1.error); //這裡要對v1.error再次進行解析,因為其是字符串,否則下面的each出錯
var error_obj = v1.error; //對於後端兩次反序列化的返回結果,v1.error是一個json對象了,不需要再次進行JSON.parse解析了,重點
alert(typeof error_obj);
$.each(error_obj,function (k,v) {
// k:username或email
// v:[{},{},{},]
var tag = document.createElement('span');
tag.className = 'error-msg';
tag.innerHTML = v[0].message;
$("input[name='" + k +"']").after(tag);
})
}else {
location.href = 'loginajax.html';
}
}
})
})
})
</script>
</body>
</html>
以上是Form驗證的基本用法,日常開發中需要的其他一些問題:
- 除了字符驗證和郵件驗證,還有哪些驗證,可不可以自己定制規則
- 除了生成input標簽,是否可以生成其他標簽
- 顯示默認值