Django-Forms组件之钩子函数源码详解

一个人成熟的标志之一,就是明白 每天发生在自己身上99%的事情,对于别人而言没有任何意义。所以,冷暖自知。

第一步:需要一个form类

class MyForm(forms.Form):
    name = forms.CharField(max_length=6)
    password = forms.CharField(max_length=8, min_length=3)
    email = forms.EmailField(required=False)  # form表单required默认是True

第二步:实例化form对象

form_obj=MyForm({"name":"wpr","password":"123abc","email":"123@qq.com"})

第三步:查看数据校验是否合法

🌈一切从这里开始,先留个心

form_obj.is_valid()  ⚠️只有当所有的字段都校验通过才会返回True  🐷is_开头返回的都是布尔值

第四步:查看校验错误的信息

form_obj.errors  ⚠️这个里面放的是所有校验未通过的字段及错误提示,⚠️🐷🌸这里要注意的是钩子函数中的报错 不会被添加到errors里面,测试是看不到结果的

第五步:查看校验通过的数据

form_obj.cleaned_data  ⚠️所有符合校验规则的数据都会被放到该对象中

tips:


🌸form组件校验数据的规则:从上往下依次取值校验;
              校验通过的放到cleaned_data;
              校验失败的放到errors;
⚠️form中所有的字段默认都是必须传值的(required=True);
🐨 校验数据的时候可以多传数据,多传的数据不会做任何校验,不会影响form校验规则
🐷前端取消校验<form action="" method="post" novalidate>


开始对钩子函数源码详解

  首先is_valid( )是校验数据的部分,将数据放入is_valid( )开始校验,合格的放在哪里,不合格的放在哪里,因此如果不执行is_valid,是不能执行后面的cleaned_data或者errors,也就是说他是循环每个字段分别去校验,而cleaned_data和errors本质上就是两个字典,用来存放正确的数据和错误的数据。
🌸总结:学form组件最核心的方法是is_valid( ),最重要的源码也是is_valid(),钩子函数也在is_valid( )中。

  • 前期准备
from django import forms
class MyForm(forms.Form):
    name = forms.CharField(max_length=6)
    password = forms.CharField(max_length=8, min_length=3)
    email = forms.EmailField(required=False)  # form表单required默认是True

form_obj=MyForm({"name":"wpr","password":"123abc","email":"123@qq.com"})
form_obj.is_valid()


    # 钩子函数,当前面的校验都通过后才会执行
    # 局部钩子函数,单个字段的校验利用局部钩子函数
    def clean_name(self):
        name = self.cleaned_data.get('name')  # 首先从校验正确的数据中获取名字
        if '666' in name:  # 对名字进行逻辑判断
            self.add_error('name', '光喊666不行')  # 对应name字段名进行提醒
        return name  
    # 全局钩子函数,多个字段校验利用全局钩子函数
    def clean(self):
        password = self.cleaned_data.get('password')  # 从校验通过的字典中取得密码
        confirm_password = self.cleaned_data.get('confirm_password')  # 从校验通过的字典中取得确认密码
        if not password == confirm_password:  # 得到数据后进行逻辑判断
            self.add_error('confirm_password', '两次密码不一致')  # 判断后在确认密码后面进行提醒
        return self.cleaned_data
  • 进入is_valid( )查看
def is_valid(self):
    """
    Returns True if the form has no errors. Otherwise, False. If errors are
    being ignored, returns False.
    """
    return self.is_bound and not self.errors

详解:首先铺陈一个基础,True and True返回的是True,True and False返回的是False。这里and连接两个返回,前面的self.is_bound返回的一定是True,那么is_valid最后返回True还是False取决于errors到底是空字典还是有键值的,而当errors为空字典,说明没有任何错误,那么not 空就是True,如果errors里面有错误的键值,那么就返回False。

  • 那么接下来就主要看self.errors里面返回的是什么
# errors默认为None
self._errors = None  # Stores the errors after clean() has been called.

@property
def errors(self):
    "Returns an ErrorDict for the data provided for the form"
    if self._errors is None:
        self.full_clean()  # 因为默认为None,所以继续从这进入
    return self._errors
  • 进入full_clean( )查看
