1. 程式人生 > >Django——form元件is_valid校驗機制

Django——form元件is_valid校驗機制

#先來歸納一下整個流程
#(1)首先is_valid()起手,看seld.errors中是否值,只要有值就是flase
#(2)接著分析errors.裡面判斷_errors是都為空,如果為空返回self.full_clean(),否則返回self._errors
#(3)現在就要看full_clean(),是何方神聖了,裡面設定_errors和cleaned_data這兩個字典,一個存錯誤欄位,一個儲存正確欄位。
#(4)在full_clean最後有一句self._clean_fields(),表示校驗欄位
#(5)在_clean_fields函式中開始迴圈校驗每個欄位,真正校驗欄位的是field.clean(value),怎麼校驗的不管
#(6)在_clean_fields中可以看到,會將欄位分別新增到_errors和cleaned_data這兩個字典中
#(7)結尾部分還設定了鉤子,找clean_XX形式的,有就執行。執行錯誤資訊也會新增到_errors中
#(8)整個校驗過程完成
#下面分析form元件中is_valid校驗的流程
#在分析過程中重點關注_erroes和clean_data這兩個字典
def login(request):
    if request.method == "POST":
        form_obj = LoginForm(request.POST)
        if form_obj.is_valid():
            #如果檢驗全部通過
            print(form_obj.clean_data) #這裡全部都沒問題
            return HttpResponse("你好,歡迎回來!
") else: #print(form_obj.clean_data) #print(form_obj.errors) return render(request, "login.html", {"form_obj": form_obj,) form_obj = LoginForm() return render(request, "login.html", {"form_obj": form_obj})
個人程式碼例項
def register(request):
    
if request.is_ajax(): print('request.POST',request.POST) form = UserForm(request.POST) # 把值傳入form表單中 print('form', form) response = {"user": None, "msg": None} if form.is_valid(): response["user"] = form.cleaned_data.get("user") # 生成一條使用者紀錄 user = form.cleaned_data.get("user") print("user", user) pwd = form.cleaned_data.get("pwd") email = form.cleaned_data.get("email") avatar_obj = request.FILES.get("avatar") extra = {} if avatar_obj: extra["avatar"] = avatar_obj UserInfo.objects.create_user(username=user, password=pwd, email=email, **extra) print('UserInfo',UserInfo) else: print('form.cleaned_data',form.cleaned_data) print('form.errors',form.errors) response["msg"] = form.errors print('response', response) return JsonResponse(response) form = UserForm() print('form = UserFrom', form) return render(request, "register.html", {"form": form})

個人檢視

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css">
    <script src="/static/js/jquery-3.2.1.min.js"></script>
    <style>
        #avatar_img {
            margin-left: 20px;
        }

        #avatar {
            display: none;
        }

        .error {
            color: red;
        }
    </style>

</head>
<body>
<h3>註冊頁面</h3>
<div class="container">
    <div class="row">
        <div class="col-md-6 col-lg-offset-3">

            <form id="form">
                {% csrf_token %}

                {% for field in form %}
                    <div class="form-group">
                        <label for="{{ field.auto_id }}">{{ field.label }}</label>
                        {{ field }} <span class="error pull-right"></span>
                    </div>
                {% endfor %}

                <div class="form-group">
                    <label for="avatar">
                        頭像
                        <img id="avatar_img" width="60" height="60" src="/static/blog/img/default.png" alt="">
                    </label>
                    <input type="file" id="avatar" name="avatar">
                </div>

                <input type="button" class="btn btn-default reg_btn" value="submit"><span class="error"></span>

            </form>

        </div>
    </div>
</div>


<script>
    // 頭像預覽
    $("#avatar").change(function () {

        // 獲取使用者選中的檔案物件
        var file_obj = $(this)[0].files[0];
        // 獲取檔案物件的路徑
        var reader = new FileReader();
        reader.readAsDataURL(file_obj);
        // 修改img的src屬性 ,src=檔案物件的路徑
        reader.onload = function () {
            $("#avatar_img").attr("src", reader.result)
        };

    });

    // 基於Ajax提交資料

    $(".reg_btn").click(function () {
        //console.log($("#form").serializeArray());
        var formdata = new FormData();

        var request_data = $("#form").serializeArray();
        console.log(request_data);
        $.each(request_data, function (index, data) {
            formdata.append(data.name, data.value)
        });

        formdata.append("avatar", $("#avatar")[0].files[0]);
        console.log('formdata',formdata);
        $.ajax({
            url: "",
            type: "post",
            contentType: false,
            processData: false,
            data: formdata,
            success: function (data) {
                //console.log(data);

                if (data.user) {
                    // 註冊成功
                    location.href="/login/"
                }
                else { // 註冊失敗

                    //console.log(data.msg)
                    // 清空錯誤資訊
                    $("span.error").html("");
                    $(".form-group").removeClass("has-error");

                    // 展此次提交的錯誤資訊!
                    $.each(data.msg, function (field, error_list) {
                        console.log(field, error_list);
                        if (field=="__all__"){
                            $("#id_re_pwd").next().html(error_list[0]).parent().addClass("has-error");
                        }
                        $("#id_" + field).next().html(error_list[0]);
                        $("#id_" + field).parent().addClass("has-error");


                    })

                }
            }
        })

    })


