用户表单是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_table
oras_ul
oras_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类的源码分析