Django Form源码分析之Field验证逻辑
来源:互联网 发布:淘宝助手安卓版 编辑:程序博客网 时间:2024/05/21 07:04
引言
在上一篇对BaseForm的分析中,我只提及了在Form层次的输入验证,在Form.full_clean()主要调用的两个函数self._clean_field(), self._clean_form()。其中,self._clean_field方法代表了Field层次的输入验证。
在Django官方文档中,验证逻辑依次按照如下流程图:
其中self._clean_form对应着form.clean()。
同样,在引言中给出Field的初始化方便查阅
def __init__(self, required=True, widget=None, label=None, initial=None, help_text='', error_messages=None, show_hidden_initial=False, validators=[], localize=False, disabled=False, label_suffix=None): # required -- Boolean that specifies whether the field is required. # True by default. # widget -- A Widget class, or instance of a Widget class, that should # be used for this Field when displaying it. Each Field has a # default Widget that it'll use if you don't specify this. In # most cases, the default widget is TextInput. # label -- A verbose name for this field, for use in displaying this # field in a form. By default, Django will use a "pretty" # version of the form field name, if the Field is part of a # Form. # initial -- A value to use in this Field's initial display. This value # is *not* used as a fallback if data isn't given. # help_text -- An optional string to use as "help text" for this Field. # error_messages -- An optional dictionary to override the default # messages that the field will raise. # show_hidden_initial -- Boolean that specifies if it is needed to render a # hidden widget with initial value after widget. # validators -- List of additional validators to use # localize -- Boolean that specifies if the field should be localized. # disabled -- Boolean that specifies whether the field is disabled, that # is its widget is shown in the form but not editable. # label_suffix -- Suffix to be added to the label. Overrides # form's label_suffix. self.required, self.label, self.initial = required, label, initial self.show_hidden_initial = show_hidden_initial self.help_text = help_text self.disabled = disabled self.label_suffix = label_suffix widget = widget or self.widget if isinstance(widget, type): widget = widget() # Trigger the localization machinery if needed. self.localize = localize if self.localize: widget.is_localized = True # Let the widget know whether it should display as required. widget.is_required = self.required # Hook into self.widget_attrs() for any Field-specific HTML attributes. extra_attrs = self.widget_attrs(widget) if extra_attrs: widget.attrs.update(extra_attrs) self.widget = widget # Increase the creation counter, and save our local copy. self.creation_counter = Field.creation_counter Field.creation_counter += 1 messages = {} for c in reversed(self.__class__.__mro__): messages.update(getattr(c, 'default_error_messages', {})) messages.update(error_messages or {}) self.error_messages = messages self.validators = self.default_validators + validators super(Field, self).__init__()
Form._clean_field()
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. if field.disabled: value = self.initial.get(name, field.initial) else: 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.add_error(name, e)
在Form中self.fields是一个OrderDict结构,可以近似地看为一个key为field的声明名字(string),value为Field(class)的一个dict。
首先看一下Field.disabled这个属性,在初始化的时候默认为False,这个属性是什么意思呢?
# disabled -- Boolean that specifies whether the field is disabled, that # is its widget is shown in the form but not editable.
当清楚这个属性的作用后,再看判断的逻辑:
if field.disabled: value = self.initial.get(name, field.initial) else: value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name))
即当Filed不可用的时候,value会先从form.initial中获取,如果没有则会从field.initial中获取,这也是在对BaseForm的源码分析中,form.initial的优先级比field.initial高的缘故。(对initial参数的意义有疑惑的也应该去参考一下)
当Field可用的时候,将直接从self.data中获取相应的key。
def value_from_datadict(self, data, files, name): """ Given a dictionary of data and this widget's name, returns the value of this widget. Returns None if it's not provided. """ return data.get(name)
字段在检查完可用性和初始值之后开始调用Field.clean()。
Field.clean()
def clean(self, value): """ Validates the given value and returns its "cleaned" value as an appropriate Python object. Raises ValidationError for any errors. """ value = self.to_python(value) self.validate(value) self.run_validators(value) return value
在field.clean()方法中依次调用了field.to_python(), field.validate(), field.run_validators。同时,在这四个方法中raise的ValidationError都会被form._clean_field()捕捉。
Field.to_python()
def to_python(self, value): return value
to_python()主要用于返回正确的python类型,在Field中的实现非常简单,但是在不同的Field中可能会有不同的实现,如CharField:
def to_python(self, value): "Returns a Unicode object." if value in self.empty_values: return '' value = force_text(value) if self.strip: value = value.strip() return value
Field.validate()
def validate(self, value): if value in self.empty_values and self.required: raise ValidationError(self.error_messages['required'], code='required')
validate()主要验证field的require属性,如果required为True而value却为一个空值就会引发ValidationError。那么,如果我想更改field的required验证输出的错误信息该怎么办?
首先,要先清楚self.error_messages是如何初始化的:
messages = {} for c in reversed(self.__class__.__mro__): messages.update(getattr(c, 'default_error_messages', {})) messages.update(error_messages or {}) self.error_messages = messages
MRO全名为Method Resolution Order,Python在进行多重继承(关于调用super的时候发生了什么可以参考这篇文章)的时候实例调用方法时会按照MRO的list顺序依次向上寻找,MRO的list是由C3算法计算而成,可以参考这篇文章。那么在这里是什么意思呢?
可以看到,self.__classs__.__mro__本来应该返回实例继承从最近的基类到最远的基类的list,因为使用了reversed方法所以从最顶端的基类开始。(访问class的__mro__以及调用mro()都能得到mro列表)message依次更新每个类下的default_error_messages属性,而这个属性将会是一个字典。
假设从IntegerField开始:
# IntegerFieldclass IntegerField(Field): widget = NumberInput default_error_messages = { 'invalid': _('Enter a whole number.'), }# Fieldclass Field(six.with_metaclass(RenameFieldMethods, object)): widget = TextInput # Default widget to use when rendering this type of Field. hidden_widget = HiddenInput # Default widget to use when rendering this as "hidden". default_validators = [] # Default set of validators # Add an 'invalid' entry to default_error_message if you want a specific # field error message not raised by the field validators. default_error_messages = { 'required': _('This field is required.'), }
message先更新Field的default_error_messages,接着是IntegerField。最后message更新初始化参数中的error_message。
因此,如果我想更改field中默认的required属性,只需在参数中传入含有key为required的error_message字典,如果你想更改require验证的code属性,就只能重写validate方法了。
Field.run_validators()
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.run_validators()依次调用self.validators中的由开发者自定义的validator。(该方法推荐不应该重载)
Form.clean_<field_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.add_error(name, e)
在调用了field.clean()之后,通过hasattr方法判定开发者是否有针对各字段定义验证。
至此,整个Form表单验证的流程逻辑就清晰了。
- Django Form源码分析之Field验证逻辑
- Django Form源码分析之BaseForm验证逻辑
- Django Form源码分析之Metaclass的应用
- Lucene 源码分析之Field
- django admin ModelForm field 验证
- Django Form验证
- Django源码分析之server
- Django之Form组件
- Django源码分析之执行入口
- Django源码分析之权限系统
- 【Django】源码分析之session生命周期
- (9)ExtJS之Ext.form.field.Text
- django form 验证end_time不小于start_time
- Django field
- Django field
- Form 验证之钩子
- Django源码分析--引导
- django源码分析
- ※ Leetcode - Array -Remove Duplicates from Sorted Array(就地有序数组去重)
- js-div遮罩层、div弹出层居中(遮罩层居中显示)
- Android 操作数据库的框架——greenDAO的学习
- 数据库中的乐观锁与悲观锁
- new和newInstance() Constructor.newInstance()区别
- Django Form源码分析之Field验证逻辑
- GIT 添加ssh key
- html中的css样式
- HTML标签元素的分类
- UICollectionView使用
- IOS单元测试(—)
- netstat
- iOS-利用运行时给分类添加属性
- 数字推理技巧