</script>

</body>
</html>

鉤子講理(非自己)

#鉤子程式碼例項
def clean_user(self):
    val1 = self.cleaned_data.get("user")
    #從正確的欄位字典中取值
    #如果這個字串全部都是由陣列組成
    if not val1.isdigit():
        return val1
    else:
        # 注意這個報錯資訊已經確定了
        raise ValidationError("使用者名稱不能全部是數字組成")
        #在校驗的迴圈中except ValidationError as e:,捕捉的就是這個異常
        #所以能將錯誤資訊新增到_errors中



#程式碼分析部分
def is_valid(self):
    """
    Returns True if the form has no errors. Otherwise, False. If errors are
    being ignored, returns False.
    如果表單沒有錯誤,則返回true。否則為假。如果錯誤是被忽略,返回false。
    """
    return self.is_bound and not self.errors
    #is_bound預設有值
    #只要self.errors中有一個值,not True = false,返回的就是false


def errors(self):
    """
    Returns an ErrorDict for the data provided for the form
    返回一個ErrorDict在form表單存在的前提下
    """
    if self._errors is None:
        self.full_clean()
    return self._errors


def full_clean(self):
    """
    Cleans all of self.data and populates self._errors and self.cleaned_data.
    清除所有的self.data和本地的self._errors和selif.cleaned_data
    """
    self._errors = ErrorDict()
    if not self.is_bound:  # Stop further processing.停止進一步的處理
        return
    self.cleaned_data = {}

    """
    # If the form is permitted to be empty, and none of the form data has
    # changed from the initial data, short circuit any validation.
    #如果表單允許為空,和原始資料也是空的話,允許不進行任何驗證
    """

    if self.empty_permitted and not self.has_changed():
        return

    self._clean_fields()   #字面意思校驗欄位
    self._clean_form()
    self._post_clean()



def _clean_fields(self):
    #每個form元件例項化的過程中都會建立一個fields。fields實質上是一個字典。
    #儲存著類似{"user":"user規則","pwd":"pwd的規則物件"}
    for name, field in self.fields.items():
        #name是你呼叫的一個個規則欄位,field是呼叫欄位的規則
        #items是有順序的,因為他要校驗欄位的一致性
        """
        # value_from_datadict() gets the data from the data dictionaries.
        # Each widget type knows how to retrieve its own data, because some
        # widgets split data over several HTML fields.
        
        value_from_datadict()從資料字典中獲取資料。
        每個部件型別知道如何找回自己的資料,因為有些部件拆分資料在幾個HTML欄位。
        """
        #現在假設第一個欄位是user
        if field.disabled:
            value = self.get_initial_for_field(field, name)
        else:
            value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name))
        try:
            if isinstance(field, FileField):  #判斷是不是檔案
                #你是檔案的時候怎麼校驗
                initial = self.get_initial_for_field(field, name)
                value = field.clean(value, initial)
                #filed是一個物件,field.clean才是真正的規則校驗
            else:
                #你不是檔案的時候怎麼校驗
                #實際中也是走的這一部,value是你輸入的欄位值
                #如果沒有問題,那麼原樣返回
                value = field.clean(value)
                #如果一旦出現問題,那麼就會走except中的程式碼
            self.cleaned_data[name] = value

            if hasattr(self, 'clean_%s' % name):  #這裡找是否有clean_XX這個名字存在
                value = getattr(self, 'clean_%s' % name)()  #如果有執行這個函式
                self.cleaned_data[name] = value  #而在鉤子中必須報錯的返回值是確定的
                #如果上面有問題,就又把錯誤新增到了_error中
                #上面這三行程式碼是我們能新增鉤子的原因,而且規定了鉤子名的格式

                #如果這個值是正確的話,就會給這個字典新增一個鍵值對
                #剛才在full_clean中self.cleaned_data = {}已經初始化了。
                #{”pws“:123}
        except ValidationError as e:
            self.add_error(name, e)
            #如果出現錯誤,就會給_error這個字典新增一個鍵值對
            #至於add_error這個函式如何新增這個鍵值對的,我們先不管
            #鍵就是name,值就是錯誤資訊e
            #在full_clean中已經初始化self._errors = ErrorDict()
            #假設現在user有問題,那麼_error就是這樣{”user“:e}