django---forms模块源码解析(一)

用户表单是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"属性,然后在页面中进行元素的样式修改

ok以上就是对Form类、BaseForm类,已经Field类的源码分析
下面博客就来继续分析下,在代码中我们是如何进行一步步校验规则的,如何使用局部钩子,全局钩子
本人能力有限,如有错误,请留言指出,多谢~_~

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值