django form 组件源码解析
简介
form 组件主要用于
- 快速生成前端的input标签
- 对表单提交的数据进行验证。
Form对象
顾名思义,form表示一个表单,一个表单中可以有多个字段,每个字段都是必须是form中内置的字段类型,forms.fields模块中提供了我们可以使用的Field 字段类型,可以是字符串(CharField),整形(IntegerField),bool(BooleanField)等等类型,每一个字段指定一个数据类型即可。
class UserInfoModelFrom(forms.Form): username = forms.CharField(max_length=None, min_length=None, strip=True, empty_value='', *args, **kwargs) mobile_phone = forms.CharField() password = forms.CharField()
这样我们可以对上面三个字段的数据进行校验,例如:{ username:"tom", mobile_phone:"13844576433", password: 1234} 这样的数据可以通过该类进行验证,该类最基础的验证为该字段值不为空,如果可以为空,需要指定该字段的require=False参数。
ModelForm
在Form中我们需要自己手动定义每个字段,而如果我们要定义的类型与数据库中表的数据相对应,我们可以使用数据库中的字段来对应生成字段,只需要提供简单的配置信息即可。这些配置信息都在ModelForm的Meta类中指定。
以用户表功能为例,简单定义模型类:
class UserInfo(models.Model): username = models.CharField(verbose_name='用户名', max_length=32, db_index=True) # db_index=True 索引 mobile_phone = models.CharField(verbose_name='手机号', max_length=32) password = models.CharField(verbose_name='密码', max_length=32) def __str__(self): return self.username
根据模型类UserInfo,我们可以简单定义对应的ModelForm。
class UserInfoModelForm(forms.ModelForm): class Meta: model = models.UserInfo # 指定关联 UserInfo表来生成form,而form中的字段,为field = "__all__" ,即所有字段均生成,也可以指定部分字段 fields = "__all__" exclude = ["email"] # 排除某些字段,其余的生成
通过这种方式,同样可以生成使用Form的例子中的三个字段。这些字段的生成依赖于数据库字段的类型和我们提供的Meta参数。
在Meta类中可以定义特殊的类属性,例如上例中model关联UserInfo表,fields表示该form会对该表中所有字段建立表单。其他的字段包括
"fields" # 可选的字段名称列表。如果提供,只有指定的字段会生成 forms.Field "exclude" # 可选的字段名称列表。如果提供,命名字段将被排除,即使他们是列在fields中。 "widgets" # 一个字典, key为field名,值为 widget对象,这个每个字段可以映射一个widget,从而在前端表单生成时,更具widget的不同生成不同的输入框。每个字段由默认的输入框,不满意可以自己指定 "formfield_callback" # 一个字典, key为field名,值为一个可调用对象,返回值为字段form字段对象的实例,例如返回 forms.Charfield()。这样这个字段的类型 就是formfield_callback的返回值 "localized_fields" # 一个列表,字段的名称应该本地化。 "labels" # 一个字典,key为field名,值为一个文本标签名,一个字符串即可,对该字段的说明。 "help_texts" # 一个字典,key为field名,值为一个帮助文本。 "error_message" # 一个字典,key为field名,值为改field验证数据时未通过时的错误消息。 "field_classes" # 一个字典模型,key为field名字,值为改字段的类型,例如可直接指定为form.CharField。 {"username":forms.Charfield()}, 否则会使用默认值
所以,如果我们需要完整定义一个model可以定义为以下的方式,但是绝大多数情况下不会使用这么多参数。
class UserInfoModelForm(forms.ModelForm): class Meta: model = models.UserInfo fields = "__all__" exclude = ["email"] widgets = {"username": forms.Select(attrs={'class': "selectpicker", "data-live-search": "true"})}
fromfield_callback = lambda x: forms.CharField() # 必须为一个可调用对象。
localized = {}
labels = {"username":"用户名"}
help_texts = {"username":"这个字段储存的是用户名信息,本内容为帮助信息"}
error_message = {"username":"用户名错误,如果用户名字验证出错,将会返回用户名错误的信息"}
field_classes = {"username": forms.CharField()}
生成标签
生成一个ModelForm实例对象,调用对象上的对应方法即可生成input信息
def register(request): if request.method == "GET": form = UserInfoModelForm() return render(request, 'register.html', {'form': form})
前端页面代码
<form id="regForm" method="POST" novalidate> {% csrf_token %}
### 遍历form,展示每一个field,每一个field都会生成对应的htmln内容用于渲染 {% for field in form %}<div class="form-group"> <label for="{{ field.id_for_label }}">{{ field.label }}</label> ### field.id_for_label ==> id_字段名 {{ field }} <span class="error-msg"></span> </div> {% endfor %} <div class="row"> <input id="btnSubmit" type="button" class="btn btn-primary" value="注 册"/> </div> </form>
Form对象定义了__iter__魔术方法,可以直接被迭代,迭代的结果为每一个字段对象。
验证数据
上面的html代码会在浏览器中会生成一个Form表单,用户可以在表单中填入数据并提交到后台,后台使用modelform进行验证即可。
由于上面的表单使用POST方法提交,所以数据在request.POST中,所以使用UserinfoModelForm校验数即可
def register(request) form = UserinfoModelForm(data=request.POST) if form.is_valid(): # 验证通过,将用户信息写入表中 instance = form.save() # => form.instnce.save() 对应一个model对象的save,将会写入数据库 return HttpResponse("注册成功") else: return HttpResponse("注册失败")
初始化UserInfo并不会校验数据,只会生成一个对象,这个对象中保存了request.POST中的各项的值,以及这些值定义的校验方式,当调用form.is_vaild()方法,才会真正的对这些值进行验证。
forms组件源码解析
form的主要模块
from django.core.exceptions import ValidationError # 校验错误可以raise该错误(这部属于form组件内容) from django.forms.boundfield # 一个field字段与 widget 数据进行绑定 from django.forms.fields # 字段模块,数据库中不同的数据类型,对应一种字段类型 from django.forms.forms # form 类的元类信息,在form类定义时,对其进行初始化操作。 from django.forms.models # form 字段与 model数据库模型类字段之间的对应关系,以及实现数据获取提交相关的逻辑 from django.forms.widgets # 用于生成前端的组件信息
From继承关系
定义一个Model Form时候,我们通常会继承forms.Forms 或者 forms.ModelFrom。并在类中定义字段类型。这些字段类型,都会在类创建时,被一个metaclass元类进行处理, 并保存到类属性cls.base_fields中以供未来实例使用。
Form的继承关系为
BaseForm | \ | \ Form BaseModelForm \ \ ModelForm
元类的关系
MediaDefiningClass # 获取类model类中media 信息,如果没有指定一个默认的 media类 作为属性 | | DeclarativeFieldsMetaclass # Form的元类,获取Form类中定义的每个字段,将这些字段的字段类型,每个字段的验证规则等保存到字段中 | | ModelFormMetaclass # ModelForm的元类,modelForm 可以从 Meta 中model属性指定的模型类中 获取 field中指定的字段。
# 该元类实现 form 中字段 和 数据库模型类字段的映射。同时也支持自定义字段,自定义字段不和数据库字段对应。
创建ModelForm类对象
定义一个ModelfForm类,然后分析该类创建过程:
class UserInfoModelForm(forms.ModelForm): class Meta: model = models.UserInfo # 这里借助 UserInfo表来生成form,而form中的字段,为field = "__all__" ,即所有 fields = "__all__" exclude = ["email"]
当UserInfoModelForm类定义时,由于其继承ModelForm, 所以该类被ModelFormMetaclass元类创建,将执行其元类的new方法,下面根据 UserInfoModelForm 的创建过程,分析元类的执行过程。
ModelFormMetaclass
元类,即当一个类被创建时,也就是执行到class UserInfoModelForm 这个类定义时候,将会调用该类的元类,即ModelFromMetaclass,其定义如下:
class ModelFormMetaclass(DeclarativeFieldsMetaclass): def __new__(mcs, name, bases, attrs): # name 为 UserInfoModelForm 这个类名字 # bases 是 UserInfoModeForm 的所有的父类 # attr 中,保存了UserInfo中定义的所有类属性,包括 Meta类中对象 base_formfield_callback = None # 尝试从 UserInfoModelForm 的父类中获取 Meta属性,得到Meta中的formfield_callback, 否则为None, 当然之后会给一个默认值 for b in bases: if hasattr(b, 'Meta') and hasattr(b.Meta, 'formfield_callback'): base_formfield_callback = b.Meta.formfield_callback break formfield_callback = attrs.pop('formfield_callback', base_formfield_callback) # 先交给ModelFormMetaclass父类创键对象,下一节讲解。得到了 UserInfoModelForm 类对象 new_class = super(ModelFormMetaclass, mcs).__new__(mcs, name, bases, attrs) if bases == (BaseModelForm,): return new_class # 获取 Meta中指定的 model. field exclude等信息,作为 ModelFromOptions 对象的属性,接下来分别对这些属性进行处理,从这些属性中 # 找到模型类对象,找到模型类对象中与之同名的字段名。然后生成 form的字段 opts = new_class._meta = ModelFormOptions(getattr(new_class, 'Meta', None)) for opt in ['fields', 'exclude', 'localized_fields']: # 处理 Meta类中的 其中三个字段 value = getattr(opts, opt) if isinstance(value, six.string_types) and value != ALL_FIELDS: msg = ("%(model)s.Meta.%(opt)s cannot be a string. " "Did you mean to type: ('%(value)s',)?" % { 'model': new_class.__name__, 'opt': opt, 'value': value, }) raise TypeError(msg) if opts.model: # 模型类对象,然后从模型类对象中提取到字段,将这些字段映射为 form 上的字段, field_for_model即生成form字段,根据model中的字段 if opts.fields is None and opts.exclude is None: raise ImproperlyConfigured( "Creating a ModelForm without either the 'fields' attribute " "or the 'exclude' attribute is prohibited; form %s " "needs updating." % name ) if opts.fields == ALL_FIELDS: # 如果 fields = "__all__" opts.fields = None ######## 根据 model 中的字段,生成 form 中对应的字段内容 ########## # 该函数中,会默认使用 model类中的Field对象的 formfield()方法,来生成from中field字段 # 如果该字段是 ManyToMany 字段或者 forigenkey 则会找到关联的表,生成form 字段时候,添加choices和 query_set属性,关联到指定的数据上。 fields = fields_for_model( opts.model, opts.fields, opts.exclude, opts.widgets, formfield_callback, opts.localized_fields, opts.labels, opts.help_texts, opts.error_messages, opts.field_classes, # limit_choices_to will be applied during ModelForm.__init__(). apply_limit_choices_to=False, ) # 确保无论是模型类中的字段,还是自定义的字段,都应该在 Meta类中的 fields 中指定了 字段名,否则将会报错 none_model_fields = [k for k, v in six.iteritems(fields) if not v] missing_fields = (set(none_model_fields) - set(new_class.declared_fields.keys())) if missing_fields: message = 'Unknown field(s) (%s) specified for %s' message = message % (', '.join(missing_fields), opts.model.__name__) raise FieldError(message) # Override default model fields with any custom declared ones # (plus, include all the other declared fields). fields.update(new_class.declared_fields) else: fields = new_class.declared_fields # 类对象中定义的所有字段类对象信息,都会作为一个列表,报错到UserInfoModelForm的 base_fields 属性上。 new_class.base_fields = fields return new_class
上面fields_for_model是将数据字段映射为form字段的关键,其源码内容
def fields_for_model(model, fields=None, exclude=None, widgets=None, formfield_callback=None, localized_fields=None, labels=None, help_texts=None, error_messages=None, field_classes=None, apply_limit_choices_to=True): field_list = [] ignored = [] opts = model._meta # 导入model中fields模块中定义字段类型,并获取模型类的类型,遍历整个模型类中每一个指定的字段,每个字段生成一个form的field字段对象,添加到field_list中 from django.db.models.fields import Field as ModelField sortable_private_fields = [f for f in opts.private_fields if isinstance(f, ModelField)] for f in sorted(chain(opts.concrete_fields, sortable_private_fields, opts.many_to_many)): # 被指定的字段fields和没有被排除exclude的字段才会生成 form 中的字段对象 if fields is not None and f.name not in fields: continue if exclude and f.name in exclude: continue # 从meta类定义的各个字典中提取每个字段提取各自的信息,没有该字段的对应内容则为空 kwargs = {} if widgets and f.name in widgets: kwargs['widget'] = widgets[f.name] if localized_fields == ALL_FIELDS or (localized_fields and f.name in localized_fields): kwargs['localize'] = True if labels and f.name in labels: kwargs['label'] = labels[f.name] if help_texts and f.name in help_texts: kwargs['help_text'] = help_texts[f.name] if error_messages and f.name in error_messages: kwargs['error_messages'] = error_messages[f.name] if field_classes and f.name in field_classes: kwargs['form_class'] = field_classes[f.name] # f 为 model中的Field对象,每一个field对象可以调用自己的 formfield方法生成form的字段对象。 # 当然如果我们自己在meta的formfield_callback中指定了返回值为form的字段对象的可调用对象,则不会调用model,也就是 else中的的内容 # 通常来说,都会执行第一个 if 分支的内容。调用model 中字段的formField方法,生成 form 中的field字段对象实例, kwargs中的参数将作为form.field的参数 if formfield_callback is None: formfield = f.formfield(**kwargs) elif not callable(formfield_callback): raise TypeError('formfield_callback must be a function or callable') else: formfield = formfield_callback(f, **kwargs) if formfield: if apply_limit_choices_to: apply_limit_choices_to_to_formfield(formfield) field_list.append((f.name, formfield)) else: ignored.append(f.name) field_dict = OrderedDict(field_list) if fields: field_dict = OrderedDict( [(f, field_dict.get(f)) for f in fields if ((not exclude) or (exclude and f not in exclude)) and (f not in ignored)] ) return field_dict
不同的模型类的formfield方法会生成不同的 form 字段类型,用于做对应的数据校验和生成对应的前端标签。具体可以看 model模快中的各个Field及其子类的formField方法实现,功能在基类Field中实现,子类通过调用父类的方法传递不同的参数而实现多态。
def formfield(self, form_class=None, choices_form_class=None, **kwargs): """ 返回一个 django.forms.Field 实例 为 这个数据库字段. """ defaults = {'required': not self.blank, 'label': capfirst(self.verbose_name), 'help_text': self.help_text} if self.has_default(): if callable(self.default): defaults['initial'] = self.default defaults['show_hidden_initial'] = True else: defaults['initial'] = self.get_default() if self.choices: # 如果这个字段中有choice参数,在没有指定chioces_form_class的前提下,都会返回form.TypeChoiceField字段类型的实例 include_blank = (self.blank or not (self.has_default() or 'initial' in kwargs)) defaults['choices'] = self.get_choices(include_blank=include_blank) defaults['coerce'] = self.to_python if self.null: defaults['empty_value'] = None if choices_form_class is not None: form_class = choices_form_class else: form_class = forms.TypedChoiceField # 实例化一个字段对象的参数如下面所示。 for k in list(kwargs): if k not in ('coerce', 'empty_value', 'choices', 'required', 'widget', 'label', 'initial', 'help_text', 'error_messages', 'show_hidden_initial', 'disabled'): del kwargs[k] defaults.update(kwargs) if form_class is None: form_class = forms.CharField return form_class(**defaults)
这是基类 Field 中的实现,子类实现是通过覆盖该方法,指定对应的参数然后调用父类的方法实现。举个例子,在IntegerField的formfield方法中如此定义:
def formfield(self, **kwargs): defaults = {'form_class': forms.IntegerField} defaults.update(kwargs) return super(IntegerField, self).formfield(**defaults)
通过指定了form_class参数,调用父类的方法formfield方法,将会返回指定的forms.IntrgerField的实例对象。从而实现了不同的数据库字段类型生成不同的form 字段类型。
DeclarativeFieldsMetaclass
该类为 ModelFormMetaclass 的父类,同时也是forms.Form的元类,当我们继承forms.Form定义一个子类,并在该子类中定义诸多字段时,将会调用该类,该类的功能便是直接被定义在form类中的字段类型(通过从attr中获取),将Field类型的属性保存到 cls.base_fields中,未来供实例使用。
class DeclarativeFieldsMetaclass(MediaDefiningClass): """ 在 UserinfoModelform中可以定义一般的属性,即属性值不是 Field对象,如果是Field,将会被该类处理 该类的作用就是 收集 Form对象中定义的所有 field对象,并将其保存到 UserInfoModelForm的 declared_fields 属性中 """ def __new__(mcs, name, bases, attrs): # Collect fields from current class. current_fields = [] #### 遍历 每一个 UserInfoModelFrom中定义的所有类属性,如果类型是 Field,则收集到一个有序字典中 for key, value in list(attrs.items()): if isinstance(value, Field): current_fields.append((key, value)) attrs.pop(key) current_fields.sort(key=lambda x: x[1].creation_counter) attrs['declared_fields'] = OrderedDict(current_fields) #### 继续交给父类处理,得到类对象 new_class = super(DeclarativeFieldsMetaclass, mcs).__new__(mcs, name, bases, attrs) # UserInfoModelForm 父类中 declared_filed全部合并到一起,也就是说,UserInfoModelForm还可以被继承,其子类同样拥有 UserInfoModelForm中的字段 declared_fields = OrderedDict() for base in reversed(new_class.__mro__): # if hasattr(base, 'declared_fields'): declared_fields.update(base.declared_fields) # Field shadowing. for attr, value in base.__dict__.items(): if value is None and attr in declared_fields: declared_fields.pop(attr) new_class.base_fields = declared_fields new_class.declared_fields = declared_fields return new_class
MediaDefiningClass
该类处理会在cls.media属性上绑定一个media类,可以我们自己指定,否则使用默认的Media。
class MediaDefiningClass(type): """ Metaclass for classes that can have media definitions. """ def __new__(mcs, name, bases, attrs): new_class = super(MediaDefiningClass, mcs).__new__(mcs, name, bases, attrs) # type.__new__() # 如果UserinfoModelform中没有定义 media, 那么绑定一个默认的 if 'media' not in attrs: new_class.media = media_property(new_class) return new_class def media_property(cls): def _media(self): sup_cls = super(cls, self) 尝试从父类上获取 medias属性,没有,则使用,默认的Media类 try: base = sup_cls.media except AttributeError: base = Media() # 实例化默认的Media,可以自定义,media 类的作用主要与生成前端input标签的 html内容相关, # 每一个input都有自己的css和js等内容,Media负责管理这些 # Get the media definition for this class definition = getattr(cls, 'Media', None) if definition: extend = getattr(definition, 'extend', True) if extend: if extend is True: m = base else: m = Media() for medium in extend: m = m + base[medium] return m + Media(definition) else: return Media(definition) else: return base return property(_media)
该类除了作为form的元类,还作为Widget基类的元类,在Widget模块中,定义了大量的以Widget为基类的Input以及其子类,包括TextInput, EmailInput, PasswordInput子类,顾名思义,这些类用于生成不同input标签信息。而这些标签模板在django中也已经定义,这些类下定义了两个属性,input_type = 'password', template_name = 'django/forms/widgets/password.html', 指定input框的类型和模板路径。
小结
- UserInfoModelForm类生成时,会收集 modelform 中Meta类中配置,从模型类中找到fields中指定的字段,生成form字段,这个字段会以 {username:form.Charfiled, password:forms.CharField} 的形式保存到base_Fields属性中
- Meta中定义的参数,会在form.Field实例化时候,作为实例化的参数
- model.Field类会调用自己的方法去获取对应的forms.FIeld类,最后保存到base_Fields中。
实例化Form
经过简单的预处理后,我们的UserInfoModelForm被定义了。终于可以实例化该类,ModelForm同样父类,这些父类进行一些初始化操作。处理参数信息
实例化参数
实例化的常用参数如下:
class BaseModelForm(BaseForm): def __init__(self,
data=None, # 需要验证的数据,为一个字典,字典的key 需要和 字段名同名,这样会使用该forms.Field字段校验该值
files=None, # 同样为 key:value 字典,value为 前端上传的文件对象
auto_id='id_%s', # 常常作为 前端input的id
prefix=None, # 前缀名字 initial=None, # 初始化数据,一个字典,指定每个字段的初始值,如果有初始值,前端生成的input标签会有placeholder 属性,输入框中存在默认值
error_class=ErrorList, # 验证过程中的错误保存到该list中
label_suffix=None, # label 后缀名 empty_permitted=False, # 是否可以为空
instance=None, # 一个 query_set对象,会被转化为字典,与 initial字典值合并成一个大字典
use_required_attribute=None): # 省咯 pass
初始化过程
UserInfoModeForm会经过两个父类的init方法,分别是 BaseModelForm 和 BaseForm 两个方法的源码如下:
BaseModelForm
class BaseModelForm(BaseForm): def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None, initial=None, error_class=ErrorList, label_suffix=None, empty_permitted=False, instance=None, use_required_attribute=None): # 在创建 ModelForm 类时候,定义在 Meta类中的属性全部被封装为ModelOption对象并保存在 _meta属性中, # 这里获取了Meta中的定义的所有信息。 ''' opt = class Meta: model = Userininfo field = "__all__" ''' opts = self._meta if opts.model is None: raise ValueError('ModelForm has no model class specified.') if instance is None: # 如果没有该 model 类的实例,则自己实例化一个空对象。 # 如果自己指定这个instance,他应该为 model表的一个实例,也就是表中的一条数据。 self.instance = opts.model() object_data = {} else: self.instance = instance # model_to_dict的作用是从instance对象中提取指定的字段,返回字典{"字段名":字段值, ....} object_data = model_to_dict(instance, opts.fields, opts.exclude) # if initial was provided, it should override the values from instance if initial is not None: object_data.update(initial) # self._validate_unique 会在调用 BaseModelForm.clean() 后设置为True, self.clean方法的作用是。 他默认为False。 # 错误的覆盖了self.clean方法或者调用了父类的方法无法将该值置为False, self._validate_unique = False super(BaseModelForm, self).__init__( data, files, auto_id, prefix, object_data, error_class, label_suffix, empty_permitted, use_required_attribute=use_required_attribute, ) # 如果字段是 外键或者多对多的字段,那么该字段的值,只能是外键表中的指定数据,apply_limit_choices_to_to_formfield会找到该字段关联的数据, # 保存到字段的query 和 choices 属性中 for formfield in self.fields.values(): apply_limit_choices_to_to_formfield(formfield)
BaseForm
class BaseForm(object): default_renderer = None # 渲染器 field_order = None # 字段排序,默认为Meta 中filed 属性的顺序 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): self.is_bound = data is not None or files is not None self.data = data or {} self.files = files or {} self.auto_id = auto_id # 常常作为前端input 标签的唯一 id. 例如 username 字段的id为 id_usernname if prefix is not None: self.prefix = prefix self.initial = initial or {} # 每一个input标签中可以有初始值,否则默认为空,会按照字段名匹配,例如{"username":"tom"} self.error_class = error_class # 可以为字段的label添加后缀, self.label_suffix = label_suffix if label_suffix is not None else _(':') self.empty_permitted = empty_permitted self._errors = None # Stores the errors after clean() has been called. # self.base_fields 保存的是 ModelForm下类属性中定义每个Field 对象,在实例化时,每个实例深拷贝一份字段数据,以免多个ModelForm实例相互影响 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 # 如果没有定义渲染器,会使用form 中默认的渲染器。 渲染器的作用就是加载指定的html模板,并填入数据,例如插入由form 生成的input标签 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
初始化得到实例对象,该对象拥有了参数中指定的属性,源码中已经标注,其中重要的几个属性,self.fields 保存了所有的字段对象, self._errors 保存所有在校验过程中的错误信息。
得到初始化对象后,将进行校验过程。
数据校验
使用froms组件校验数据时,需要在实例化时使用data或者file参数传递要被校验的数据,两者至少选择其中一个,被检验的数据应该为一个字典,则字典中key与字段名字相同key将可以进行校验。校验并不是在实例化时完成,而是需要指定is_vaild()方法才会进行校验,校验中的错误信息会储存在 self._errors中,当然也可以使用self.errors访问。校验通过的数据存放self.clean_data中。详细的校验过程源码如下:
class BaseForm: def is_valid(self): """如果实例化form 传入了data参数且不为空,则is_bound为true,才会执行self.errors""" return self.is_bound and not self.errors @property def errors(self): """errors 是一个property属性,self._errors做一个简单的缓存使用,每个字段进行校验后的错误信息都会保存到self._errors 中 如果self._errors为None,则会执行self.full_clean方法对全部字段进行检测。 """ if self._errors is None: self.full_clean() return self._errors def full_clean(self): """ 1. 创建一个储存字段错误信息的self._errors字典,创建一个储存校验通过后该字段值的self.cleaned_data字典 2. 执行_clean_fields,_clean_form,_post_clean三个校验动作 _clean_fields中,每个字段单独校验各自的值是否可以通过 _clean_form会调用self.clean钩子函数 """ self._errors = ErrorDict() if not self.is_bound: # 没有data参数就不会继续校验 return self.cleaned_data = {} if self.empty_permitted and not self.has_changed(): return self._clean_fields() self._clean_form() self._post_clean() def _clean_fields(self): for name, field in self.fields.items(): if field.disabled: # disable表示字段不在前端被编辑 value = self.get_initial_for_field(field, name) # 获取字段的initial值 else: # 从data 或者 files中获取 该name 的值,field.widget会自动绑定上与之对应的方法处理两种情况 # widget时FileInput类型及其子类会从fiels中获取,其余会从data中获取数据。 value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name)) # 尝试捕捉ValidationError错误,默认在内部进行验证时,如果出现验证不通过的情况,可以直接raise该错误 # 所有的验证错误都会被捕捉然后执行 self.add_error()方法将错误添加到self._errors中 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 # 使用字段对象验证后的结果存放在该属性中 # 检查是否有钩子函数,名字 clean_ + fieldname,如果有则调用继续校验,结果更新到clean_data中。 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) def _clean_form(self): """一个对整个form进行检验的钩子函数,该函数的返回值会直接覆盖 self.clean_data""" cleaned_data = self.clean() except ValidationError as e: self.add_error(None, e) # 全局中出现的错误没有单独的字段名字,所以用 None作为key else: if cleaned_data is not None: self.cleaned_data = cleaned_data def _post_clean(self): """ 提供的一个钩子函数,子类BaseModelForm中重写了该方法,用于对外键(包括一对多和多对多关系)字段的值进行校验, """ pass
如果校验完全通过,is_vaild()的返回值为True,否则返回False。
数据校验通过后,Modleform实例可以直接调用save()方法将数据保存到数据库中,并同时返回这个instance(Model对象实例),保存时必须保证每个字段都能够合法的写入,否则将会保存失败,如果缺少某些字段,可以使用form.instance.field_name = value的形式指定,然后再调用save()即可。