Form
Django中form主要3个作用:
- Render,渲染: Http Get请求时,返回对应的HTML form ,当验证错误时,返回对应的form以及错误的tip
- Validate,验证: 包括在后台验证以及返回验证的结果
- Convert: 将提交的数据转换为相应的Python的数据
针对这几个作用,有几个相关的概念:
- Render : Widget以及Form assets. 前者对应html form的元素,后者对应选择这个form所需的css和javascript资源
- Validate: Field和Form. 前者定义了单个field的验证方法。后者对应了整个表单的验证。
相关的函数和属性
Validation
is_valid(): 如果返回True意味着提交的数据正确,并且已经转换成了对应的Python object, 其显示为:
@property
def errors(self):
"Returns an ErrorDict for the data provided for the form"
if self._errors is None:
self.full_clean()
return self._errors
def is_valid(self):
"""
Returns True if the form has no errors. Otherwise, False. If errors are
being ignored, returns False.
"""
return self.is_bound and not bool(self.errors)
可以看到其主要是返回errors这个函数的结果。error其实是调用了full_clean这个函数:
def full_clean(self):
"""
Cleans all of self.data and populates self._errors and
self.cleaned_data.
"""
self._errors = ErrorDict()
if not self.is_bound: # Stop further processing.
return
self.cleaned_data = {}
# If the form is permitted to be empty, and none of the form data has
# changed from the initial data, short circuit any validation.
if self.empty_permitted and not self.has_changed():
return
self._clean_fields()
self._clean_form()
self._post_clean()
这里可以看到validate的顺序: 首先是对每一个field执行clean(validate),然后对整个form执行。
1. 对每个field进行validate
def _clean_fields(self):
for name, field in self.fields.items():
# value_from_datadict() gets the data from the data dictionaries.
# Each widget type knows how to retrieve its own data, because some
# widgets split data over several HTML fields.
value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name))
try:
if isinstance(field, FileField):
initial = self.initial.get(name, field.initial)
value = field.clean(value, initial)
else:
value = field.clean(value)
self.cleaned_data[name] = value
if hasattr(self, 'clean_%s' % name):
value = getattr(self, 'clean_%s' % name)()
self.cleaned_data[name] = value
except ValidationError as e:
self._errors[name] = self.error_class(e.messages)
if name in self.cleaned_data:
del self.cleaned_data[name]
可以看到_clean_fields:
- 先是调用field自身的clean函数,而每个field在初始化的时候可以传入validator作为此field的validate函数。
- 调用getattr(self, 'clean_%s' % name)() : clean_fieldname. 这里注意没有传入参数? 此时clean_fieldname这个函数可以访问self.cleaned_data[],获取之前field的cleaned_data
Field的validator的初始化和调用如下:
class Field(object):
def __init__(self, required=True, widget=None, label=None, initial=None,
help_text='', error_messages=None, show_hidden_initial=False,
validators=[], localize=False):
#.....
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)
可以看到每一field的validator都会被传入此field的value。
2. 对整个Form进行validate
def _clean_form(self):
try:
self.cleaned_data = self.clean()
except ValidationError as e:
self._errors[NON_FIELD_ERRORS] = self.error_class(e.messages)
这里可以看到主要是调用clean这个函数
def clean(self):
"""
Hook for doing any extra form-wide cleaning after Field.clean() been
called on every field. Any ValidationError raised by this method will
not be associated with a particular field; it will have a special-case
association with the field named '__all__'.
"""
return self.cleaned_data
在form这个父类里面clean没有定义,直接返回当前的cleaned_data,在具体的form类时,如果我们要对整个form进行validate,需要定义clean函数并且返回cleaned_data.
综上所述,执行validate的顺序如下:
-
field: clean(self,data,initial=None)调用: 1.to_python() : 把数据转换成python object 2.validate() : handles field-specific validation that is not suitable for a validator 3.run_validator() : runs all of the field’s validators and aggregates all the errors into a single ValidationError
-
clean_
() method in a form subclass - The Form subclass’s clean() method
ModelForm
ModelForm其实就是根据对应的Model自动生成相应的Form.所以它不仅有:
- Render: render model field as HTML
- Validation: selector validator based off of model field definitions
而且能够save(insert,update)对应的model.
对于ModelForm,不要进行重复定义,比如:
from django.db import models
class MyModel(models.Model):
name = models.CharField(max_length=50, blank=True)
age = models.IntegerField(blank=True, null=True)
profession = models.CharField(max_length=100, blank=True)
bio = models.TextField(blank=True)
class MyModelForm(forms.ModelForm):
name = forms.CharField(max_length=100, required=True)
age = forms.IntegerField(required=True)
profession = forms.CharField(required=True)
bio = forms.TextField(required=True)
class Meta:
model = MyModel
这里MyModelForm违反了DRY(Dont't repeat yourself)的原则。相反,我们只需要修改我们想要修改的部分:
class MyModelForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(MyModelForm, self).__init__(*args, **kwargs)
# Making name required
self.fields['name'].required = True
self.fields['age'].required = True
self.fields['bio'].required = True
self.fields['profession'].required = True
class Meta:
model = MyModel