def full_clean(self):
    """
    Cleans all of self.data and populates self._errors and
    self.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()

详解:拿到两个初始变量,从逻辑上讲,接下来就是循环当前form类中的所有字段,依次判断输入进来的值和字段规则是否符合,符合就放入cleaned_data字典中,不符合就放入errors字典中。
🐷 tips:看源码时要知道自己该看什么,不要什么都看,只看我们当前逻辑关心的地方

  • ⭕️高能!!重要!!那么接下来我们就要看clean_field( ) 校验字段里面是什么
    ps:clean是校验的意思
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)

详解:
1、self.fields在类实例化时完成赋值,self.fields={"name":name字段对象,"password":password字段对象,"email":email字段对象},所以name对应的是字段字符串,field对应的是字段对象(也是规则对象),[比如这里就是name:"name"  field:name或者name:"password" field:password]。

2、往下看到value,这个value指的是传进来的字典的值(比如这里指字典中name的值wpr)。

3、接着是if isinstance(field,FileField),指的是字段对象是否为文件类型,在这里三个属性分别是CharField,CharField,EmailField,没有涉及到文件类型,所以走value = field.clean(value)

4、那就来分析value = field.clean(value)指的是用字段对象来校验这个value值,然后将它重新赋值给value,校验通过后加到cleaned_data字典中,name是这个字段字符串,value是这个通过的值,但是如果这里clean校验不通过,就会抛出一个validdation的错误,由于clean是用c语言封装起来的,所以不去深究,只要知道不通过会报错即可。

5、下一句if hasattr(self, 'clean_%s' % name): ⚠️是当上面第一层校验通过后,再走第二层钩子函数的校验,判断当前类下是否有一个叫 'clean_%s' % name名字的方法,如果有就将这个方法取出加个括号来调用这个方法,这时调用第二层钩子方法,得到一个返回值(⚠️🌸 敲黑板!!注意这里就是为什么在钩子函数中也要返回的原因,但是如果不写也不会报错,这是因为他已经通过了第一层校验,cleaned_data中已经存了那个名字,所以有时不加也没事,但为了防止版本问题产生不必要的bug,还是写上返回值,严谨!!!)
🐷敲黑板:要第一层校验通过才走钩子函数,如果第一层都没通过,钩子是没用的!!!

6、无论第一次还是第二次校验不通过就会抛出异常except ValidationError as e:self.add_error(name, e),把键和错误信息放入errors中。

7、但是这时有个疑问,从逻辑上讲如果第一层通过了,cleaned_data已经存了正确的键值,那如果第二层不通过,cleaned_data就不应该有这个键值,那么关键就在这个add_error( )中。

8、那我们就进入add_error( )中去一看究竟:

注意找有用的信息!!
for field, error_list in error.items():
    if field not in self.errors:
        if field != NON_FIELD_ERRORS and field not in self.fields:
            raise ValueError(
                "'%s' has no field named '%s'." % (self.__class__.__name__, field))
        if field == NON_FIELD_ERRORS:
            self._errors[field] = self.error_class(error_class='nonfield')
        else:
            self._errors[field] = self.error_class()
    self._errors[field].extend(error_list)
    if field in self.cleaned_data:   ⚠️# 关键就在这一句,如果字段对象已经在cleaned_data中
        del self.cleaned_data[field]   # 那么就从cleaned_data中删除这个字段

9、那从整体看是通过try except来控制,如果正确放入cleaned_data,如果错误放入errors中。

10、最后只要errors字典里面有键值,就返回False。

🐷 ps:可以将字段对象理解为字段规则/规则对象;
   字典是是无序的(.items),但在最新版本中中将字典变成有序的了,有一个OrderedDict模块,这个字典保证我们的键值是有序的,在我们定义的时候谁是第一个键值,在我们以后用的时候他都是第一个,这就保证了我们校验的时候是有序的来,先校验第一个字段,再依次校验,如果是无序的,for循环的时候都不知道要校验哪一个;

def items(self):
    "D.items() -> a set-like object providing a view on D's items"
    return _OrderedDictItemsView(self)  # 变成有序的了
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,122评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,070评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,491评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,636评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,676评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,541评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,292评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,211评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,655评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,846评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,965评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,684评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,295评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,894评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,012评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,126评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,914评论 2 355