用户表单是Web端的一项基本功能,大而全的Django框架中自然带有现成的基础form对象,Python的Django框架中forms表单类的使用方法详解

Form表单的功能

自动生成HTML表单元素
检查表单数据的合法性
如果验证错误,重新显示表单(数据不会重置)
数据类型转换(字符类型的数据转换成相应的Python类型)

Form相关的对象包括

Widget:用来渲染成HTML元素的工具,如:forms.Textarea对应HTML中的<textarea>标签
Field:Form对象中的一个字段,如:EmailField表示email字段,如果这个字段不是有效的email格式,就会产生错误。
Form:一系列Field对象的集合,负责验证和显示HTML元素
Form Media:用来渲染表单的CSS和JavaScript资源。

我先将forms模块下的结构目录,通过图片列据出来 
这里写图片描述

我们平时在使用forms时候,会通过如下的类继承forms.Form来使用

class RegistForm(forms.Form):
    pass

我们先从forms.Form入手,代码只有一行,肯定也是被继承的一个类

class Form(six.with_metaclass(DeclarativeFieldsMetaclass, BaseForm)):

Form类又继承了一个类six.with_metaclass(DeclarativeFieldsMetaclass, BaseForm) 
继承的这个类接受了2个参数,一个DeclarativeFieldsMetaclass元类,一个BaseForm类 
six.with_metaclass通过这2个参数构造了一个类

我们先看元类DeclarativeFieldsMetaclass,代码如下:


class DeclarativeFieldsMetaclass(MediaDefiningClass):
    """
    Metaclass that collects Fields declared on the base classes.
    """
    def __new__(mcs, name, bases, attrs):
        # Collect fields from current class.
        current_fields = []
        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)

        # Walk through the MRO.
        declared_fields = OrderedDict()
        for base in reversed(new_class.__mro__):
            # Collect fields from base class.
            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

 

我们大概分析下这个元类,为构造类做了哪些操作? 
首先通过for key, value in list(attrs.items())来遍历BaseForm,或者类似RegistForm(forms.Form)我们定义使用的类 
遍历类中的方法,属性,构造键值对,举例如下:

phone =forms.CharField()
def clean_user(self):
        val = self.cleaned_data.get("user")
        ret = models.User.objects.filter(name=val)
        if not ret:
            return val
        else:
            raise ValidationError("该用户已经注册")

 

构造成如下的数据格式:

phone <django.forms.fields.CharField object at 0x0660FBB0>
clean_user <function RegistForm.clean_user at 0x06862198>

 

然后调用new_class = super(DeclarativeFieldsMetaclass, mcs).__new__(mcs, name, bases, attrs)通过元类方法构造类,这里就是构造类的类, 
另外我们在看下元类DeclarativeFieldsMetaclass的继承类MediaDefiningClass,代码如下:

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)

        if 'media' not in attrs:
            new_class.media = media_property(new_class)

        return new_class

 

这个父类主要是判断if 'media' not in attrs:如果定制类没有media字段属性,就去通过方法media_property(new_class),获取Media对象,该对象的方法又渲染,引用js等,如下代码

 def render(self):
        return mark_safe('\n'.join(chain(*[getattr(self, 'render_' + name)() for name in MEDIA_TYPES])))

    def render_js(self):
        return [
            format_html(
                '<script type="text/javascript" src="{}"></script>',
                self.absolute_path(path)
            ) for path in self._js
        ]

    def render_css(self):
        # To keep rendering order consistent, we can't just iterate over items().
        # We need to sort the keys, and iterate over the sorted list.
        media = sorted(self._css.keys())
        return chain(*[[
            format_html(
                '<link href="{}" type="text/css" media="{}" rel="stylesheet" />',
                self.absolute_path(path), medium
            ) for path in self._css[medium]
        ] for medium in media])

 

ok到这里,类class Form(six.with_metaclass(DeclarativeFieldsMetaclass, BaseForm)):中的six.with_metaclass方法参数DeclarativeFieldsMetaclass就介绍完毕,接下来先看下six.with_metaclass这个方法

def with_metaclass(meta, *bases):
    """Create a base class with a metaclass."""
    class metaclass(meta):
        def __new__(cls, name, this_bases, d):
            return meta(name, bases, d)
    return type.__new__(metaclass, 'temporary_class', (), {})

 

方法意思就是通过元类方法去构造,比如我们熟悉的Form、Widget、ModelForm、Model等 
接着我们看下BaseForm类是做什么处理的? 
BaseForm定义了一些方法,诸如数据是否已经绑定,能否验证通过is_valid,展现格式as_tableoras_uloras_p

