Django学习之Forms组件部分源码学习及钩子使用

    前面学习了django form表单的一些基本功能使用,本次主要学习form钩子的使用以及form的源码。首先实现一个基本的from表单使用。

from django.shortcuts import render,HttpResponse
from app01 import models
from django import forms
from django.core.exceptions import ValidationError



class UserForm(forms.Form):
    username=forms.CharField(label='用户名',min_length=6)
    email=forms.EmailField(label='邮箱')


def fmindex(request):
    if request.method=="GET":
        obj=UserForm()
        return render(request, 'fm.html', {'obj': obj})
    elif request.method=="POST":
        obj=UserForm(request.POST)
        if obj.is_valid():
            print(obj.cleaned_data)  # 保存全部通过验证的表单数据
            return render(request, 'fm.html', {'obj': obj})
        else:
            print(obj.errors)
    return render(request, 'fm.html', {'obj': obj})
views.py
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>

</head>
<body>
    <form action="/fm/" method="POST">
        <!--as_p表示把form表单在前端页面渲染成p标签的形式展示,此外还有as_ul,as_table等-->
        {{ obj.as_p }}
        <input type="submit" value="提交" />
    </form>
</body>
</html>
fm.html

 

 

 form组件流程及部分源码

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

    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就是只有一个return,前面的self.is_bound返回的一定是True,那么is_valid最后返回True还是False取决于errors到底是空字典还是有键值的,而当errors为空字典,说明没有任何错误,那么not 空就是True,如果errors里面有错误的键值,那么就返回False。

    self.errors源码

@property
def errors(self):
    "Returns an ErrorDict for the data provided for the form"
    if self._errors is None:
        self.full_clean()  # 因为self._errors默认为None,所以继续从这进入
    return self._errors

    可以看到,self.errors中执行了full_clean()方法,继续查看full_clean()方法中执行了哪些操作

    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 = {}
        # 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()

       可以看到full_clean()中先定义了self._errors和self.cleaned_data两个空字典,分别存放用户输入验证成功和失败的数据。然后分别执行self._clean_fields()、self._clean_form()、self._post_clean()三个方法。接下来我们分别看一下这个3个方法中分别做了哪些操作。

 _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)

    可以看到_clean_fields方法中依次循环用户输入的信息并进行规则验证,其中name对应的是字段字符串,field对应的是字段对象(也是规则对象)。注意到其中有个hasattr方法,是当用户定义的form类中有clean_字段名的一个方法时(如:用户在form类中定义了clean_username方法,该方法中用户自定义自己想实现的功能),在程序循环到这个字段且通过前面第一层的规则校验后时会去执行clean_字段名(clean_username)这个方法。注意此时我们看到了form组件中的第一个钩子(clean_字段名),这个钩子一个局部钩子,从这里可以看到,该钩子需要一个返回值,后面会详细介绍钩子相关使用。_clean_fields方法中还会捕获异常。我们继续看_clean_form方法源码

_clean_form源码

    def _clean_form(self):
        try:
            cleaned_data = self.clean()
        except ValidationError as e:
            self.add_error(None, e)
        else:
            if cleaned_data is not None:
                self.cleaned_data = cleaned_data

    可以看到到_clean_form中调用了clean方法,并把返回值赋值给cleaned_data, _clean_form方法中也有异常捕获。clean方法是form组件中的第2个钩子并且是全局钩子。我接着看clean方法源码

clean()

    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

    我们可以看到clean方法中仅仅是把cleaned_data作为返回值返回,这里返回的cleaned_data是什么值用户通过obj.cleaned_data获取到的就是什么值。clean该方法中用户可以自定义实现自己的功能,自己重写该方法时有异常需要抛出,便于_clean_form中捕获。

    最后我们看下full_clean方法中执行的最后一个_post_clean方法主要做了什么操作

_post_clean源码

    def _post_clean(self):
        """
        An internal hook for performing additional cleaning after form cleaning
        is complete. Used for model validation in model forms.
        """
        pass

    我们可以看到_post_clean中什么也没做,可以让用户自己定义,_post_clean也是一个钩子,_post_clean不允许抛出异常。可以self.add_error("__all__", ValidationError('用户名或邮箱错误...'))添加错误信息。

     至此梳理一下整体的流程,

    1、首先用户调用is_valid()进行验证,

    2、执行full_clean方法(依次执行_clean_fields、_clean_form、_post_clean方法),

    3、_clean_fields方法中依次循环对用户定义的form类中每个字段进行正则验证,字段验证通过后,判断用户定义的form类中是否有clean_字段名方法,如果有执行改方法,

    4、然后执行_clean_form方法(该方法中调用了clean方法,用户可以在clean方法中定义自己想要实现功能)

    5、最后执行_post_clean方法(_post_clean中没任何操作,用户可以定义自己想要实现功能)

    通过前面对form源码的分析可以看出,用户要实现一些自定义的功能,可以通过钩子来实现,下面就学习一些几个钩子的使用

 局部钩子(clean_字段名)

        在自定义的from类中定义一个方法clean_字段名,注意:名字必须为clean_%s。其原理是,当字段正则校验成功后,会在用户定义的form类中查找是否有以clean_开头的函数名,如果有,就调用该函数,运行我们自定义的函数,如果满足条件就返回当前被校验字段的内容(源码中有value = getattr(self, 'clean_%s' % name)())。否则手动触发ValidationError错误,源码中会捕获并将值返回。

        

