""" Django validation and HTML form handling. """ from django.core.exceptions import ValidationError # NOQA from django.forms.boundfield import * # NOQA from django.forms.fields import * # NOQA from django.forms.forms import * # NOQA from django.forms.formsets import * # NOQA from django.forms.models import * # NOQA from django.forms.widgets import * # NOQA
以上代码能够看出,forms整合了fields,widgets,等模块。前端
当is_valid()执行时:数据库
点进去errors:django
再点进去self.full_clean()。这里的_errors是一个错误字典。 app
在没有其余错误的时候,程序又执行了三个方法。第一个的做用是将每个提交过来的字段进行一个一个的验证。第二个是全局验证,第三个是hook。frontend
在第一个函数中,看到name和field分别循环拿到了每个提交过来的字段和其Forms点后面的类进行一个一个验证。没有一个错误都会放到error中。ide
注意红框中的内容:去找一个clean_name的方法。显然是用来尝试拿到用户自定义验证的函数。所以:函数
def clean_username(self): """ Form中字段中定义的格式匹配完以后,执行此方法进行验证 :return: """ value = self.cleaned_data['username'] if "666" in value: raise ValidationError('666已经被玩烂了...', 'invalid') return value
上述的代买就是自定义了username的验证方式,运用此函数就能够随心所欲。能够直接raise一个错误由于外层中已经exception过了。可是返回为clean的值。源码分析
在第二个函数在中:post
点进去clean函数发现是一个hook,这里是让用户自定义全局验证的hook。所以有:spa
def clean(self): v1 = self.cleaned_data['name'] v2 = self.cleaned_data['email'] if v1 == 'root' and v2 == 'root@live.com': pass else: raise ValidationError('用户名或邮箱错误!') return self.cleaned_data
上述代码中的cleaned_data中拿到了提交过来的字段。
第三个函数不在截图了,就是空hook。与子不一样的是返回的东西:
def _post_clean(self): v1 = self.cleaned_data['name'] v2 = self.cleaned_data['email'] if v1 == 'root' and v2 == 'root@live.com': pass else: self.add_error("__all__", ValidationError('用户名或密码错误...'))
发生错误时,返回的是一个双下划綫all,这里是有缘由的。上图中也是一个None。其结果是一致的都在error函数中归为了一个。
总结:就是forms函数验证的过程就是将字段在 full_clean函数中验证,经过_clean_fields一个一个验证,有错误就提交给error记录。
Form: UserForm -> Form -> BaseForm
ModelForm: UserModelForm -> ModelForm -> BaseModelForm -> BaseForm
上述一直在叙述form其实说的就是ModelForm二者继承的同样的东西。
from django.forms import ModelForm class userModelForm(ModelForm): psp = forms.CharField(max_length=32)
和form不一样的是:他不须要本身建对应的字段。只要关联一下app中的models就行,关联方式:在下面建一个meta。
ModelForm a. class Meta: model, # 对应Model的 fields=None, # 字段 exclude=None, # 排除字段 labels=None, # 提示信息 help_texts=None, # 帮助提示信息 widgets=None, # 自定义插件 error_messages=None, # 自定义错误信息(总体错误信息from django.core.exceptions import NON_FIELD_ERRORS) field_classes=None # 自定义字段类 (也能够自定义字段) localized_fields=('birth_date',) # 本地化,如:根据不一样时区显示数据
class CustomerForm(ModelForm): def __new__(cls, *args, **kwargs): for field_name,field_obj in cls.base_fields.items(): if field_name in cls.Meta.readonly_fields: field_obj.widget.attrs['class'] = 'readonly_fields form-control' else: field_obj.widget.attrs['class'] = 'form-control' return ModelForm.__new__(cls) def clean_qq(self): if self.instance.qq != self.cleaned_data['qq']: self.add_error("qq","Unknown error") return self.cleaned_data['qq'] class Meta: model = models.Customer fields = "__all__" exclude = ['tags','content','memo','status','referral_from','consult_course'] readonly_fields = [ 'qq','consultant','source']
在new方法中即生成这个类以前找到了cls中的全部base_fields是个字典,循环这个字典使得每个字段都加上了本身定义的class。这样在前端自动生成的as_p等中自动加上了class。最后return ModelForm.__new__(cls)。
1 # _*_ coding:utf-8 _*_ 2 3 from django.forms import forms,ModelForm,ValidationError 4 from django.utils.translation import ugettext as _ 5 from crm import models 6 7 8 def create_model_form(request,admin_info): # 生成modelform的方法 须要的参数为列表 9 admin_class = admin_info[1] # 这是models的一些其余自定制的信息 相似于admin 10 model_obj = admin_info[0] # 这就是一个须要生成modelform的model表 11 readonly_fields = admin_class.readonly_fields # 拿到model中的只读字段 12 def __new__(cls, *args, **kwargs): #定义modelform的new方法 做用是加上widget 13 for field_name,field_obj in cls.base_fields.items(): #上边有写 每一个字段的名字和对象 14 if admin_class.readonly_table: # 如果在整个表在只读中 15 field_obj.widget.attrs['id'] = 'readonly_fields' # 加上自定好的id和class 16 field_obj.widget.attrs['class'] = 'my-input readonly_fields' 17 elif field_name in readonly_fields: # 同理 某个字段在只读中 18 field_obj.widget.attrs['id'] = 'readonly_fields' 19 field_obj.widget.attrs['class'] = 'my-input readonly_fields' 20 else: 21 field_obj.widget.attrs['class'] = 'my-input' 22 return ModelForm.__new__(cls) # 最后别忘加返回值 返回这个new方法给type 23 24 25 def read_clean(self): # 这里是本身自定制的一个只读验证方法 名字无所谓 是对每一次POST过来的信息校对是否偷偷该只读数据 26 error_list = [] # 目的是防止有人经过更改前端来修改只读数据 27 for i in readonly_fields: # 循环设定好的只读的字段 28 field_val = getattr(self.instance, i) # val in db 这里拿到了实例就是model的这个只读字段 29 if hasattr(field_val, "select_related"): # m2m 查看这个字段是不是m2m 30 m2m_objs = getattr(field_val, "select_related")().select_related() 31 m2m_vals = [j[0] for j in m2m_objs.values_list('id')] # 拿到一个queryset 关联的多对多的id 32 set_m2m_vals = set(m2m_vals) # 转换成set更利于进行对比数据是否有被改变 33 set_m2m_vals_from_frontend = set([i.id for i in self.cleaned_data.get(i)]) # 从自身的数据库取出正确未被改变的数据 34 if set_m2m_vals != set_m2m_vals_from_frontend: #如果不一样 说明只读数据被偷偷修改 35 # error_list.append(ValidationError( 36 # _('Field %(field)s is readonly'), 37 # code='invalid', 38 # params={'field': field}, 39 # )) 40 self.add_error(i, "readonly field") # 最后发现self中的有add_error这个方法 外部modelform有try过 41 continue 42 43 value_custom = getattr(self.instance,i) # 不是多对多的数据能够直接比较 44 value_field = self.cleaned_data.get(i) 45 if value_custom != value_field: # 数据被偷偷修改 46 error_list.append(ValidationError( # 添加一个错误的标准模式,还有列表形式 47 _('Field %(field)s is readonly,data should be %(val)s'), 48 code='invalid', 49 params={'field': i, 'val': value_custom}, 50 )) 51 52 if admin_class.readonly_table: # 只有是post方式才会触发 modelform 53 raise ValidationError( 54 _('Table is readonly,cannot be modified or added'), 55 code='invalid' 56 ) 57 58 if error_list: 59 raise ValidationError(error_list) 60 61 62 class Meta: # 定义modelform的meta函数 63 model = model_obj 64 fields = "__all__" # 默认为all 65 exclude = admin_class.exclude_fields # 除外的从其传过来的里面找 66 attrs = {'Meta':Meta} # 这里必须这样定义 要不报错 67 _model_form_class = type("DynamicModelForm",(ModelForm,),attrs) #生成类的方法是用type 3个参数 分别是名字(自定义) 继承 和 meta方法 68 setattr(_model_form_class,'__new__',__new__) #经过setattr的方式将一个函数变为类的方法 69 setattr(_model_form_class,'clean',read_clean) #经过setattr的方式将一个函数变为类的方法 70 return _model_form_class # 返回已经生成的modelform
这样,经过调用这个方法就能够生成不一样表的modelform。用于model表的验证,前端生成,增删改查。前端循环这个modelform每一个字段自动生成。
1 if request.method == "POST": 2 form_obj = model_form_class(request.POST,instance=obj) #更新 3 if form_obj.is_valid(): 4 form_obj.save() 5 else: 6 form_obj = model_form_class(instance=obj)
这里是一个更新操做,也是一个修改操做。与增长惟一不一样的是是否有实例:instance 验证和save方法一步解决。