1. 程式人生 > >Django學習筆記(14)——AJAX與Form元件知識補充(區域性鉤子和全域性鉤子詳解)

Django學習筆記(14)——AJAX與Form元件知識補充(區域性鉤子和全域性鉤子詳解)

   我在之前做了一個關於AJAX和form元件的筆記,可以參考:Django學習筆記(8)——前後臺數據互動實戰(AJAX);Django學習筆記(6)——Form表單

 

  我覺得自己在寫Django筆記(8)的時候,我只是對AJAX有個粗略的瞭解,就明白其兩大優點,區域性重新整理和非同步互動。Form元件知識雖然大多數都明白了,但是對區域性鉤子和全域性鉤子還不是很清楚。留了個坑,所以在以後的學習中,肯定會再遇到。

  現在,我感覺自己將關於AJAX和Django的資料互動這部分了解清楚了,而且將Form元件的鉤子也理解透徹了。特意做個筆記記錄自己的學習過程。也是在Django筆記(8)的時候說過的,我將這段內容理清楚了,會寫一個易於理解的文章鞏固,而這篇檔案就是。

  下面我分別將自己所做的AJAX知識筆記,Form元件中區域性鉤子和全域性鉤子的筆記展示一下。其中區域性鉤子和全域性鉤子都做了原始碼分析,是為了更好的理解其原理吧。

 

AJAX知識

1,AJAX請求頭中常見contentType

  資料傳送出去,還需要服務端解析成功才有意義。Python內建了自動解析常見資料格式的功能。服務端通常是根據請求頭(headers)中的Content-Type 欄位獲取請求頭中的訊息主體是用何種方式編碼,再對主體進行解析。所以說到POST提交資料方案,包含了Content-Type 和訊息主體編碼方式兩部分。

  下面我們一起來看看ajax中POST請求的Content-Type。以application開頭的媒體格式型別:

application/xhtml+xml :XHTML格式

application/xml     : XML資料格式

application/atom+xml  :Atom XML聚合格式   

application/json    : JSON資料格式

application/pdf       :pdf格式 

application/msword  : Word文件格式

application/octet-stream : 二進位制流資料(如常見的檔案下載)

application/x-www-form-urlencoded : 
<form encType=””>中預設的encType,form表單資料被編碼為key/value格式
傳送到伺服器(表單預設的提交資料的格式)

  ContentType指的是請求體的編碼型別,常見的有三種:

1.1,application/x-www-form-urlencoded

  這應該是最常見的POST提交資料的方式了。瀏覽器的原生表單<form>預設的提交方式,如果不設定enctype屬性,那麼最終就會以application/x-www-form-urlencoded 方式提交資料。請求類似於下面這樣(無關的請求頭在本文中都省略掉了):

POST http://www.example.com HTTP/1.1
Content-Type: application/x-www-form-urlencoded;charset=utf-8

user=james&age=24

  

  下面將引數的預設使用方法總結如下:

data:        當前ajax請求要攜帶的資料,是一個object物件,ajax方法就會預設地把
它編碼成某種格式(urlencoded:?a=1&b=2)傳送給服務端;此外,ajax預設以get方
式傳送請求。
 
contentType:"application/x-www-form-urlencoded"。傳送資訊至伺服器時內容
編碼型別。用來指明當前請求的資料編碼格式;urlencoded:?a=1&b=2;

  

 

1.2,multipart/form-data

  這又是一個常見的POST資料提交的方式。我們使用表單上傳檔案時,必須讓表單的enctype 等於 multipart/form-data。

  直接看一個例子:

POST http://www.example.com HTTP/1.1
Content-Type:multipart/form-data; boundary=----WebKitFormBoundaryrGKCBY7qhFd3TrwA

------WebKitFormBoundaryrGKCBY7qhFd3TrwA
Content-Disposition: form-data; name="user"

james
------WebKitFormBoundaryrGKCBY7qhFd3TrwA
Content-Disposition: form-data; name="file"; filename="chrome.png"
Content-Type: image/png

