django-源码分析-表单系统-1

目前的环境


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#django-admin.py startproject django_study
#cd django_study && python manager.py startapp study && cd -
#tree django_study
django_study
|-- django_study
| |-- __init__.py
| |-- __init__.pyc
| |-- settings.py
| |-- settings.pyc
| |-- urls.py
| `-- wsgi.py
|-- manage.py
`-- study
|-- forms.py
|-- forms.pyc
|-- __init__.py
|-- __init__.pyc
|-- models.py
|-- models.pyc
|-- tests.py
`-- views.py

一个简单的表单示例


1
2
3
4
from django import forms
class MyTestForm(forms.Form):
my_test_field = forms.CharField(max_length=20, min_length=0)

通过使用manager.py shell命令本地测试这个form类(IPython环境)。MyTestForm继承forms.Form,声明式的定义每个field的具体类型和属性等。

1
2
3
4
5
6
7
# python manager.py shell
In [1]: from study import forms as s_forms
In [2]: s_form = s_forms.MyTestForm(data={'my_test_field':'my_test_field_data'})
In [3]: s_form.as_p()
Out[3]: u'<p><label for="id_my_test_field">My test field:</label> <input id="id_my_test_field" maxlength="20" name="my_test_field" type="text" value="my_test_field_data" /></p>'

按照python的语法,my_test_field只是MyTestForm的类属性而不是实例属性,那么不同的实例如何区分。实际上MyTestForm只是一个声明,django通过元编程(超类)会生成真正的用于实例化的类,不妨称为真实类。具体情况结合源码分析。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# <1>
# django/forms/forms.py
# 为了使用python的元编程,Form就是一个简单的继承类,作为`MyTestForm`等自定义类的语法支持,所有的自定义类必须继承于Form
# Form的基类就是six.with_metaclass的返回值,实际上是一个临时的中介类,它的具体作用稍后分析
class Form(six.with_metaclass(DeclarativeFieldsMetaclass, BaseForm)):
"A collection of Fields, plus their associated data."
# This is a separate class from BaseForm in order to abstract the way
# self.fields is specified. This class (Form) is the one that does the
# fancy metaclass stuff purely for the semantic sugar -- it allows one
# to define a form using declarative syntax.
# BaseForm itself has no way of designating self.fields.
# <2>
# django/utils/six.py
# 临时的中介类由此函数生成,即 type.__new__(cls, name, (), d)
# 这个临时的中介类作为Form的基类,以便于在生成真实类时调用meta这个超类
# 到这里就有了两层的中介:第一层forms.Form作为用户自定义类的基类(类似于接口的作用)
# 第二层,临时的中介类作为forms.Form的基类,以便于调用形成最后真实类的超类meta
# MyTestClass -> Form -> 临时中介类 -> 超类meta -> 真实类
def with_metaclass(meta, *bases):
"""Create a base class with a metaclass."""
# This requires a bit of explanation: the basic idea is to make a
# dummy metaclass for one level of class instantiation that replaces
# itself with the actual metaclass. Because of internal type checks
# we also need to make sure that we downgrade the custom metaclass
# for one level to something closer to type (that's why __call__ and
# __init__ comes back from type etc.).
class metaclass(meta):
__call__ = type.__call__
__init__ = type.__init__
def __new__(cls, name, this_bases, d):
if this_bases is None:
return type.__new__(cls, name, (), d)
return meta(name, bases, d)
return metaclass('temporary_class', None, {})
# 上面的__new__方法会调用2次:第一次在six.with_metaclass被调用时调用,返回一个临时的中介类作为forms.Form的继承基类;
# 第二次在定义(程序运行编译时)`MyTestForm`时调用,返回meta(name, bases, d)生成最后的真实类,
# 这个类就是`MyTestForm`实例化时真正其作用的类
# 所以,表面看上`MyTestForm`是自定义类,实例由该类实例化;
# 单由于超类的存在,`MyTestForm`这个名字实际上指向的是由DeclarativeFieldsMetaclass动态生成的类
#(meta(name, bases, d)),它的基类是BaseForm
# <3>
# django/forms/forms.py
# DeclarativeFieldsMetaclass是生成最后真实类的超类, meta(name, bases, attrs)中的meta
# 将定义的各个Field实例,收集到真实类的base_fields中
# 这个超类的以小写class结尾,与其它超累的大写Class不一致:)
class DeclarativeFieldsMetaclass(MediaDefiningClass):
"""
Metaclass that collects Fields declared on the base classes.
"""
# 这个attrs包含了MyTestClass等用户自定义类中的类属性,例如my_test_field等
# 还包含了BaseForm的各个属性和方法
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
# <4>
# django/forms/forms.py
# BaseForm是`MyTestForm`等用户自定义类的基类
# 会作为 meta(name, bases, attrs)中的bases
@python_2_unicode_compatible
class BaseForm(object):
# This is the main implementation of all the Form logic. Note that this
# class is different than Form. See the comments by the Form class for more
# information. Any improvements to the form API should be made to *this*
# class, not to the Form class.
def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
initial=None, error_class=ErrorList, label_suffix=None,
empty_permitted=False):
# 如果实例化时传入了数据,则认为该Form是绑定的。
# 例如用户提交表单在view中有form = MyTestForm(**request.cleaned_data)
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
self.prefix = prefix
self.initial = initial or {}
self.error_class = error_class
# Translators: This is the default suffix added to form field labels
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._changed_data = None
# The base_fields class attribute is the *class-wide* definition of
# fields. Because a particular *instance* of the class might want to
# alter self.fields, we create self.fields here by copying base_fields.
# Instances should always modify self.fields; they should not modify
# self.base_fields.
# 将属于类的base_fields复制到属于实例的fields中,防止后续操作污染类属性base_fields
# base_fields是由
self.fields = copy.deepcopy(self.base_fields)

可以看到,由于超类的使用用户可以通过声明式的方式定义自己的表单类。但也会使得类不在以默认的方式实现,从而源码可读性变差。

with_metaclass的演变


关于six.with_metaclass有一些演变导致现在的可读性更差,现在试图分析下原因,源码如下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 1.7.1版本django中的six.with_metaclass
def with_metaclass(meta, *bases):
"""Create a base class with a metaclass."""
# This requires a bit of explanation: the basic idea is to make a
# dummy metaclass for one level of class instantiation that replaces
# itself with the actual metaclass. Because of internal type checks
# we also need to make sure that we downgrade the custom metaclass
# for one level to something closer to type (that's why __call__ and
# __init__ comes back from type etc.).
class metaclass(meta):
__call__ = type.__call__
__init__ = type.__init__
def __new__(cls, name, this_bases, d):
if this_bases is None:
return type.__new__(cls, name, (), d)
return meta(name, bases, d)
return metaclass('temporary_class', None, {})
# 1.5.11版本的six.with_metaclass
def with_metaclass(meta, *bases):
"""Create a base class with a metaclass."""
return meta("NewBase", bases, {})
# 这个meta中的__new__会调用两次:第一次是调用with_metaclass生成中介类,作为Form的基类;
# 第二次实现`MyTestForm`等用户自定义类生成真实类

旧版本中with_metaclass直接返回一个由超类动态生成的中介类作为forms.Form的基类,继承关系比较简单MyTestForm->Form-> 中介类 -> meta->真实类。而现在的形式更加复杂,在于生成中介类和真实类使用了不同的方法,中介类使用type.__new__(cls, name, (), d),真实类使用meta(name, bases, d),这样做的主要好处是使最后真实类的类型和继承关系更加清楚。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 1.7.1
In[13]: from django import forms
In[15]: type(forms.Form)
Out[15]: django.forms.forms.DeclarativeFieldsMetaclass
In[16]: forms.Form.mro()
Out[16]: [django.forms.forms.Form, # 不存在tempary_class的中介类
django.forms.forms.BaseForm,
object]
# 1.5.1
In [1]: from django import forms
In [2]: type(forms.Form)
Out[2]: django.forms.forms.DeclarativeFieldsMetaclass
In [3]: forms.Form.mro
Out[3]: <function mro>
In [4]: forms.Form.mro()
Out[4]:
[django.forms.forms.Form,
django.forms.forms.NewBase, # 1.7.1中的继承中少了这个中介类NewBase(tempary_class)
django.forms.forms.BaseForm,
object]

form表单


回过头看一下MyClassForm.as_p()中的各部分如何运作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
my_test_form.as_p -> self._html_output -> self.no_field_errors -> self.errors -> self.full_clean
-> self._clean_fields()
# 负责按照field的名字(例如:"my_test_field")获取数据,然后调用my_test_field.clean,
# 各Field.clean主要处理各类型Field本身数据检查工作,例如 max_length,min_length检查等,
# 然后调用my_test_form.clean_my_test_field执行用户在form类中自定义的处理函数,例如
class MyTestForm(forms.Form):
my_test_field = forms.CharField(max ...)
def clean_my_test_field(self):
value = self.cleaned_data['my_test_field']
return value.strip()
# 如果有错误,则将名字和异常存入self.add_error(field, error)
-> self._clean_form -> self.clean
# self.clean提供给用户的hook接口,自定义类中可以重载, 例如:
class MyTestForm(forms.Form):
my_test_field = forms.CharField(max...)
def clean(self):
value = self.cleaned_data.get('my_test_field', None)
if value:
self.cleaned_data['my_test_field'] = value.strip()
return self.cleaned_data
-> self._post_clean
# 另一个hook
# 回到self.no_field_erros
-> 然后迭代每个field,处理两类error,top_error属于表单或者隐藏field的; error属于每个field
-> 如果form定义了error_css_class或required_css_class就提取出来
-> 一次处理field的label,help_text,到这里会发现一些属于field但是需要在field外表现的属性都会被提取出来
-> 输出html格式文本

field类


每类field是一种表单域,每个field处理lable、单选框、复选框、错误提示、验证等。如示例中所示,field通过声明为form类的属性而定义。form会将所有的field实例收集到base_fields属性中并在实例化时赋值给实例的fields,再通过改写__getitem__方法实现field = form[fieldname]获取BoundField实例。在form.as_p中涉及到了一些field的操作,现在跟踪下。

1
2
3
4
5
6
7
8
# form._clean_fields中遍历field获取对应的值。
-> field.widget.value_from_datadict
# 获取对应的field.widget的值,一般情况下返回的就是data.get(fieldname)
-> field.clean -> self.to_python -> self.validate -> self.run_validators -> value
# 所有的data都是字符串格式,to_python会根据field的类型返回合适的python数据类型,例如int、list、None、''等
# self.validate会验证to_ptyhon返回的值,例如验证数字是否是NaN、Inf、-Inf,required的CharField是否为空等
# self.run_validators循环调用每个validator验证,validator根据每个field的参数而来,例如max_length,min_length等

简单的继承关系图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Field -> CharField -> RegexField
-> EmailField
-> FielField -> ImageField
-> URLField
-> IPAddressField
-> GenericIPAddressField
-> SlugField
-> IntegerField -> FloatField
-> DecimalField
-> BaseTemporalField -> DateField
-> TimeField
-> DateTimeField
-> BooleanField -> NULLBolleanField
-> ChoiceField -> TypeChoiceField
-> MultipleChoiceField -> TypedMultipleChoiceField
-> FilePathField
-> ComboField
-> MultiValueField -> SplitDateTimeField
-> FielPathField

BoundField


BoundField是一个存有数据的Field,主要是提供接口(类似代理模式)以及html格式输出。默认情况,BoundField输出对应类型Field的html。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# django.forms.BoundField
def __str__(self):
"""Renders this field as an HTML widget."""
if self.field.show_hidden_initial:
return self.as_widget() + self.as_hidden(only_initial=True)
return self.as_widget()
def as_widget(self, widget=None, attrs=None, only_initial=False):
"""
Renders the field by rendering the passed widget, adding any HTML
attributes passed as attrs. If no widget is specified, then the
field's default widget will be used.
"""
if not widget:
widget = self.field.widget
if self.field.localize:
widget.is_localized = True
attrs = attrs or {}
auto_id = self.auto_id
if auto_id and 'id' not in attrs and 'id' not in widget.attrs:
if not only_initial:
attrs['id'] = auto_id
else:
attrs['id'] = self.html_initial_id
if not only_initial:
name = self.html_name
else:
name = self.html_initial_name
# 通过field.render函数获取field的html
return force_text(widget.render(name, self.value(), attrs=attrs))

django选择通过实现一个BoundFiled代理类统一处理field的输出,而不是在基类中实现接口将实现分散在各Field类型中。

widget


widget是表单中的小部件处理表单域的输入以及验证。Widget类似Form是由超类实现,超类的作用是为Widget实现media属性,具体的作用稍后分析。不同widget对应input标签中的type属性。

1
2
3
4
5
6
7
8
9
10
11
12
# Input.render
# render会被BoundField调用,输出input标签的html语句
def render(self, name, value, attrs=None):
if value is None:
value = ''
# self.input_type由每个继承input的重载,实现input标签中的type="textarea"属性
final_attrs = self.build_attrs(attrs, type=self.input_type, name=name)
if value != '':
# Only add the 'value' attribute if a value is non-empty.
final_attrs['value'] = force_text(self._format_value(value))
# flatatt会将attrs转换为key=value形式,例如 class="css_class" name="for_id"
return format_html('<input{0} />', flatatt(final_attrs))

widget的继承关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Widget -> Input -> TextInput -> DateTimeBaseInput -> DateInput
-> DateTimeInput
-> TimeInput
-> NumberInput
-> EmailInput
-> URLInput
-> PasswordInput
-> HiddenInput -> HiddenInput
-> FileInput -> ClearableFileInput
-> Textarea
-> CheckboxInput
-> Select -> NullBooleanSelect
-> SelectMultiple
-> MultiWidget -> SplitDateTimeWidget -> SplitHiddenDateTimeWidget
SubWidget -> ChoiceInput -> RadioChoiceInput -> RadioInput ->
-> CheckboxChoiceInput