欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Django模型驗證器介紹與源碼分析

 更新時間:2020年09月08日 09:36:16   作者:大江東流  
這篇文章主要給大家介紹了關(guān)于Django模型驗證器與源碼分析的相關(guān)資料,文中通過圖文介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

前言

在Django的模型字段參數(shù)中,有一個參數(shù)叫做validators,這個參數(shù)是用來指定當(dāng)前字段需要使用的驗證器,也就是對字段數(shù)據(jù)的合法性進(jìn)行驗證,比如大小、類型等。

Django的驗證器可以分為模型相關(guān)的驗證器和表單相關(guān)的驗證器,它們基本類似,但在使用上有區(qū)別。

本文討論的是模型相關(guān)的驗證器。

一、自定義驗證器

一個驗證器其實就是一個可調(diào)用的對象(函數(shù)或類),接收一個初始輸入值作為參數(shù),對這個值進(jìn)行一系列邏輯判斷,如果不滿足某些規(guī)則或者條件,則表示驗證不通過,拋出一個ValidationError異常。如果滿足條件則通過驗證,不返回任何內(nèi)容(也就是默認(rèn)的return None),可以繼續(xù)下一步。

驗證器具有重要作用,可以被重用在別的字段上,是工具類型的邏輯封裝。

下面是一個驗證器的例子,它只允許偶數(shù)通過驗證:

from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _

def validate_even(value):
 if value % 2 != 0:
 raise ValidationError(
  _('%(value)s is not an even number'),
  params={'value': value},
 )

通過下面的方式,將偶數(shù)驗證器應(yīng)用在字段上:

from django.db import models

class MyModel(models.Model):
 even_field = models.IntegerField(validators=[validate_even])

因為驗證器運行之前,(輸入的)數(shù)據(jù)會被轉(zhuǎn)換為 Python 對象,因此我們可以將同樣的驗證器用在 Django form 表單中(事實上Django為表單提供了另外一些驗證器):

from django import forms

class MyForm(forms.Form):
 even_field = forms.IntegerField(validators=[validate_even])

你還可以通過Python的魔法方法__cal__()編寫更復(fù)雜的可配置的驗證器,比如Django內(nèi)置的RegexValidator驗證器就是這么干的。

驗證器也可以是一個類,但這時候就比較復(fù)雜了,需要確保它可以被遷移框架序列化,確保編寫了deconstruction()和__eq__()方法。這種做法很難找到參考文獻(xiàn)和博文,要靠自己摸索或者研究DJango源碼。

二、工作機制

讓我們來測試一下上面寫的驗證器:

>>> from .models import MyModel
>>> a = MyModel.objects.create(even_field=3)
>>> a
<MyModel: MyModel object (1)>
>>> a.even_field
3

什么??。?!不是說只有偶數(shù)才能通過驗證嗎?這里我提供了數(shù)字3,可是為什么創(chuàng)建成功了??

我們接著在admin站點中注冊MyModel模型,然后在圖形化界面后臺中創(chuàng)建MyModel的實例,你會發(fā)現(xiàn)這個時候驗證器起作用了,奇數(shù)是無法通過表單驗證的!

為什么會這樣??

這就要從Django的源碼說起!

Django是這么設(shè)計的:

  • 模型的驗證器不會在調(diào)用save()方法的時候自動執(zhí)行
  • 表單的驗證器會在調(diào)用save()方法的時候自動執(zhí)行

