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()即可。

posted @ 2020-08-09 03:07  没有想象力  阅读(444)  评论(0编辑  收藏  举报