這篇文章將為大家詳細講解有關深入淺析Django模型驗證器,文章內容質量較高,因此小編分享給大家做個參考,希望大家閱讀完這篇文章后對相關知識有一定的了解。
前言
在Django的模型字段參數中,有一個參數叫做validators,這個參數是用來指定當前字段需要使用的驗證器,也就是對字段數據的合法性進行驗證,比如大小、類型等。
Django的驗證器可以分為模型相關的驗證器和表單相關的驗證器,它們基本類似,但在使用上有區別。
本文討論的是模型相關的驗證器。
一、自定義驗證器
一個驗證器其實就是一個可調用的對象(函數或類),接收一個初始輸入值作為參數,對這個值進行一系列邏輯判斷,如果不滿足某些規則或者條件,則表示驗證不通過,拋出一個ValidationError異常。如果滿足條件則通過驗證,不返回任何內容(也就是默認的return None),可以繼續下一步。
驗證器具有重要作用,可以被重用在別的字段上,是工具類型的邏輯封裝。
下面是一個驗證器的例子,它只允許偶數通過驗證:
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])
因為驗證器運行之前,(輸入的)數據會被轉換為 Python 對象,因此我們可以將同樣的驗證器用在 Django form 表單中(事實上Django為表單提供了另外一些驗證器):
from django import forms class MyForm(forms.Form): even_field = forms.IntegerField(validators=[validate_even])
你還可以通過Python的魔法方法__cal__()編寫更復雜的可配置的驗證器,比如Django內置的RegexValidator驗證器就是這么干的。
驗證器也可以是一個類,但這時候就比較復雜了,需要確保它可以被遷移框架序列化,確保編寫了deconstruction()和__eq__()方法。這種做法很難找到參考文獻和博文,要靠自己摸索或者研究DJango源碼。
二、工作機制
讓我們來測試一下上面寫的驗證器:
>>> from .models import MyModel >>> a = MyModel.objects.create(even_field=3) >>> a <MyModel: MyModel object (1)> >>> a.even_field 3
什么??。?!不是說只有偶數才能通過驗證嗎?這里我提供了數字3,可是為什么創建成功了??
我們接著在admin站點中注冊MyModel模型,然后在圖形化界面后臺中創建MyModel的實例,你會發現這個時候驗證器起作用了,奇數是無法通過表單驗證的!
為什么會這樣??
這就要從Django的源碼說起!
Django是這么設計的:
為什么這么設計?個人猜測,Django官方為了序列化、鏈式調用等功能的兼容性,沒有自動進行驗證操作。
這個設計在源碼中是怎么體現的?
表單的內容在其它章節中講解。
下面介紹Django模型的驗證步驟和四個方法:
模型驗證的步驟:
本質上,后面三個方法是具體實現,full_clean()是領頭羊,實際操作中,你完全可以具體使用其中一個或多個。用了full_clean()就等于后面三個都用。
full_clean()
簽名:Model.full_clean(exclude=None, validate_unique=True)
讓我們看下它的源代碼:
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)
可以看出,它依次調用了其它三個方法,如果最后的errors中有內容,則拋出ValidationError異常。
我們最好不要去修改full_clean()方法的源代碼,一般也不用重寫它,直接調用即可。
模型的save()方法不會自動調用full_clean()方法,你必須手動調用。
如果調用驗證器后,拋出ValidationError異常,Django會將所有的異常信息放置在e.message_dict字典中供使用。比如下面的例子:
# 在視圖中我們可以這么做 from django.core.exceptions import ValidationError try: article.full_clean() except ValidationError as e: # 在這里做一些異常處理操作 pass
在模型定義中我們可以如下重寫save()方法,實現自動驗證功能,不需要在視圖中反復調用了:
# 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方法是關鍵 try: self.full_clean() super().save(*args, **kwargs) except ValidationError as e: print('模型驗證沒通過: %s' % e.message_dict)
執行過程展示:
>>> from .models import MyModel >>> a = MyModel.objects.create(even_field=5) 模型驗證沒通過: {'even_field': ['5 is not an even number']}
這樣,我們就實現了自動的模型驗證。
小技巧:可以通過打印e來查看,Django怎么封裝的錯誤信息,給我們提供了哪些鍵值,比如上例中,我們可以使用e.message_dict['even_field']。
clean_fields()
簽名:Model.clean_fields(exclude=None)
參數同上,看下它的源代碼:
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)
我們最好也不要去修改和重寫它的源代碼。
這個方法本質上就是循環模型中的所有字段,找出其中定義了驗證器的那些,并執行它們。
我們前面自定義的偶數驗證器,其實就是在這里被調用的。
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
什么都沒有!實際上,這個方法是給你留了個鉤子,你需要重寫它,然后在里面編寫模型級別的驗證,比如修改模型的屬性,以及跨字段相關的驗證邏輯。
下面我們通過一個例子來展示它的用法:
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): # 不允許草稿文章具有發布日期字段 if self.status == '草稿' and self.pub_date is not None: raise ValidationError(_('草稿文章尚未發布,不應該有發布日期!')) # 如果已發布的文章還沒有設置發布日期,則將發布日期設置為當天 if self.status == '已發布' and self.pub_date is None: self.pub_date = datetime.date.today() # 更多Django技術文章請訪問https://www.liujiangblog.com
說明:
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])
注意:這里我們導入了NON_FIELD_ERRORS,在最后打印了e.message_dict[NON_FIELD_ERRORS],這是為什么呢?
因為,clean()中編寫的都是模型級別、跨字段的驗證方法,沒有具體和某個字段綁定,所以Django提供了一個NON_FIELD_ERRORS關鍵字,用來說明這不是某個字段引起的異常,而是非字段相關的錯誤。
如果你非要將錯誤定位到某個具體的字段,也不是不可以的,如下例子所示:
class Article(models.Model): ... def clean(self): if self.status == '草稿' and self.pub_date is not None: raise ValidationError({'pub_date': _('草稿文章尚未發布,不應該有發布日期!')}) ...
甚至,你可以如下方式,映射字段和錯誤信息:
raise ValidationError({ 'title': ValidationError(_('Missing title.'), code='required'), 'pub_date': ValidationError(_('Invalid date.'), code='invalid'), })
這些技巧,本質上就是給ValidationError異常類提供信息參數。
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參數,那么該參數包含的所有字段都不會進行唯一性驗證。
我們最好也不要去修改和重寫它的源代碼。
總結
Django中模型驗證器的使用套路:
三、內置驗證器
驗證器的作用很重要,需求也很廣泛,Django為此內置了一些驗證器,我們直接拿來使用即可:
RegexValidator
這是正則匹配驗證器。用于對輸入的值進行正則搜索,如果命中,則平安無事,如果沒命中則彈出 ValidationError 異常。
數字簽名:class RegexValidator(regex=None, message=None, code=None, inverse_match=None, flags=0)
參數說明:
EmailValidator
數字簽名:class EmailValidator(message=None, code=None, whitelist=None)
郵件格式驗證器。
參數說明:
URLValidator
數字簽名:class URLValidator(schemes=None, regex=None, message=None, code=None)
RegexValidator的子類,用于驗證url的格式是否正確。
schemes:指定URL/URI的協議模式,默認值為['http', 'https', 'ftp', 'ftps']
validate_email
EmailValidator的一個實例,未做任何自定義。
validate_slug
一個確保輸入值是字母、數字、下劃線和連字符組合的RegexValidator的實例。
validate_unicode_slug
上面的Unicode編碼版本
validate_ipv4_address
一個RegexValidator的實例,用于判斷輸入值是否為ipv4格式
validate_ipv6_address
上面的ipv6版本
validate_ipv46_address
同時支持ipv4和ipv6
validate_comma_separated_integer_list
判斷輸入是否是一個以逗號分隔的數字列表,一個RegexValidator的實例。
int_list_validator
數字簽名:int_list_validator(sep=', ', message=None, code='invalid', allow_negative=False)
判斷一個由數字組成的字符串是否以指定的sep分隔。allow_negative用于反轉判斷邏輯。
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)
數字驗證器。當發生下面情況時彈出異常:
FileExtensionValidator
簽名:class FileExtensionValidator(allowed_extensions, message, code)
文件擴展名不在合法性列表中。合法性列表通過參數allowed_extensions指定。
validate_image_file_extension
通過pillow庫確定一個圖片文件的擴展名是合法的
ProhibitNullCharactersValidator
簽名:class ProhibitNullCharactersValidator(message=None, code=None)
對輸入值進行 str(value) 操作,轉換成字符串,然后如果這個字符串中包含1個以上的空字符'\x00',則驗證失敗。
關于深入淺析Django模型驗證器就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。