#自定义UserForm类,继承Form
class UserForm(forms.Form):
    username=forms.CharField(label='用户名',min_length=2)
    email=forms.EmailField(label='邮箱')
     
#可以对每个字段进行其他的校验
def clean_username(self): # self:当前form对象 value = self.cleaned_data['username']#通过cleaned_data获得对应字段的'干净数据' # 可以去数据库中判断用户是否存在或者其他操作 if value == 'root': # 正常,把username返回到clean_data,将name写入clean_data字典中 return value #返回的值即为cleaned_data中username对应的值,例如返回aaaa,可以看到username对应的值就是aaaa,{'username': 'root', 'email': '1129614034@qq.com'} else: # 失败,抛异常,将异常信息以 {'username':value} 写入errors字典中 raise ValidationError('数据库中不存在改用户') def fmindex(request): if request.method=="GET": obj=UserForm() return render(request, 'fm.html', {'obj': obj}) elif request.method=="POST": obj=UserForm(request.POST) if obj.is_valid():#校验,is_valid如果是true表示校验成功(满足UserForm里的条件),反之,校验失败 print(obj.cleaned_data) # 保存全部通过验证的表单数据 return render(request, 'fm.html', {'obj': obj}) else: print(obj.errors) return render(request, 'fm.html', {'obj': obj})

验证失败错误信息

<ul class="errorlist"><li>username<ul class="errorlist"><li>数据库中不存在该用户</li></ul></li></ul>

验证成功获取到正确信息

{'username': 'root', 'email': '11@qq.com'}

 全局钩子

     _post_clean和clean都是全局钩子,两者实现功能差不多。但是_post_clean不允许抛出异常,否则会导致整个程序异常,要通过self.add_error("__all__", ValidationError('用户名或邮箱错误...'))添加错误信息。clean可以抛出异常,程序可以捕获。

    # 重写clean方法
    def clean(self):
        # 程序能走到该函数,说明前面正则校验和每个字段的单独校验已经通过了,所以可以从cleaned_data中取出密码和确认密码
        v1 = self.cleaned_data['username']
        v2 = self.cleaned_data['email']
        #对所有校验的字段进行其他判断,如果满足条件,就将cleaned_data返回,由于正确信息都已经保存在cleaned_data中,可以直接写pass
        if v1 == "root" and v2 == "root@live.com":
            pass
        else:
            #如果不满足就手动触发ValidationError错误。
            raise ValidationError('用户名或邮箱错误!!!')
            
    #重写_post_clean方法
    # def _post_clean(self):
    #     v1 = self.cleaned_data['username']
    #     v2 = self.cleaned_data['email']
    #     if v1 == "root" and v2 == "root@live.com":
    #         pass
    #     else:
    #         self.add_error("__all__", ValidationError('用户名或邮箱错误...'))


def fmindex(request):
    if request.method=="GET":
        obj=UserForm()
        return render(request, 'fm.html', {'obj': obj})
    elif request.method=="POST":
        obj=UserForm(request.POST)
        if obj.is_valid():#校验,is_valid如果是true表示校验成功(满足UserForm里的条件),反之,校验失败
            print(obj.cleaned_data)  # 保存全部通过验证的表单数据
            return render(request, 'fm.html', {'obj': obj})
        else:
            print(obj.errors)
    return render(request, 'fm.html', {'obj': obj})

     补充:

     form组件在使用选择标签时,需要注意choices的选项可以从数据库中获取,但是由于是静态字段 ***获取的值无法实时更新***,那么有2中方法可以实现

from django.shortcuts import render,HttpResponse
from app01 import models
from django import forms
from django.forms import models as models_fields
from django.core.exceptions import ValidationError



#先写一个类,继承Form
class UserForm(forms.Form):
    username=forms.CharField(label='用户名',min_length=2)
    email=forms.EmailField(label='邮箱')
    user_type1 = forms.ChoiceField(choices=models.UserType.objects.values_list('id', 'name'))

    #利用ModelForm,使用此方法models中要增加
    """def __str__(self):
        return self.name
    """
    user_type2 = models_fields.ModelChoiceField(queryset=models.UserType.objects.all(),
                                                empty_label='请选择用户类型',
                                                to_field_name="id",
                                                limit_choices_to={'id': 1})

    def __init__(self, *args, **kwargs):
        super(UserForm, self).__init__(*args, **kwargs)
        self.fields['user_type1'].widget.choices = models.UserType.objects.all().values_list('id', 'name')

 

posted @ 2019-12-14 15:48  泉love水  阅读(243)  评论(0编辑  收藏  举报