简单分析完class Form(six.with_metaclass(DeclarativeFieldsMetaclass, BaseForm)): 
我们来看下Fields模块,我们使用时候,会写成如下的方式

 user =  forms.CharField(max_length=18, min_length=3,
                           error_messages={
                               'required': '不能为空',
                               'min_length': 'too short',
                               'max_length': "too long"
                           })

    pwd = forms.CharField(max_length=32, min_length=3,
                          error_messages={
                              'max_length': "too long",
                              'required': '不能为空',
                              'min_length': 'too short',
                          },
                          widget=widgets.PasswordInput(attrs={"class":"active"}))

    rePwd = forms.CharField(label='确认密码',
                                 max_length=32,
                                 error_messages={},
                                 widget=widgets.PasswordInput(attrs={"class":"active"}))


    email = forms.EmailField(
        error_messages={
            "invalid": '格式错误'
        }

 

上述的CharField、EmailField、都直接或者间接继承了Field类, 
我们看下__init__方法中,都做了什么初始化操作? 
这里就举出几个参数的意思: 
widget:一个小部件类或一个小部件类的实例,应用于显示此字段时。每个字段都有一个默认的小部件,如果不指定它,它将使用。在大多数情况下,默认是TextInput控件。 
error_messages:一些错误提示,当输入跟校验规则不匹配时候,用于提示的信息 
disabled:指定字段是否已禁用的布尔值,即是widget显示的形式但不可编辑。

接下来看一些Field对象的clean方法

    def clean(self, value):
        value = self.to_python(value)
        self.validate(value)
        self.run_validators(value)
        return value

 

这里面调用3个方法,我们依次看下

由于CharField等都或多或少重写Field类方法,这里我们利用CharField来了解 
CharField方法to_python中,如下是代码部分 
我们先看to_python方法


    def to_python(self, value):
        "Returns a Unicode object."
        if value not in self.empty_values:
            value = force_text(value)
            if self.strip:
                value = value.strip()
        if value in self.empty_values:
            return self.empty_value
        return value

 

to_python 方法的意思就是,通过force_text方法,将填写进表单的数据转为字符串类型的, 
然后继续看下clean方法中的self.validate(value)方法

   def validate(self, value):
        if value in self.empty_values and self.required:
            raise ValidationError(self.error_messages['required'], code='required')

 

这个方法从字面就能猜到,应该是进行校验输入的表单数据,如果输入为空,就raise 抛出异常

    def validate(self, value):
        if value in self.empty_values and self.required:
            raise ValidationError(self.error_messages['required'], code='required')

 

继续看clean方法中的self.run_validators(value)方法

    def run_validators(self, value):
        if value in self.empty_values:
            return
        errors = []
        for v in self.validators:
            try:
                v(value)
            except ValidationError as e:
                if hasattr(e, 'code') and e.code in self.error_messages:
                    e.message = self.error_messages[e.code]
                errors.extend(e.error_list)
        if errors:
            raise ValidationError(errors)

 

run_validators方法就是开始遍历校验规则,对输入的表单数据进行校验,如果校验不通过,就errors.extend(e.error_list),然后抛出ValidationError异常

另外我们在看其他的2个方法has_changed

 def has_changed(self, initial, data):
        """
        Return True if data differs from initial.
        """
        # Always return False if the field is disabled since self.bound_data
        # always uses the initial value in this case.
        if self.disabled:
            return False
        try:
            data = self.to_python(data)
            if hasattr(self, '_coerce'):
                return self._coerce(data) != self._coerce(initial)
        except ValidationError:
            return True

        initial_value = initial if initial is not None else ''
        data_value = data if data is not None else ''
        return initial_value != data_value

 

has_changed() 方法用于决定字段的值是否从初始值发生了改变。返回True 或False。 
当你需要检查表单的数据是否从初始数据发生改变时,可以使用表单的has_change()方法

另外在构造字段属性时候,我们还可以对错误信息的表单进行样式设置 
例如下面的示例:

pwd = forms.CharField(max_length=32, min_length=3,
                          error_messages={
                              'max_length': "too long",
                              'required': '不能为空',
                              'min_length': 'too short',
                          },
                          widget=widgets.PasswordInput(attrs={"class":"active"}))

 

为该表单某元素,添加class="active"属性,然后在页面中进行元素的样式修改

以上就是对Form类、BaseForm类,Field类的源码分析 

posted on 2018-04-08 21:26  Py行僧  阅读(371)  评论(0编辑  收藏  举报