1. 程式人生 > 實用技巧 >django—Form元件校驗方法(is_valid)執行流程

django—Form元件校驗方法(is_valid)執行流程

  1、從is_valid方法入手

    def is_valid(self):
    """Return True if the form has no errors, or False otherwise."""
    return self.is_bound and not self.errors

    如果is_valid要返回True的話,那麼self.is_bound要為True並且self.errors要為Flase(字面意思上看errors為False的話,表示沒有錯誤資訊)

  2、is_bound

    以下程式碼出現在BaseForm類中,也就是Form的父類(Form繼承了父類的初始化方法)

    def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
     initial=None, error_class=ErrorList, label_suffix=None,
    empty_permitted=False, field_order=None, use_required_attribute=None, renderer=None):
    self.is_bound = data is not None or files is not None

    is_bound為True的條件是data不為None,或者files不為None

    也就是說在初始化一個Form物件時,只要傳入了data或者files,那麼is_bound的屬性就一定為True

    在呼叫is_valid方法校驗資料之前,肯定需要將POST提交的資料用來初始化一個Form物件,然後呼叫該方法對資料進行校驗

    所以,只要是用來校驗POST提交的資料時,is_bound肯定為True    

      form_obj = RegForm(request.POST)  # 包含使用者提交的資料的From物件
      if form_obj.is_valid(): # 對資料進行校驗
      return HttpResponse('ok')

  3、errors

    @property
    def errors(self):
    """Return an ErrorDict for the data provided for the form."""
    if self._errors is None:
    self.full_clean()
    return self._errors

    errors實際上是一個方法,只是使用property裝飾器裝飾成了屬性,可以通過物件.屬性名的方式呼叫

    當self._errors為None時會執行,full_clean方法。然後最終返回_errors。

    _errors同樣定義在BaseForm的初始化方法內,初始化即為None,但是最終返回卻將它作為結果返回。

    那麼,說明full_clean內部肯定對_errors做了修改

    直接看full_clean方法內部做了些什麼

  4、full_clean() 

    def full_clean(self):
    """
    Clean all of self.data and populate self._errors and self.cleaned_data.
    """
    self._errors = ErrorDict() # 錯誤字典(初始化為空字典)
    if not self.is_bound: # Stop further processing.
    return
    self.cleaned_data = {} # 建立了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()

    該方法首先賦值了一個錯誤字典給_errors(前面提到,初始化時為None),也就是說再經過一系列校驗後,如果最後該字典為空,那麼說明沒有錯誤,即校驗通過。

    接著往下看。

    如果is_bound為False直接終止方法執行,也就是該Form物件內沒有資料,不需要校驗。

    然後建立了一個字典cleaned_data,用於存放後續校驗無誤的資料。

    從這就可以知道,一個form物件只有在使用了is_valid()方法後,內部才會生成cleaned_data字典。使用is_valid()之前沒有該變數。

    然後接著看後續呼叫校驗方法的過程。

  5、_clean_fields() 

    def _clean_fields(self):
    for name, field in self.fields.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.
    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)
    else:
    value = field.clean(value)
    self.cleaned_data[name] = value
    if hasattr(self, 'clean_%s' % name):
    value = getattr(self, 'clean_%s' % name)()
    self.cleaned_data[name] = value
    except ValidationError as e:
    self.add_error(name, e) # 將捕獲到的異常新增到錯誤字典中

    該方法內部,直接對fields進行迭代取值處理。

    fields內部存放的就是表單提交的資料,以字典形式存放,name對應的就是Form類中定義的屬性名,field就是一個欄位物件。

    如果當前迭代到的欄位物件對應的input被禁用的話(disableed為True),那麼就以該input的初始值作為該欄位的值(initial設定的值。如果沒有設定,得到的則是空字串)。

    反之,該欄位的值就是使用者輸入的值。

    

    接著,判斷當前迭代到的欄位物件型別,如果是檔案型別,則使用檔案相關的校驗方式

    其他型別,都使用else程式碼塊裡的校驗方式

    def clean(self, value):
    """
    Validate the given value and return its "cleaned" value as an
    appropriate Python object. Raise ValidationError for any errors.
    """
    value = self.to_python(value)
    self.validate(value)
    self.run_validators(value)
    return value

    validate(value)方法,即校驗當前欄位的值是否為空,如果為空則丟擲required的提示資訊(欄位為必填項時會進行該校驗)

    run_validators方法,執行的是當前欄位預設的校驗器(如果是Email欄位,則它的預設校驗器就會校驗欄位的值是否符合正確的郵箱格式)以及自定義的校驗器(即設定欄位的引數validators時,傳入的自定義的校驗函式物件)

    如果校驗過程中出現異常,會層層丟擲,每一層都在捕獲異常,最終會將捕獲到的異常新增到錯誤欄位中。

    except ValidationError as e:
    self.add_error(name, e)

    然後最終返回原值。如果上述校驗無誤,則將該值新增到cleaned_data字典內。

    接著,判斷有沒有該欄位的區域性鉤子函式,對該欄位進行校驗。

    有的話,就呼叫這個區域性鉤子就要該欄位的值,校驗無誤就將該值重新賦值一遍給cleaned_data。出現異常就會丟擲,執行異常處理函式,將錯誤資訊新增到錯誤字典,並將該欄位的值從cleaned_data字典內刪除。

    從這也就能知道,區域性鉤子函式的返回值必須是對應欄位的值原樣返回,否則即使校驗通過,在賦值時也會出錯,導致最終得到的值不對。

   總結:

    clean方法主要功能就是執行欄位內建校驗方法、欄位內建校驗器以及自定義校驗器

    _clean_fields方法只要功能就是執行clean、以及區域性鉤子函式,對資料進行校驗。

  

  6、_clean_form()

    執行全域性的鉤子函式

    全域性鉤子函式 ,返回值必須是cleaned_data

    def clean(self):
    """
    Hook for doing any extra form-wide cleaning after Field.clean() has been
    called on every field. Any ValidationError raised by this method will
    not be associated with a particular field; it will have a special-case
    association with the field named '__all__'.
    """
    return self.cleaned_data

    因為,自定義的全域性鉤子函式,實際上就是重寫了上述方法,最終返回值必須和上述方法用於,即校驗無誤的資料字典

  總結:

    整個is_valid()的主要的流程為:

      1、執行full_clean方法

        先定義一個錯誤字典使用者後續判斷校驗是否正確,

        還有一個cleaned_data存放後續校驗無誤的資料

      2、執行_clean_fields方法

        該方法對資料進行除了全域性鉤子函式以外的校驗

        先執行欄位內建校驗方法、欄位內建校驗器以及自定義校驗器對資料進行校驗

        然後執行區域性鉤子函式對資料進行校驗

      3、執行_clean_form方法

        該方法對資料進行全域性鉤子函式的校驗

    所有校驗執行的先後順序:

      欄位內建的校驗方法——>欄位內建校驗器——>欄位自定義的校驗器——>區域性鉤子函式——>全域性鉤子函式