為什么這么設(shè)計?個人猜測,Django官方為了序列化、鏈?zhǔn)秸{(diào)用等功能的兼容性,沒有自動進(jìn)行驗證操作。

這個設(shè)計在源碼中是怎么體現(xiàn)的?

  • Django的模型相關(guān)源碼中,沒有is_valid()方法,也不會自動調(diào)用full_clean() 方法,所以Django不會自動進(jìn)行模型驗證。但是它依然提供了四個重要的驗證方法,也就是full_clean() 、clean_fields() 、clean() 和validate_unique(),一會細(xì)說
  • Django的表單系統(tǒng)forms的相關(guān)源碼中,表單在save之前會自動執(zhí)行一個is_valid()方法,這個方法里會調(diào)用驗證器。

表單的內(nèi)容在其它章節(jié)中講解。

下面介紹Django模型的驗證步驟和四個方法:

模型驗證的步驟:

  • 如果你手動調(diào)用了full_clean()方法,那么會依次自動調(diào)用下面的三個方法
  • clean_fields():驗證各個字段的合法性
  • clean():驗證模型級別的合法性
  • validate_unique():驗證字段的獨一無二性

本質(zhì)上,后面三個方法是具體實現(xiàn),full_clean()是領(lǐng)頭羊,實際操作中,你完全可以具體使用其中一個或多個。用了full_clean()就等于后面三個都用。

full_clean()

簽名:Model.full_clean(exclude=None, validate_unique=True)

  • exclude用于指定某些字段不進(jìn)行驗證,也就是所謂的例外字段
  • validate_unique用于指定是否調(diào)用validate_unique()方法

讓我們看下它的源代碼:

 def full_clean(self, exclude=None, validate_unique=True):

 errors = {}
 if exclude is None:
  exclude = []
 else:
  exclude = list(exclude)

 try:
  self.clean_fields(exclude=exclude) #1
 except ValidationError as e:
  errors = e.update_error_dict(errors)

 try:
  self.clean() #2
 except ValidationError as e:
  errors = e.update_error_dict(errors)

 if validate_unique:
  for name in errors:
  if name != NON_FIELD_ERRORS and name not in exclude:
   exclude.append(name)
  try:
  self.validate_unique(exclude=exclude) #3
  except ValidationError as e:
  errors = e.update_error_dict(errors)

 if errors:
  raise ValidationError(errors)

可以看出,它依次調(diào)用了其它三個方法,如果最后的errors中有內(nèi)容,則拋出ValidationError異常。

我們最好不要去修改full_clean()方法的源代碼,一般也不用重寫它,直接調(diào)用即可。

模型的save()方法不會自動調(diào)用full_clean()方法,你必須手動調(diào)用。

如果調(diào)用驗證器后,拋出ValidationError異常,Django會將所有的異常信息放置在e.message_dict字典中供使用。比如下面的例子:

# 在視圖中我們可以這么做
from django.core.exceptions import ValidationError
try:
 article.full_clean()
except ValidationError as e:
 # 在這里做一些異常處理操作
 pass

在模型定義中我們可以如下重寫save()方法,實現(xiàn)自動驗證功能,不需要在視圖中反復(fù)調(diào)用了:

# models.py
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _

def validate_even(value):
 if value % 2 != 0:
 raise ValidationError(
  _('%(value)s is not an even number'),
  params={'value': value},
 )
 
from django.db import models

class MyModel(models.Model):
 even_field = models.IntegerField(validators=[validate_even])

 def save(self, *args, **kwargs): # 重寫save方法是關(guān)鍵
 try:
  self.full_clean() 
  super().save(*args, **kwargs)
 except ValidationError as e:
  print('模型驗證沒通過: %s' % e.message_dict)

執(zhí)行過程展示:

>>> from .models import MyModel
>>> a = MyModel.objects.create(even_field=5)
模型驗證沒通過: {'even_field': ['5 is not an even number']}


這樣,我們就實現(xiàn)了自動的模型驗證。

小技巧:可以通過打印e來查看,Django怎么封裝的錯誤信息,給我們提供了哪些鍵值,比如上例中,我們可以使用e.message_dict['even_field']。

clean_fields()

簽名:Model.clean_fields(exclude=None)

參數(shù)同上,看下它的源代碼:

 def clean_fields(self, exclude=None):

  if exclude is None:
   exclude = []

  errors = {}
  for f in self._meta.fields:
   if f.name in exclude:
    continue

   raw_value = getattr(self, f.attname)
   if f.blank and raw_value in f.empty_values:
    continue
   try:
    setattr(self, f.attname, f.clean(raw_value, self)) #核心是這一句
   except ValidationError as e:
    errors[f.name] = e.error_list

  if errors:
   raise ValidationError(errors)

我們最好也不要去修改和重寫它的源代碼。

這個方法本質(zhì)上就是循環(huán)模型中的所有字段,找出其中定義了驗證器的那些,并執(zhí)行它們。

我們前面自定義的偶數(shù)驗證器,其實就是在這里被調(diào)用的。

clean()

這個方法很特別,我們看看它的源代碼:

 def clean(self):
  """
  Hook for doing any extra model-wide validation after clean() has been
  called on every field by self.clean_fields. Any ValidationError raised
  by this method will not be associated with a particular field; it will
  have a special-case association with the field defined by NON_FIELD_ERRORS.
  """
  pass

什么都沒有!實際上,這個方法是給你留了個鉤子,你需要重寫它,然后在里面編寫模型級別的驗證,比如修改模型的屬性,以及跨字段相關(guān)的驗證邏輯。

下面我們通過一個例子來展示它的用法:

import datetime
from django.core.exceptions import ValidationError
from django.db import models
from django.utils.translation import gettext_lazy as _

class Article(models.Model):
 content = models.TextField()
 status = models.CharField(max_length=32)
 pub_date = models.DateField(blank=True, null=True)

 def clean(self):
  # 不允許草稿文章具有發(fā)布日期字段
  if self.status == '草稿' and self.pub_date is not None:
   raise ValidationError(_('草稿文章尚未發(fā)布,不應(yīng)該有發(fā)布日期!'))
  # 如果已發(fā)布的文章還沒有設(shè)置發(fā)布日期,則將發(fā)布日期設(shè)置為當(dāng)天
  if self.status == '已發(fā)布' and self.pub_date is None:
   self.pub_date = datetime.date.today()

# 更多Django技術(shù)文章請訪問https://www.liujiangblog.com

說明:

  • gettext_lazy在這里無關(guān)緊要
  • 在Article模型中重寫了clean方法,它不需要接受其它參數(shù)
  • 第一個if判斷,不允許草稿文章具有發(fā)布日期字段。如果你提供了,對不起,拋出ValidationError異常
  • 第二個if判斷,如果已發(fā)布的文章還沒有設(shè)置發(fā)布日期,則將發(fā)布日期設(shè)置為當(dāng)天
  • 這是一個跨字段的,全局性的驗證方法,它不像我們一開始自定義的驗證器那樣,不是作為一個驗證器參數(shù)進(jìn)行提供,而是寫在clean方法中了,一定要注意區(qū)別。

clean()方法寫好了,我們就可以在Article模型中重寫save()方法了:

 def save(self, *args, **kwargs):

  from django.core.exceptions import NON_FIELD_ERRORS

  try:
   self.full_clean()
   super().save(*args, **kwargs)
  except ValidationError as e:
   print('驗證沒通過: %s' % e.message_dict[NON_FIELD_ERRORS])

注意:這里我們導(dǎo)入了NON_FIELD_ERRORS,在最后打印了e.message_dict[NON_FIELD_ERRORS],這是為什么呢?

因為,clean()中編寫的都是模型級別、跨字段的驗證方法,沒有具體和某個字段綁定,所以Django提供了一個NON_FIELD_ERRORS關(guān)鍵字,用來說明這不是某個字段引起的異常,而是非字段相關(guān)的錯誤。

如果你非要將錯誤定位到某個具體的字段,也不是不可以的,如下例子所示:

class Article(models.Model):
 ...
 def clean(self):
  if self.status == '草稿' and self.pub_date is not None:
   raise ValidationError({'pub_date': _('草稿文章尚未發(fā)布,不應(yīng)該有發(fā)布日期!')})
  ...

甚至,你可以如下方式,映射字段和錯誤信息:

raise ValidationError({
 'title': ValidationError(_('Missing title.'), code='required'),
 'pub_date': ValidationError(_('Invalid date.'), code='invalid'),
})

這些技巧,本質(zhì)上就是給ValidationError異常類提供信息參數(shù)。

validate_unique()

簽名:Model.validate_unique(exclude=None)

它的源代碼也很簡單:

 def validate_unique(self, exclude=None):

  unique_checks, date_checks = self._get_unique_checks(exclude=exclude)

  errors = self._perform_unique_checks(unique_checks)
  date_errors = self._perform_date_checks(date_checks)

  for k, v in date_errors.items():
   errors.setdefault(k, []).extend(v)

  if errors:
   raise ValidationError(errors)

這個方法類似clean_fields(),只不過它只用來驗證模型中的唯一性約束是否滿足,而不是字段的值是否滿足驗證需求。

如果你提供了exclude參數(shù),那么該參數(shù)包含的所有字段都不會進(jìn)行唯一性驗證。

我們最好也不要去修改和重寫它的源代碼。

總結(jié)

Django中模型驗證器的使用套路:

  • 編寫字段級別的驗證器,在字段中作為參數(shù)指定
  • 或者編寫clean()方法,實現(xiàn)模型級別、跨字段的驗證功能
  • 重寫save()方法,調(diào)用full_clean(),實現(xiàn)全自動的驗證
  • 或者在視圖中,通過模型實例調(diào)用full_clean()方法,實現(xiàn)手動驗證

三、內(nèi)置驗證器

驗證器的作用很重要,需求也很廣泛,Django為此內(nèi)置了一些驗證器,我們直接拿來使用即可:

RegexValidator

這是正則匹配驗證器。用于對輸入的值進(jìn)行正則搜索,如果命中,則平安無事,如果沒命中則彈出 ValidationError 異常。

數(shù)字簽名:class RegexValidator(regex=None, message=None, code=None, inverse_match=None, flags=0)

參數(shù)說明:

  • regex:用于匹配的正則表達(dá)式
  • message:自定義異常錯誤信息。默認(rèn)是"Enter a valid value"
  • code:自定義錯誤碼。默認(rèn)是"invalid"
  • inverse_match:將通過和不通過驗證的判斷邏輯反轉(zhuǎn)。也就是未命中則平安,命中則出錯。
  • flags:編譯正則表達(dá)式時使用的正則flags。默認(rèn)為0。

EmailValidator

數(shù)字簽名:class EmailValidator(message=None, code=None, whitelist=None)

郵件格式驗證器。

參數(shù)說明:

  • message: 自定義錯誤信息,默認(rèn)為 "Enter a valid email address"。
  • code: 自定義錯誤碼,默認(rèn)為"invalid"。
  • whitelist:郵件域名白名單,默認(rèn)為['localhost']。

URLValidator

數(shù)字簽名:class URLValidator(schemes=None, regex=None, message=None, code=None)

RegexValidator的子類,用于驗證url的格式是否正確。

schemes:指定URL/URI的協(xié)議模式,默認(rèn)值為['http', 'https', 'ftp', 'ftps']

validate_email

EmailValidator的一個實例,未做任何自定義。

validate_slug

一個確保輸入值是字母、數(shù)字、下劃線和連字符組合的RegexValidator的實例。

validate_unicode_slug

上面的Unicode編碼版本

validate_ipv4_address

一個RegexValidator的實例,用于判斷輸入值是否為ipv4格式

validate_ipv6_address

上面的ipv6版本

validate_ipv46_address

同時支持ipv4和ipv6

validate_comma_separated_integer_list

判斷輸入是否是一個以逗號分隔的數(shù)字列表,一個RegexValidator的實例。

int_list_validator

數(shù)字簽名:int_list_validator(sep=', ', message=None, code='invalid', allow_negative=False)

判斷一個由數(shù)字組成的字符串是否以指定的sep分隔。allow_negative用于反轉(zhuǎn)判斷邏輯。

MaxValueValidator

簽名:class MaxValueValidator(limit_value, message=None)

是否超過指定最大值

MinValueValidator

簽名:class MinValueValidator(limit_value, message=None)

是否小于指定的最小值

MaxLengthValidator

簽名:class MaxLengthValidator(limit_value, message=None)

輸入值的長度是否超過限定值

MinLengthValidator

輸入值的長度是否小于限定值

DecimalValidator

簽名:lass DecimalValidator(max_digits, decimal_places)

數(shù)字驗證器。當(dāng)發(fā)生下面情況時彈出異常:

  • 輸入值超過max_digits
  • 輸入值的位數(shù)超過decimal_places
  • 輸入值大于最大位數(shù)與小數(shù)位數(shù)之差。(待確認(rèn))

FileExtensionValidator

簽名:class FileExtensionValidator(allowed_extensions, message, code)

文件擴展名不在合法性列表中。合法性列表通過參數(shù)allowed_extensions指定。

validate_image_file_extension

通過pillow庫確定一個圖片文件的擴展名是合法的

ProhibitNullCharactersValidator

簽名:class ProhibitNullCharactersValidator(message=None, code=None)

對輸入值進(jìn)行 str(value) 操作,轉(zhuǎn)換成字符串,然后如果這個字符串中包含1個以上的空字符'\x00',則驗證失敗。

更多特性請參考官方文檔

更多技術(shù)文章請訪問: https://www.liujiangblog.com

更多視頻教程請訪問: https://www.liujiangblog.com/video/

總結(jié)

到此這篇關(guān)于Django模型驗證器介紹與源碼分析的文章就介紹到這了,更多相關(guān)Django模型驗證器與源碼內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Python中的變量和作用域詳解

    Python中的變量和作用域詳解

    這篇文章主要介紹了Python中的變量和作用域詳解的相關(guān)資料,非常不錯,具有參考借鑒價值,需要的朋友可以參考下
    2016-07-07
  • Django項目后臺不掛斷運行的方法

    Django項目后臺不掛斷運行的方法

    今天小編就為大家分享一篇Django項目后臺不掛斷運行的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2019-08-08
  • pandas基礎(chǔ)?Series與Dataframe與numpy對二進(jìn)制文件輸入輸出

    pandas基礎(chǔ)?Series與Dataframe與numpy對二進(jìn)制文件輸入輸出

    這篇文章主要介紹了pandas基礎(chǔ)Series與Dataframe與numpy對二進(jìn)制文件輸入輸出,series是一種一維的數(shù)組型對象,它包含了一個值序列和一個數(shù)據(jù)標(biāo)簽
    2022-07-07
  • Python爬蟲利器之PhantomJS詳解

    Python爬蟲利器之PhantomJS詳解

    這篇文章主要介紹了Python爬蟲利器之PhantomJS詳解,PhantomJS是一個基于WebKit的無頭瀏覽器,它沒有圖形界面,但是它可以像傳統(tǒng)的瀏覽器一樣訪問web頁面,并返回已呈現(xiàn)的內(nèi)容,PhantomJS是一種命令行工具,可以用它來測試和爬取Web頁面,需要的朋友可以參考下
    2023-09-09
  • Java多線程編程中ThreadLocal類的用法及深入

    Java多線程編程中ThreadLocal類的用法及深入

    這篇文章主要介紹了Java多線程編程中ThreadLocal類的用法及深入,嘗試了自己實現(xiàn)一個ThreadLocal類以及對相關(guān)的線程安全問題進(jìn)行討論,需要的朋友可以參考下
    2016-06-06
  • Django Admin 管理工具的實現(xiàn)

    Django Admin 管理工具的實現(xiàn)

    這篇文章主要介紹了Django Admin 管理工具的實現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-05-05
  • Python?代碼智能感知類型標(biāo)注與特殊注釋詳解

    Python?代碼智能感知類型標(biāo)注與特殊注釋詳解

    這篇文章主要為大家介紹了Python?代碼智能感知類型標(biāo)注與特殊注釋詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-09-09
  • pycharm遠(yuǎn)程連接docker容器的操作流程

    pycharm遠(yuǎn)程連接docker容器的操作流程

    這篇文章主要給大家介紹了pycharm遠(yuǎn)程連接docker容器的操作流程,文中通過代碼示例和圖文講解介紹的非常詳細(xì),具有一定的參考價值,需要的朋友可以參考下
    2023-08-08
  • Python雙向鏈表插入節(jié)點方式

    Python雙向鏈表插入節(jié)點方式

    這篇文章主要介紹了Python雙向鏈表插入節(jié)點方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-06-06
  • Pyramid將models.py文件的內(nèi)容分布到多個文件的方法

    Pyramid將models.py文件的內(nèi)容分布到多個文件的方法

    默認(rèn)的Pyramid代碼結(jié)構(gòu)中,就只有一個models.py文件,在實際項目中,如果需要對models進(jìn)行分類,放到不同文件下,應(yīng)該怎么辦
    2013-11-11

最新評論