PNG ... content of chrome.png ...
------WebKitFormBoundaryrGKCBY7qhFd3TrwA--

  這個例子稍微複雜點,首先生成了一個boundary 用於分割不同的欄位,為了避免與正文內容重複,boundary很長很複雜。然後Content-Type裡指明瞭資料是以 multipart/form-data 來編碼。

  本次請求的 boundary 是什麼內容?

  訊息主體裡是按照欄位個數又分為多個結構類似的部分,每部分都是以 --boundary 開始。緊接著是內容描述資訊,然後是回車,最後是欄位具體內容(文字或者二進位制)。如果傳輸的是檔案,還要包含文字名和檔案型別資訊。訊息主體最後以 --boundary-- 標識結束。關於 multipart/form-data 的詳細定義,請前往 rfc 1967 檢視。

  這種方式一般用來上傳檔案,各大服務端語言對它也有著良好的支援。

  上面提到的這兩種POST資料的方式,都是瀏覽器原生支援的,而且現階段標準中原生表單也只支援這兩種方式。其實enctype 還支援 text/plain,不過用的非常少。

  隨著越來越多的Web站點,尤其是WebAPP,全部使用AJAX進行資料互動之後,我們完全可以定義新的資料提交方式,給開發帶來更多便利。

 

1.3,application/json

  application/json 這個Content-Type 作為響應頭大家肯定不陌生。實際上,現在越來越多的人吧他作為請求頭,用來告訴服務端訊息主體是序列化的JSON字串。由於JSON規範的流行,處理低版本IE之外的各大瀏覽器都支援原生的JSON.stringify,服務端語言也有處理JSON的函式,使用JSON不會遇到什麼麻煩。

 

  上述這種預設引數形式,data中的csrf跨站請求偽造鍵值對會被中介軟體自動識別,ContentType引數還有如下一種形式,介紹如下:

contentType:"application/json",即向伺服器傳送一個json字串。
 
注意:contentType:"application/json"一旦設定,data必須是json字串,不能是json物件

  這種型別,使用request.POST 無法顯示,這種型別要使用 request.body才能顯示資料。

1.4,text/xml(不常用)

  相比於JSON,XML不能更好的適用於資料交換,它包含了太多的包裝,而且它跟大多數程式語言的資料模型不匹配,讓大多數程式設計師感到詫異,XML是面向資料的,JSON是面向物件和結構的,後者會給程式設計師一種更加親切的感覺。

  我們現在一般這樣來使用:

  • 1,XML儲存資料,儲存配置檔案等需要結構化儲存的地方使用;
  • 2,資料傳輸,資料互動使用JSON

  下面就是 ajax Content-Type 為 text/xml 的請求:

 

2,檔案上傳

  注意:我們使用表單上傳檔案時,必須讓表單的enctype 等於 multipart/form-data。

2.1  基於form表單的檔案上傳

  html程式碼:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>upload</title>
</head>
<body>
 
