zoukankan      html  css  js  c++  java
  • 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()即可。

  • 相关阅读:
    教你作一份高水准的简历
    python并发
    阻塞,非阻塞,同步,异步
    python三层架构
    paramiko与ssh
    python-进程
    生产者消费者模型
    python-线程
    python-socket
    python-mysql
  • 原文地址:https://www.cnblogs.com/k5210202/p/13461449.html
Copyright © 2011-2022 走看看