Django源码阅读之forms表单
在我们在写web后端的时候,我们都要对前端传入的数据进行验证,判断是否合法。在django框架中,我们就需要使用到forms这个东西了。不清楚forms的用法可以参考这篇博客django使用表单验证前端传入数据并储存
所以forms表单在我们开发web项目的时候用的也是很多的一个点,接下来我们就来阅读一下django中forms表单的源码。
首先我们知道在django中使用表单,首先得写一个类,然后继承django.forms.Form,然后在这个类里面编写我们需要验证的字段。例如下面这样。
from django import forms
class TestForm(forms.Form):
name = forms.CharField(max_length=20, min_length=10, error_messages={
'required':'name不能为空',
'max_length':'name不能少于10个字符',
'min_length':'name不能超过20个字符'
})
password = forms.CharField(max_length=16, min_length=6, error_messages={
'required':'密码不能为空',
'max_length':'密码不能超过16个字符',
'min_length':'密码不能少于6个字符'
})
email = forms.EmailField(error_messages={
'required':'邮箱不能为空',
'invalid':'请输入格式正确的邮箱'
})
当然,上面我只是随便写了三个需要验证的字段。我们还可以写更多,这里就不演示了。
在我们定义好了这个Form类的时候,相当一定义好了我们的验证器了,那么我们在需要使用的位置导入这个Form类。然后开始使用。
例如,在视图中.
from .forms import TestForm
def test(request):
form = TestForm(request.POST)
if form.is_valid():
...
else:
...
首先我们先实例化一个TestForm对象, 然后调用form.is_valid方法判断数据是否合法。在进行相应的操作。
所以我们阅读源码就要从初始化这个位置开始阅读,因为这里才开始使用我们写的form表单验证器。
首先我们找到TestForm的__init__(self)
函数, 因为TestForm是我们自己写的,我们并没有写__init__(self)
函数,但是我们是继承的django.forms.Form
这个类,所以我们找到这个类的__init__(self)
函数。找到这个类的时候我们发现他只是一个空壳类,什么都没有,但是它是继承django.forms.BaseForm
这个类的。最后,我们终于在这个类中中找到了__init__
函数了。那么我们就从这里开始阅读吗?答案是否定的,因为Form这个类有一个参数,metaclass=DeclarativeFieldsMetaclass, 这个就涉及到元类这一部分的知识了,大家不懂的可以参考一下廖雪峰大佬的讲解
这里我就直接说运行顺序了,首先会执行metaclass这个参数指定的类的__new__
方法, 然后这个方法会返回一个新的类。相当于metaclass这个参数就是用来动态控制类的创建的。
找到django.forms.DeclarativeFieldsMetaclass
的__new__
方法,开始阅读源码。
django.forms.DeclarativeFieldsMetaclass._new_()
from django.forms.fields import Field
from django.forms.widgets import MediaDefiningClass
from collections import OrderedDict
class DeclarativeFieldsMetaclass(MediaDefiningClass):
def __new__(mcs, name, bases, attrs):
# 首先来了解这几个参数的具体含义和值
# mcs: 因为我们是实例化的TestForm这个类, 所以mcs就是TestForm这个类
# name:TestForm的名字,也就是 'TestForm'
# bases: TestForm的所有父类,一个元祖对象
# attrs: 一个字典对象,包含着TestForm的所有类属性,类方法。比如我们定义的name,password,email这些
# 定义一个空列表
current_fields = []
# 依次遍历attrs这个字典的key和value
for key, value in list(attrs.items()):
# 如果对应的值是Field的实例化对象,也就是我们自己定义的需要表单验证的属性。
# 因为所有的字段都是Field的子类
if isinstance(value, Field):
# 将key和value组成一个二元祖,并且天骄到current_fields列表中
current_fields.append((key, value))
# 在attrs中删除这个key和对应的值,因为attrs里面的key和value最后都会变成新创建的类里面的属性和方法的。
# 我们不需要我们定义的这些字段变成这个类的属性或者方法,所以就从attrs中删除了这个字段。
attrs.pop(key)
# attrs中添加一个新值,key为declared_fields, value为OrderedDict(current_fields)
# OrderedDict:就是转化为一个有序字典,
# current_fields里面就是我们在TestForm中定义的所有字段
attrs['declared_fields'] = OrderedDict(current_fields)
# 调用DeclarativeFieldsMetaclass类的父类的__new__方法。传入对应的参数
new_class = super(DeclarativeFieldsMetaclass, mcs).__new__(mcs, name, bases, attrs)
# DeclarativeFieldsMetaclass.__new__代码如下
'''
class MediaDefiningClass(type):
def __new__(mcs, name, bases, attrs):
# 这些参数的意思和对应的值就不用 说了吧,就是上面传入进来的参数。
# 再次调用MediaDefiningClass的父类的__new__方法,就是动态创建一个类。
# 因为MediaDefiningClass这个类的父类是type,在python,type.__new__方法是可以动态创建一个类的。
# 也就是所有的class都可以看做是type的实例化对象。
# 这样我们就根据TestForm中的值,创建好了一个新的类
new_class = super(MediaDefiningClass, mcs).__new__(mcs, name, bases, attrs)
# 如果'media'这个key不在attrs中
if 'media' not in attrs:
# 绑定media这个属性为这个函数的返回值,因为这个函数和我们后面要阅读的源码没什么大的关系,
# 这里就不阅读了,大家感兴趣的可以自己看一下
new_class.media = media_property(new_class)
# 返回新创建的class
return new_class
'''
# 这样,我们就得到了一个新的类new_class
# 初始化一个有序字典
declared_fields = OrderedDict()
# new_class.__mro__存放着所有的父类,一个元祖对象。大家可以自己打印一下自己写的类的__mro__这个函数的返回结果
for base in reversed(new_class.__mro__):
# 如果当前遍历的父类包含declared_fields这个类属性,就更新上面初始化的declared_fields
# 这样我们自己写的TestForm也可以被别人继承,并且也可以使用我们定义的password,username,email这些表单验证字段。
if hasattr(base, 'declared_fields'):
declared_fields.update(base.declared_fields)
# 遍历当前父类的__dict__.items(), class.__dict__装着所有的类属性, 是一个字典对象
for attr, value in base.__dict__.items():
# 如果值为None, 并且attr(也就是base.__dict__的key)在上面的declared_fields中
# 从declared_fields删除这个键值对,就是为了过滤掉一些空字段
if value is None and attr in declared_fields:
declared_fields.pop(attr)
# 上面的for循环完了之后,declared_fields中就是我们在表单中定义的所有字段了
# 然后给新的类绑定base_fields和declared_fields这两个类属性,值都为declared_fields,
# 也就是我们定义的所有表单字段
new_class.base_fields = declared_fields
new_class.declared_fields = declared_fields
# 再返回这个新的类。
return new_class
读完上面的代码我么可以发现,django将我们自定义的所有表单字段全部从类属性中剔除。放在了类属性base_fields和declared_fields上。
接下来我们就可以开始阅读__init__函数了.
django.forms.BaseForm._init_
from django.utils.html import html_safe
from django.forms.utils import ErrorList
from .renderers import get_default_renderer
import copy
# 首先我们可以看到这个类被html_safe这个装饰器装饰了。
# 这个装饰器里面的代码就不阅读了,因为这个是和表单渲染模板相关的,我们只是阅读表单验证数据这一部风的代码。
# 这里就直接告诉大家这个装饰器干了什么事吧,就是检查被装饰的类有没有__html__和__str__这两个魔术方法。
# 如果有__html__这个魔术方法,抛出异常
# 如果没有__str__这个魔术方法,抛出异常
# 然后类的__str__方法,生成新的__str__方法,和__html__方法。
@html_safe
class BaseForm:
# 初始化一些类属性
default_renderer = None
field_order = None
prefix = None
use_required_attribute = True
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):
# 我们在实例化TestForm的时候是不是传入的第一个参数为request.POST,所以data的值就为
# data : request.POST
# files : request.FILES 这个参数在我们对上传的文件进行表单验证的时候,就需要传入这个数据
# 只要data和files其中一个不为None, self.is_bound就为True
self.is_bound = data is not None or files is not None
# self.data为data或者None
self.data = {} if data is None else data
# self.files为files或者None
self.files = {} if files is None else files
self.auto_id = auto_id
if prefix is not None:
self.prefix = prefix
self.initial = initial or {}
# 这里说一下error_class这个参数,对应的值为ErrorList
# 这个是Django自己定义的一个list,继承list和collections.UserList。
# collections.UserList的用法大家可以百度一下,其实就是一个list
# 和普通的list一样的操作,只是zaiErrorList中django又定义了很多自己的方法
self.error_class = error_class
self.label_suffix = label_suffix if label_suffix is not None else _(':')
self.empty_permitted = empty_permitted
self._errors = None
# self.base_fields存放了我们所有的表单验证字段,这个是在__new__方法里面绑定的属性
# 深拷贝一份这些数据,绑定到self.fields属性中
self.fields = copy.deepcopy(self.base_fields)
self._bound_fields_cache = {}
# 下面的这些属性也不会影响我么阅读表单验证的代码,这里就不一一阅读了,大家可以自己自己阅读一下, 还是挺简单的
self.order_fields(self.field_order if field_order is None else field_order)
if use_required_attribute is not None:
self.use_required_attribute = use_required_attribute
if self.empty_permitted and self.use_required_attribute:
raise ValueError(
'The empty_permitted and use_required_attribute arguments may '
'not both be True.'
)
# 这个属性和表单的渲染模板相关,这里也不阅读了。
if renderer is None:
if self.default_renderer is None:
renderer = get_default_renderer()
else:
renderer = self.default_renderer
if isinstance(self.default_renderer, type):
renderer = renderer()
self.renderer = renderer
上面我们阅读完了django.forms.BaseForm.__init__
这个函数,相当于就初始化完了这个对象了,那么接下来我们应该开始从哪个位置开始阅读了呢?
我们想一下,我们初始化完form表单的时候,需要调用form.is_valid()这个函数,然后判断这个方法的返回值是否为True。如果为True,就代表表单验证成功,否则就是验证出错了。
所以我们接下来阅读form.is_valid()方法。这个方法也在django.forms.BaseForm
下
TestForm().is_valid()
def is_valid(self):
# self.is_bound我们在__init__函数中看到了,只要传入了request.POST或者request.FILES中
# 的任意一个,这个值就为True
# 接下来我们看一下self.errors这个属性
# 只要这个属性为False,那么这个函数就会返回True,也就是验证成功,
# 找到self.error,它其实是一个实例方法,只是被property装饰器装饰成了一个属性
return self.is_bound and not self.errors
TestForm.errors
@property
def errors(self):
# 返回一个ErrorDict对象
# 在init函数中,self._errors被初始化为了None
# 所以这个条件会被满足,执行self.full_clean方法
if self._errors is None:
self.full_clean()
# 返回self._errors的值,如果表单验证传入的数据没有错误,那么这个值就为空,
# self.is_valid函数就会返回True,表示数据验证成功
return self._errors
TestForm.full_clean()
from django.forms.utils import ErrorDict
def full_clean(self):
# 初始化一个ErrorDict对象,因为ErrorDict就是继承的dict类,所以self._errors就是一个字典
# 只是比普通的字典多了一些ErrorDict中定义的方法
self._errors = ErrorDict()
# 如果self.is_bound为False,那么表示没有数据,直接返回
if not self.is_bound:
return
# 定义一个字典self.cleaned_data
# 看到这里大家有没有觉得很熟悉了,我们在使用表单的时候,获取数据就是从self.clean_data中获取数据的
self.cleaned_data = {}
# self.empty_permitted在__init__函数中被初始化为了False,所以这个条件直接不成立
# 后面的判断条件和我们这里没有关系,我们就不去阅读了
if self.empty_permitted and not self.has_changed():
return
# 依次执行下面三个函数, 我们在下面依次阅读这三个函数的源码
self._clean_fields()
self._clean_form()
# 这个函数在BaseForm中也是一个空壳函数, 没有什么作用。
# 主要是用来用于完成表单清洗后执行额外的清洗。用于模型表单中的模型验证。
# 我们普通的表单验证他没有什么作用,这里就不管他 了
self._post_clean()
TestForm._clean_fields()
from django.forms.fields import FileField
from django.core.exceptions import ValidationError
def _clean_fields(self):
# self.fields就是我们form表单中我们自定义的字段
for name, field in self.fields.items():
# 首先我们得知道name和field对应的是什么值
# name:就是我们的字段名称,比如username,password,email这些
# field:就是字段对应的对象,如果forms.CharField(), forms.EmailField()等等
# field就是具体字段的实例化属性,所以我们首先首先找到对应的字段
# 我们在实例化字段的时候,可以传入一个disabled这个值,如果不传,默认就是为False
# 所以一般我们的字段都只会进入else里面,这里我们就直接看一下self.get_initial_for_field这个函数里面的代码吧
'''
def get_initial_for_field(self, field, field_name):
# self.initial在__init__函数中我们初始化为了一个空字典
# 调用字典的get方法,第一个参数为字典的key,第二个参数为如果字典中没有这个key,就会返回第二个参数的值
# 所以这里会得到第二个参数的值,因为self.initial为一个空字典。
# value的值就为field.initial
# field就是为每一个具体的字段对象
# 查看对应的Field类我们可以发现,initial的初始值为我们传入的initial这个关键字参数。
# 但是我们实例化field的时候并没有传入这个值,所以就为None。所以这个位置的value也是None
value = self.initial.get(field_name, field.initial)
# 如果value可以被调用,就调用value,得到value()的返回值
if callable(value):
value = value()
# 返回value
return value
'''
if field.disabled:
value = self.get_initial_for_field(field, name)
else:
# field.widget也是需要我们初始化字段对象的时候需要传入的参数,如果没有传入,那么默认的field.widget就为
# from django.forms.widgets import TextInput
# 所以我们得找到这个下面的value_from_datadict方法
'''
def value_from_datadict(self, data, files, name):
return data.get(name)
'''
# 我们可以看到这个函数就是返回data.get(name).
# data就是我们传入的self.data
# name为self.add_prefix(name)
# self.add_prefix函数的作用就是个name添加前缀,如果我们实例化对象的时候传入了前缀的话,也就是prefix这个变量
# 大家可以自己看一下这个函数里面的代码,也很简单,这里我就直接说这个函数干了什么事吧
# 如果self.prefix这个属性不为空的话,就返回`self.prefix-name`
# 但是我们在初始化的时候传入了前缀,所有这里就是返回name,也就是字段的名字
# self.data在__init__函数中将它的值赋值为request.POST
# 所以value的值相当于就是request.POST.get(name)
# 最后,value也就是从前端传入的值
value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name))
# 接下来就是开始验证数据了
try:
# 判断field是否是FileField的实例化对象,也就是是否为文件对象,这是对文件字段的验证
if isinstance(field, FileField):
# 这个方法里面干了什么我们上面已经说过了
initial = self.get_initial_for_field(field, name)
# 调用field.clean方法,value为前端传入的值, field.clean方法我们在下面讲解里面的代码
value = field.clean(value, initial)
# 如果不是文件对象相关字段
else:
# 调用field.clean方法,并且传入前端获取到的值作为参数
value = field.clean(value)
# 在self.clean_data中添加一个键值对,name -> field.clean函数的返回值
# 这也就是为什么我们获取数据都是从form.clean_data中获取的原因了
self.cleaned_data[name] = value
# 如果我们编写了clean_<name>这个方法。这就是为什么我么需要对一个字段单独做一些验证的时候
# 直接编写clean_<name>这个方法就可以了
if hasattr(self, 'clean_%s' % name):
# 调用getattr函数,获取这个方法并执行,得到返回结果
value = getattr(self, 'clean_%s' % name)()
# 更改键值对,将value设置为函数返回的结果
# 所以我们编写的clean_<name>必须有返回值,不然后面我们就获取不到相应的值了
self.cleaned_data[name] = value
# 捕获validationError这个异常,这也就是为什么我们在自定义验证的时候,需要抛出这个异常,就能够在后面得到错误信息的原因了
except ValidationError as e:
# 执行self.add_erorr方法,下面我们会说到这个函数干了什么
self.add_error(name, e)
TestForm.add_errors()
from django.core.exceptions import ValidationError
NON_FIELD_ERRORS = '__all__'
def add_error(self, field, error):
# filed : 就是字段的名称, 如username, password, email ,这些等等
# error : 一个ValidationError的实例化对象,就是抛出异常的时候实例化的具体对象, 也可以不是
# 如果error不是ValidationError的实例化对象
if not isinstance(error, ValidationError):
# 实例化ValidationError这个对象
error = ValidationError(error)
# 如果error拥有error_dict这个属性或方法
if hasattr(error, 'error_dict'):
# 如果field不为None
if field is not None:
# 抛出异常
raise TypeError(
"The argument `field` must be `None` when the `error` "
"argument contains errors for multiple fields."
)
# 重新赋值error为error.error_dict
else:
error = error.error_dict
else:
# 如果field不为None,那么就为field
# 否则就为NON_FIELD_ERRORS, 也就是 '__all__'这个字符窜
error = {field or NON_FIELD_ERRORS: error.error_list}
# 依次遍历error.items()
# 在上面的代码中,已经将error变成字典了
for field, error_list in error.items():
# field就是表单中的字段名
# 如果 field不在self.errors中
if field not in self.errors:
# self.fields就包含了所有的字段, 我们可以在 __init__函数中看到
# 如果field != 'NON_FIELD_ERRORS' 并且不在 self.fields中
if field != NON_FIELD_ERRORS and field not in self.fields:
# 抛出ValueError的异常
raise ValueError(
"'%s' has no field named '%s'." % (self.__class__.__name__, field))
# 下面的代码就很简单了,这里主要说一下self.error_class
# 在__init__函数中, 我们error_class初始化为django.forms.utils.ErrorList
# 大家可以看一下这个里面的代码,其实很简单,这里就不说了,就是一个多了几个方法的list
if field == NON_FIELD_ERRORS:
self._errors[field] = self.error_class(error_class='nonfield')
else:
self._errors[field] = self.error_class()
# self._errors[field] 和 error_list相加起来
self._errors[field].extend(error_list)
# 如果field在self.cleaned_data中,就删除
if field in self.cleaned_data:
del self.cleaned_data[field]
django.core.exceptions.ValidationError 类 _init_
class ValidationError(Exception):
def __init__(self, message, code=None, params=None):
# 调用父类的 __init__ 方法
super().__init__(message, code, params)
# 如果传入的message是ValidationError类的实例
if isinstance(message, ValidationError):
# 如果message有 error_dict这个属性或值
if hasattr(message, 'error_dict'):
# 重新赋值message = message.error_dict
message = message.error_dict
# 如果message没有message这个属性或方法
elif not hasattr(message, 'message'):
# 重新赋值message = message.error_dict
message = message.error_list
# 如果都不满足
else:
# 赋值这三个值为message对象的相关属性
message, code, params = message.message, message.code, message.params
# 如果传入的是dict字典对象
if isinstance(message, dict):
# 初始化一个self.error_dict 字典
self.error_dict = {}
# 遍历传入的字典
for field, messages in message.items():
# 如果messages不是ValidationError实例化对象,
if not isinstance(messages, ValidationError):
# 初始化为ValidationError的实例对象
messages = ValidationError(messages)
# 添加一个键值对 field -> messages.error_list
self.error_dict[field] = messages.error_list
# 如果为一个列表
elif isinstance(message, list):
# 初始化一个列表
self.error_list = []
# 依次遍历这个列表
for message in message:
# 如果message不为ValidationError实例对象
if not isinstance(message, ValidationError):
# 实例化这个对象
message = ValidationError(message)
# message有error_dict这个对象或属性
if hasattr(message, 'error_dict'):
# sum 对一个序列中的每一个值进行相加, 在和第二个参数进行相加
# 例如 sum( [[1], [2], [3]] ) => [1, 2, 3]
# 例如 sum( [[1], [2], [3]], [4]) => [4, 1, 2, 3]
# list.extend 将两个列表加起来
self.error_list.extend(sum(message.error_dict.values(), []))
else:
# 将message.error_list和self.error_list加起来
self.error_list.extend(message.error_list)
# 如果上面的都不是, 依次赋值就行了
else:
self.message = message
self.code = code
self.params = params
self.error_list = [self]
field.clean ( django.forms.fields.Field().clean )
因为field.clean在django.forms.fields.Field这个类中,也就是所有的字段类的父类,所以这里就只阅读这个类里面的clean方法了,其他一些字段类重写了clean方法,这里就不一一阅读了,大家对那个字段的clean方法感兴趣,可以自己去阅读一下。
def clean(self, value):
# value: 就是我们传入的从前端获取到的值
# self.to_python方法基本上大多数字段也重写这个方法,这里也不阅读每一个字段类的to_python方法了。
# 其实这个方法的作用就是将得到的value转化为python中的数据类型
value = self.to_python(value)
# 这个方法是验证字段是否为空,如果self.required 为True 并且value为空,就会抛出一个异常,异常码为required
# 这个方法也是大多数字段类都重写了的,这里也只是说了Field中的valid方法
self.validate(value)
# 依次调用validators,对value进行验证,如果验证出错,抛出ValidationError,并且给出相应的错误异常码,例如invalid,max_length这些
self.run_validators(value)
# 这里我们阅读一下run_validators里面的代码
'''
def run_validators(self, value):
# 如果value在self.empty_values这个属性里面, 直接返回
# self.empty_values这个属性的值为(None, '', [], (), {})
if value in self.empty_values:
return
# 定义一个空列表
errors = []
# 依次遍历self.validators这个列表
# self.validators里面装个很多验证器,有这个字段默认的验证器,也有我们自己在初始化字段的时候传入的validators
# 例如 email = forms.CharField(validators=[validators.EmailValidator(message='请输入正确的邮箱格式')], validators.RegexValidator(r'1[345678]\d{9}',message='请输入正确的手机号吗'), ...])
# self.validators大家可以在字段的额__init__函数中看到对这个属性的初始化,这里就不去查看了
# 所以这个循环就是依次使用我们指定的验证器和字段自带的验证器来验证数据
for v in self.validators:
try:
# 首先我们知道self.validators里面存储的是一个个验证器对象,那么调用一个实例化对象要想被像函数那样调用的话。
# 就必须重写 __call__ 方法
# 所以这个位置我们想知道这个里面干了什么事情,我们就得依次找到验证器的__call__方法进行查看代码
# 以为每个验证器的代码都不一样,这里也不去阅读了,大家对哪一个验证器里面的验证过程感兴趣,就可以自己去研究一下
v(value)
# 捕获ValidationError异常
except ValidationError as e:
# 如果这个异常有code这个属性并且e.code早self.error_messages中
# self.error_messages为一个字典,每一个字段中这个属性存的值也不一样,大家可以查看一下对应字段中这个属性的值
# 它的格式为 {'required':'<提示信息>', 'invalid':'<提示信息>', ...}
if hasattr(e, 'code') and e.code in self.error_messages:
# 赋值e.message属性为self.error_messages[e.code], 也就是相应的提示信息
e.message = self.error_messages[e.code]
# 扩展errors这个列表
errors.extend(e.error_list)
# 如果这个列表不为空, 抛出ValidationError异常
if errors:
raise ValidationError(errors)
'''
# 返回value
return value
TestForm._clean_form()
from django.core.exceptions import ValidationError
def _clean_form(self):
try:
# 调用self.clean方法, 这个方法在BaseForm中什么都没有做,直接返回self.cleaned_data
# 但是有时候我们会在我们自己定义的Form中重写这个方法,就会执行我们自己重写的clean方法了
# 所以我们有时候需要验证一些数据的时候,就会重写这个方法, cleaned_data为这个方法的返回值
cleaned_data = self.clean()
# 捕获ValidationError异常,然后添加错误信息
except ValidationError as e:
self.add_error(None, e)
# else里面的代码为try里面没有抛出异常才会执行
else:
# 如果返回值不是None,重新赋值self.cleaned_data为返回的值
if cleaned_data is not None:
self.cleaned_data = cleaned_data
经历过上面那么多代码的阅读,终于将得到的数据进行了判断,也就是执行完了form.is_valid()函数。并且能得到一些错误信息。
如果表单中的数据没有错误,我们就可以从form.cleaned_data中依次获取数据进行操作了。
上面就是表单对数据验证部分的源码阅读,内容还是挺多的,如果有什么写错了的地方,希望大家能指正出来。