{# 檔案上傳的form#}
{#<form method="post" enctype="multipart/form-data">#}
<form method="post" action="/user8_book/upload.html/" enctype="multipart/form-data">
    {% csrf_token %}
    <p><input id="name" type="text"  name="input-name" placeholder="請輸入檔名稱" ></p>
    <p><input id="file" type="file" name="file"></p>
    <p><input id="submit" type="submit" value="submit"></p>
</form>
 
</body>
</html>

  view檢視函式:

from django.shortcuts import render, HttpResponse
import os

# Create your views here.

def index(request):
    if request.method == 'POST':
        obj = request.FILES.get('file')
        print("獲取檔案的名稱: ", obj.name)

        f = open(os.path.join('statics/upload', obj.name), 'wb')
        for line in obj.chunks():
            f.write(line)
        f.close()

        # POST請求資料
        print("POST 請求資料: ", request.POST)
        # GET 請求資料
        print("上傳的檔案資料: ", request.FILES)

        return HttpResponse("upload OK")

    return render(request, 'file_put/index.html')

  我們上傳資料後,在後臺終端可以看到下面資訊:

獲取檔案的名稱:  distance.jpg

POST 請求資料:  <QueryDict: {'csrfmiddlewaretoken': 
['uHoRIy1DrQyS2nD87UndwfoQXH9KEd5zEcIpzjZPjo2zk84TfBatR9QCbaAmckFh'], 
'user': ['file1']}>

上傳的檔案資料:  <MultiValueDict: {'avatar': [<InMemoryUploadedFile: distance.jpg 
(image/jpeg)>]}>

  

2.2 基於AJAX檔案上傳

  html程式碼:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    
    <script src="http://ajax.aspnetcdn.com/ajax/jquery/jquery-2.1.4.min.js"></script>
    
</head>
<body>

    <h2>基於AJAX檔案上傳</h2>
    <form action="" method="post" enctype="multipart/form-data">
         {% csrf_token %}
        <p>username:<input type="text" name="user"  placeholder="請輸入檔名稱"></p>
        <p>photo:<input type="file" name="avatar" ></p>
        <p><input type="submit" value="ajax submit" class="btn"></p>
    </form>
</body>

<script>
    //  ajax上傳
    $('.btn').click(function () {
        var formdata = new FormData();
        formdata.append('user', $("#user").val());
        formdata.append('avatar', $("#avatar")[0].files[0]);

        $.ajax({
            url: '',
            type: 'post',
            contentType: false,
            processData: false,
            data: formdata,
            success: function (data){
                console.log(data)
            }
        })
    })
</script>
</html>

  注意1:使用ajax上傳檔案的時候,前端程式碼必須出現下面:

            contentType: false,
            processData: false,

  注意2:AJAX(Formdata)是什麼呢?

  XMLHttpRequest Level 2 添加了一個新的介面 FormData,利用FormData物件,我們可以通過JavaScript用一些鍵值對來模擬一系列表單控制元件,我們還可以使用XMLHttpRequest 的 send()  方法來非同步的提交這個“表單”。比起普通的 ajax,使用FormData的最大優點就是我們可以非同步上傳一個二進位制檔案。

  所有主流瀏覽器的較新版本都已經支援這個物件了。比如Chtome 7+, Firefox 4+, IE 10+,Opera 12+,Safari 5+ 等。

   view函式:

from django.shortcuts import render, HttpResponse
import os

# Create your views here.

def index(request):
    if request.method == 'POST':
        obj = request.FILES.get('avatar')
        print("獲取檔案的名稱: ", obj.name)

        f = open(os.path.join('statics/upload', obj.name), 'wb')
        for line in obj.chunks():
            f.write(line)
        f.close()

        # POST請求資料
        print("POST 請求資料: ", request.POST)
        # GET 請求資料
        print("上傳的檔案資料: ", request.FILES)

        return HttpResponse("upload OK")

    return render(request, 'file_put/index.html')

def ajax_put(request):
    if request.method == 'POST':
        obj = request.FILES.get('avatar')
        print("獲取檔案的名稱: ", obj)
        # POST請求資料
        print("POST 請求資料: ", request.POST)
        # 檔案的請求資料
        print("上傳的檔案資料: ", request.FILES)
        f = open(os.path.join('statics/upload', obj.name), 'wb')
        for line in obj.chunks():
            f.write(line)
        f.close()

        return HttpResponse("upload OK")

    # if request.method == 'POST':
    #     # 請求報文中的請求體
    #     print(request.body)
    #     print(request.POST)
    #     print(request.FILES)
    #     file_obj = request.FILES.get('avatar')
    #     with open(file_obj.name, 'wb') as f:
    #         for line in file_obj:
    #             f.write(line)
    #     return HttpResponse("AJAX OK")

    return render(request, 'file_put/ajax_index.html')

  我們上傳資料後,在後臺終端可以看到下面資訊:

獲取檔案的名稱:  50788990.jpg

POST 請求資料:  <QueryDict: {'csrfmiddlewaretoken': 
['x1KicPbnZ6k7lg7AeerN4WBfUC14JLeoHw4Q3A9zREOOD1ylmVe3pQ3185sGhSO6'], 
'user': ['file132']}>

上傳的檔案資料:  
<MultiValueDict: {'avatar': [<InMemoryUploadedFile: 50788990.jpg (image/jpeg)>]}>

  

Form元件學習

1,什麼是forms元件

  forms元件就是一個類,可以檢查前端傳來的資料,是否合法。

  例如前端傳來的郵箱資料,判斷郵箱格式是否正確,使用者名稱不能以什麼開頭等等。

2,form元件的使用語法

  簡單舉個例子說一下:

from django.shortcuts import render, HttpResponse
from django import forms

# 1.先寫一個類,繼承Form
class MyForm(forms.Form):
    # 定義一個屬性,可以用來校驗字串型別
    # 限制最大長度是8,最小長度是3
    name=forms.CharField(max_length=8,min_length=3)
    pwd=forms.CharField(max_length=8,min_length=3,required=True)
    # 校驗是否是郵箱格式
    email=forms.EmailField()

    

# 2.在檢視函式中使用MyForm來校驗資料
# 例項化產生物件,傳入要校驗的資料(可以傳字典字典,也可以不傳)
myform=MyForm(request.POST)

# 3.校驗,is_valid如果是true表示校驗成功(滿足myform裡的條件),反之,校驗失敗
if myform.is_valid():
    # myform.clean_data 表示校驗通過的資料
    print(myform.cleaned_data)
    return HttpResponse('校驗成功')
else:
    print(myform.cleaned_data)
    #校驗失敗的資訊,myform.errors  可以當成一個字典,它是所有錯誤資訊{name:[列表,]}
    # 每個欄位.errors 是一個列表,表示每個欄位的錯誤資訊
    print(myform.errors)
    return HttpResponse('校驗失敗')

  方法總結:

  • myform.clean_data 驗證通過的資料
  • myform.errors 錯誤資料的物件
  • myform.errors.as_data 錯誤資料的資訊

3,渲染模板

  form元件可以在檢視函式中使用,也可以在前端模板中使用。我們展示一下檢視層和模板層。

檢視層:

# 檢視層:
def index(request):
    myform = Myform()
    return render(request,'index.html',local())

  

模板層:

# 模板層
# 1.渲染方式一:
    <form action='' method='post'>
        使用者名稱:{{myform:name}} <br>
        <input type='submit' value = '提交'></input>
    </form>
    # 這裡的{{myform:name}} 和你寫input框是一樣的效果,就是屬性比input框多一點

# 2.渲染方式二(推薦使用):
    <form action='' method='post'>
        {% for foo in myform%}
            {{ foo.lable }} : {{ foo }}  <br>
        <input type='submit' value = '提交'></input>
    </form>
    # 頁面顯示都是一樣的,foo.lable不是使用者名稱,是name,但是可以在建立Myform類時,在CharFiel中新增lable='使用者名稱',這樣就行了。

# 3.渲染方式三:
    <form action='' method='post'>
         {{ myform.as_p }}
        <input type='submit' value = '提交'></input>
    </form>

  下面舉3個例子,詳細說一下這三種渲染方式

3.1  form元件的渲染方式1

  html程式碼如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>

<form action="" method="post">
    {% csrf_token %}
    <p>username: <input type="text" name="name"></p>
    <p>password:<input type="text" name="pwd"></p>
    <p>re_password:<input type="text" name="r_pwd"></p>
    <p>email:<input type="text" name="email"></p>
    <p>telephone: <input type="text" name="tel"></p>
    <p><input type="submit"></p>
</form>

<hr>
<h3>渲染方式1 form元件的渲染</h3>
<form action="" method="post">
    {% csrf_token %}
    <p>username:
        {{ form.name }}
    </p>
    <p>password:
        {{ form.pwd }}
    </p>
    <p>re_password:
        {{ form.r_pwd }}
    </p>
    <p>email:
        {{ form.email }}
    </p>
    <p>telephone:
        {{ form.tel }}
    </p>
    <p><input type="submit"></p>
</form>

</body>
</html>

  views.py

from django.shortcuts import render,HttpResponse

# Create your views here.

from django import forms

class UserForm(forms.Form):
    name = forms.CharField(min_length=3)
    pwd = forms.CharField(min_length=3)
    r_pwd = forms.CharField(min_length=3)
    email = forms.EmailField()
    # 手機號碼的規則固定為11位,***
    tel = forms.CharField(min_length=3)

def reg(request):
    if request.method == 'POST':
        # print(request.POST)
        # form = UserForm({'name': 'yuan', 'email': '123'})
        # print(form.is_valid())

        form = UserForm(request.POST)
        print(form.is_valid())
        if form.is_valid():
            print(form.cleaned_data)
        else:
            print(form.errors)


        return HttpResponse("OK")

    form = UserForm()

    return render(request, 'form/reg.html', locals())

  

3.2  form元件渲染方式2

  如果變數太多了,我們不可能這樣寫很多個,所以我們使用for迴圈。

<hr>
<h3>渲染方式2 form元件的渲染</h3>
<form action="" method="post">
    {% csrf_token %}
    {% for foo in form  %}
        <div>
            <label for="">{{ foo.label }}</label>
            {{ foo }}
        </div>
    {% endfor %}

    <p><input type="submit"></p>
</form>

  我們可以在form函式裡面設定label標籤:

class UserForm(forms.Form):
    name = forms.CharField(min_length=3, label='使用者名稱')
    pwd = forms.CharField(min_length=3, label='密碼')
    r_pwd = forms.CharField(min_length=3, label='確認密碼')
    email = forms.EmailField( label='郵箱')
    # 手機號碼的規則固定為11位,***
    tel = forms.CharField(min_length=3, label='電話')

  

3.3  form元件渲染方式3

<hr>
<h3>渲染方式3 form元件的渲染</h3>
<form action="" method="post">
    {% csrf_token %}
    {{ form.as_p }}

    <p><input type="submit"></p>
</form>

  但是這種方式不推薦使用。因為其固定死了格式。簡單測試的時候可以使用。

   三種效果都一樣,結果也是,這裡就不再展示了。我們推薦使用第二種渲染方式。

4,form元件渲染錯誤資訊

   當返回請求的資料失敗的時候,我們如何渲染錯誤資訊

<form action='' method='post'>
    {% for foo in myform%}
        {{ foo.lable }} : {{ foo }} <span>{{foo.errors.0}}</span><br>
    <input type='submit' value = '提交'></input>
</form>

 舉個例子:

  檢視函式:

def register(request):

    if request.method=="POST":
        form=UserForm(request.POST)
        if form.is_valid():
            print(form.cleaned_data)       # 所有乾淨的欄位以及對應的值
        else:
            print(form.cleaned_data)       #
            print(form.errors)             # ErrorDict : {"校驗錯誤的欄位":["錯誤資訊",]}
            print(form.errors.get("name")) # ErrorList ["錯誤資訊",]
        return render(request,"register.html",locals())
    form=UserForm()
    return render(request,"register.html",locals())

  模板:

<form action="" method="post" novalidate>
    {% csrf_token %}
    
    {% for field in form %}
        <div>
            <label for="">{{ field.label }}</label>
            {{ field }} <span class="pull-right" style="color: red">{{ field.errors.0 }}</span>
        </div>
    {% endfor %}
    <input type="submit" class="btn btn-default">

</form>

  

 

5,Form元件校驗的區域性鉤子和全域性鉤子

5.1,什麼是區域性鉤子

  定義一個函式,名字叫:clean_欄位名字,內部,取出該欄位,進行校驗。如果通過,將該欄位返回,如果失敗,丟擲異常(ValidationError)。

  其中ValidationError的異常型別需要引入:

from django.core.exceptions import ValidationError

  

5.2,區域性鉤子的原始碼分析

   首先,我們分析一段原始碼。關於鉤子函式:

  從is_valid()點進去,然後點選errors:(也就是 form/forms.py的原始碼)

我們可以看到下面:

  點進去full_clean(),然後檢視_clean_filed_() 方法

  注意(看原始碼訣竅):看原始碼的時候,看不懂就過!!

  這裡我們檢視_clean_fields()函式,這是區域性鉤子的應用

   首先,從fileds欄位中匯入其內容,當沒有問題的時候,嘗試判斷其是否合法,如果合法的話,我們將vlaue欄位賦值給 cleaned_data[name],然後利用一個反射函式,嘗試呼叫區域性鉤子,如果有區域性鉤子函式,我們呼叫並執行,如果校驗成功,我們將name 的值返回到clean_data,並寫入clean_data字典中,也就是更新字典,如果出錯的話,就丟擲異常,將異常資訊以鍵值對({'name': value} 寫入 errors 字典中)。

  如果出錯的話,區域性鉤子丟擲的異常會新增到該欄位中的錯誤資訊,我們在前臺獲取錯誤資訊的方法如下:

    for迴圈生成 input 框

    {{ foo.errrs.0 }}

  

5.3,區域性鉤子示例:

 # 函式名稱必須以 claen_欄位名  的格式
    def clean_name(self):
        # 目的:如果使用者名稱已經註冊,則報異常
        val = self.cleaned_data.get('name')

        ret = UserInfo.objects.filter(name=val)
        if not ret:
            return val
        else:
            raise ValidationError('使用者名稱已經被註冊')

    def clean_tel(self):
        # 目的:校驗手機號碼長度為11
        val = self.cleaned_data.get('tel')

        if len(val) == 11:
            return val
        else:
            raise ValidationError("手機號碼格式錯誤")

5.4,什麼是全域性鉤子

  在寫註冊使用者的時候,有輸入密碼,確認密碼,可以進行佈局鉤子處理,處理完畢是不是在進行判斷,判斷其是否相等,相等的話就存到資料庫中,不相等就丟擲異常。

  全域性鉤子主要應用場景就是每次校驗多個欄位,而區域性鉤子每次取的是單個欄位,單個變數。

5.5,全域性鉤子的原始碼分析

  下面繼續分析一段原始碼。

  從cleaned_data() 點選進去,我們會發現 clean_form執行的方法是 clean()方法。

  下面我們看clean()方法:

  我們會發現 預設的clean()方法什麼都沒有寫,直接返回的一個 cleaned_data!!所以這就是給我們寫的,讓我們覆蓋的東西。

  為什麼這麼說呢?

  我們會發現,點進去類 UserInfo中 forms.Form繼承基類方法BaseForm。

  而我們所寫的全域性鉤子中 clean_data也是在基類BaseForm裡面。所以程式碼會先去我們所寫的類中找clean()方法,如果沒有的話,再繼續執行下一步。

  校驗成功的話,和區域性鉤子一樣,更新欄位值,並返回到clean_data字典裡面。但是校驗失敗的話,丟擲的異常和區域性鉤子有點不一樣,他是將異常資訊以鍵值對({'__all__': [value, ]}) 寫入errors字典中。全域性鉤子丟擲的異常會新增到__all__裡面,所以我們在後臺獲取異常資訊

   後臺獲取錯誤資訊是這樣的:

errors = form.errors.get('__all__')

myforms.errors.get('__all__')[0]

  注意先判斷上面的 errors 是否存在,存在的話,在前臺獲取錯誤資訊如下:

{{ myforms.errors.__all__.0 }}

  

5.6,全域性鉤子示例:

# 函式名稱必須命名為clean
    def clean(self):
        pwd = self.cleaned_data.get('pwd')
        r_pwd = self.cleaned_data.get('r_pwd')
        # 首先判斷是否都通過檢測,都不為空,再進行校驗
        if pwd and r_pwd:
            if pwd == r_pwd:
                # 如果兩次密碼相同,則返回乾淨的字典資料
                return self.cleaned_data
            else:
                # 沒通過檢測,則返回異常資訊
                ValidationError('兩次密碼不一致')
        else:
            # 如果獲取的兩個變數中,但凡有一個為空,我們就不需要校驗了
            return self.cleaned_data

  

5.7,一個完整的forms元件校驗

   這裡程式碼主要展示了檢視層和form元件和HTML程式碼。當然前提是我們需要有UserInfo這個資料庫。算了還是展示一下models函式吧

  models.py

from django.db import models

# Create your models here.

class UserInfo(models.Model):
    name = models.CharField(max_length=32)
    pwd = models.CharField(max_length=32)
    email = models.EmailField()
    tel = models.CharField(max_length=32)

  

  MyForms.py

from django import forms

from django.forms import widgets
from form_demo.models import UserInfo

from django.core.exceptions import ValidationError

class UserForm(forms.Form):
    name = forms.CharField(min_length=3, label='使用者名稱',
                           error_messages={'required': '使用者名稱最短是3位!!'},
                           widget=widgets.TextInput(attrs={'class': 'form-control'}))
    pwd = forms.CharField(min_length=3, label='密碼',
                          error_messages={'required': '密碼最短是3位!!'},
                          widget=widgets.PasswordInput(attrs={'class': 'form-control'}))
    r_pwd = forms.CharField(min_length=3, label='確認密碼',
                            error_messages={'required': '密碼最短是3位!!'},
                            widget=widgets.PasswordInput(attrs={'class': 'form-control'}))
    email = forms.EmailField(label='郵箱',
                             error_messages={'required': '不符合郵箱格式!!'},
                            widget=widgets.TextInput(attrs={'class': 'form-control'}))
    # 手機號碼的規則固定為11位,***
    tel = forms.CharField(min_length=3, label='電話',
                          widget=widgets.TextInput(attrs={'class': 'form-control'}))

    # 函式名稱必須以 claen_欄位名  的格式
    def clean_name(self):
        # 目的:如果使用者名稱已經註冊,則報異常
        val = self.cleaned_data.get('name')

        ret = UserInfo.objects.filter(name=val)
        if not ret:
            return val
        else:
            raise ValidationError('使用者名稱已經被註冊')

    def clean_tel(self):
        # 目的:校驗手機號碼長度為11
        val = self.cleaned_data.get('tel')

        if len(val) == 11:
            return val
        else:
            raise ValidationError("手機號碼格式錯誤")

    # 函式名稱必須命名為clean
    def clean(self):
        pwd = self.cleaned_data.get('pwd')
        r_pwd = self.cleaned_data.get('r_pwd')
        # 首先判斷是否都通過檢測,都不為空,再進行校驗
        if pwd and r_pwd:
            if pwd == r_pwd:
                # 如果兩次密碼相同,則返回乾淨的字典資料
                return self.cleaned_data
            else:
                # 沒通過檢測,則返回異常資訊
                ValidationError('兩次密碼不一致')
        else:
            # 如果獲取的兩個變數中,但凡有一個為空,我們就不需要校驗了
            return self.cleaned_data

  

  views.py

from django.shortcuts import render, HttpResponse, redirect

# Create your views here.

from form_demo.Myforms import UserForm


def reg(request):
    if request.method == 'GET':
        form = UserForm()
        return render(request, 'form/reg.html', locals())

    elif request.method == 'POST':
        # form 表單的 name 屬性值應該與forms元件欄位名稱一致
        form = UserForm(request.POST)
        print(form.is_valid())
        # print(forms.clean)
        if form.is_valid():
            print(form.cleaned_data)
            # 校驗全部通過,建立資料時,從clean_data中獲取資料,
            # 但是必須將其中多於的資料pop掉,如下面程式碼
            # myform.cleaned_data.pop('re_pwd')
            # models.User.objects.create(**myform.cleaned_data)
            return redirect('http://www.baidu.com')
        else:
            all_error = form.errors.get('__all__')
            if all_error:
                all_error = all_error[0]
            print(form.errors)
            print(form.errors.get('__all__'))
            # return render(request, 'form/reg.html', locals())


    return render(request, 'form/reg.html', locals())

  

 reg.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
        <!-- 最新版本的 Bootstrap 核心 CSS 檔案 -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">


</head>
<body>

<hr>
<hr>


<div class="container">
    <div class="row">
        <div class="col-md-6 col-lg-offset-3">
            <form action="" method="post">
                {% csrf_token %}
                <p><label>{{ form.name.label }}</label>
                    {{ form.name }}<span class="pull-right error">{{ form.name.errors.0 }}</span>
                </p>
                <p><label>{{ form.pwd.label }}</label>
                    {{ form.pwd }}<span class="pull-right error">{{ form.pwd.errors.0 }}</span>
                </p>
                <p><label>{{ form.r_pwd.label }}</label>
                    {{ form.r_pwd }}<span class="pull-right error">{{ form.r_pwd.errors.0 }}</span><span>{{ all_error }}</span>
                </p>
                <p><label>{{ form.email.label }}</label>:
                    {{ form.email }}<span class="pull-right error">{{ form.email.errors.0 }}</span>
                </p>
                <p><label>{{ form.tel.label }}</label>:
                    {{ form.tel }}<span class="pull-right error">{{ form.tel.errors.0 }}</span>
                </p>
                <p><input type="submit"></p>
            </form>
        </div>

    </div>
</div>



</body>
</html>

  

 我們展示一下效果。

1,我們給資料庫存入james這個名字,然後註冊james,我們在前端看效果:

2,我們註冊電話號碼為5位,我們在前端看效果:

3,我們註冊密碼和確認密碼不一致的時候,我們在前端看效果:

 

 

 參考文獻:https://www.cnblogs.com/JetpropelledSnake/p/9397889.html#top(區域性鉤子和全域性鉤子,本來我放的程式碼,但是百度找到了這位老鐵畫的圖的,就借花獻佛,直接拿來用了,自己再加以詳細的解釋)

https://www.cnblogs.com/yuanchenqi/articles/7638956.