博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

forms组件

Posted on 2022-03-13 20:13  ~sang  阅读(13)  评论(0编辑  收藏  举报

forms组件之校验字段

# 第一步:定义一个类,继承forms.Form
# 第二步:在类中写字段,要校验的字段,字段属性就是校验规则
# 第三步:实例化得到一个Form对象,把要校验的数据传入
# 第四步:调用register_form.is_valid()校验,校验通过就是True
# 第五步:校验通过有register_form.cleaned_data
# 第六步:校验不通过register_f

django Form所有内置字段

Field
   required=True,               是否允许为空
   widget=None,                 HTML插件
   label=None,                  用于生成Label标签或显示内容
   initial=None,                初始值
   help_text='',                帮助信息(在标签旁边显示)
   error_messages=None,         错误信息 {'required': '不能为空', 'invalid': '格式错误'}
   validators=[],               自定义验证规则
   localize=False,              是否支持本地化
   disabled=False,              是否可以编辑
   label_suffix=None            Label内容后缀


CharField(Field)
   max_length=None,             最大长度
   min_length=None,             最小长度
   strip=True                   是否移除用户输入空白

IntegerField(Field)
   max_value=None,              最大值
   min_value=None,              最小值

FloatField(IntegerField)
  ...

DecimalField(IntegerField)
   max_value=None,              最大值
   min_value=None,              最小值
   max_digits=None,             总长度
   decimal_places=None,         小数位长度

BaseTemporalField(Field)
   input_formats=None          时间格式化  

DateField(BaseTemporalField)    格式:2015-09-01
TimeField(BaseTemporalField)    格式:11:12
DateTimeField(BaseTemporalField)格式:2015-09-01 11:12

DurationField(Field)            时间间隔:%d %H:%M:%S.%f
  ...

RegexField(CharField)
   regex,                      自定制正则表达式
   max_length=None,            最大长度
   min_length=None,            最小长度
   error_message=None,         忽略,错误信息使用 error_messages={'invalid': '...'}

EmailField(CharField)      
  ...

FileField(Field)
   allow_empty_file=False     是否允许空文件

ImageField(FileField)      
  ...
   注:需要PIL模块,pip3 install Pillow
   以上两个字典使用时,需要注意两点:
       - form表单中 enctype="multipart/form-data"
       - view函数中 obj = MyForm(request.POST, request.FILES)

URLField(Field)
  ...


BooleanField(Field)  
  ...

NullBooleanField(BooleanField)
  ...

ChoiceField(Field)
  ...
   choices=(),                选项,如:choices = ((0,'上海'),(1,'北京'),)
   required=True,             是否必填
   widget=None,               插件,默认select插件
   label=None,                Label内容
   initial=None,              初始值
   help_text='',              帮助提示


ModelChoiceField(ChoiceField)
  ...                        django.forms.models.ModelChoiceField
   queryset,                  # 查询数据库中的数据
   empty_label="---------",   # 默认空显示内容
   to_field_name=None,        # HTML中value的值对应的字段
   limit_choices_to=None      # ModelForm中对queryset二次筛选
   
ModelMultipleChoiceField(ModelChoiceField)
  ...                        django.forms.models.ModelMultipleChoiceField


   
TypedChoiceField(ChoiceField)
   coerce = lambda val: val   对选中的值进行一次转换
   empty_value= ''            空值的默认值

MultipleChoiceField(ChoiceField)
  ...

TypedMultipleChoiceField(MultipleChoiceField)
   coerce = lambda val: val   对选中的每一个值进行一次转换
   empty_value= ''            空值的默认值

ComboField(Field)
   fields=()                  使用多个验证,如下:即验证最大长度20,又验证邮箱格式
                              fields.ComboField(fields=[fields.CharField(max_length=20), fields.EmailField(),])

MultiValueField(Field)
   PS: 抽象类,子类中可以实现聚合多个字典去匹配一个值,要配合MultiWidget使用

SplitDateTimeField(MultiValueField)
   input_date_formats=None,   格式列表:['%Y--%m--%d', '%m%d/%Y', '%m/%d/%y']
   input_time_formats=None    格式列表:['%H:%M:%S', '%H:%M:%S.%f', '%H:%M']

FilePathField(ChoiceField)     文件选项,目录下文件显示在页面中
   path,                      文件夹路径
   match=None,                正则匹配
   recursive=False,           递归下面的文件夹
   allow_files=True,          允许文件
   allow_folders=False,       允许文件夹
   required=True,
   widget=None,
   label=None,
   initial=None,
   help_text=''

GenericIPAddressField
   protocol='both',           both,ipv4,ipv6支持的IP格式
   unpack_ipv4=False          解析ipv4地址,如果是::ffff:192.0.2.1时候,可解析为192.0.2.1PS:protocol必须为both才能启用

SlugField(CharField)           数字,字母,下划线,减号(连字符)
  ...

UUIDField(CharField)           uuid类型

 

forms组件之渲染标签

# 渲染方式一
<h2>通过form自动渲染一</h2>
<form action="" method="post">
   <p>用户名 {{ form.name }}</p>
   <p>密码 {{ form.password }}</p>
   <p>确认密码 {{ form.re_password }}</p>
   <p>邮箱 {{ form.email }}</p>
   <input type="submit" value="提交">
</form>
# 渲染方式二
<h2>通过form自动渲染二(基本用这种)</h2>
<div class="container-fluid">
   <div class="row">
       <div class="col-md-6 col-md-offset-3">
           <form action="" method="post" novalidate>
              {% for item in form %}
                   <div class="form-group">
                       <p>{{ item.label }}{{ item }} <span style="color: red">{{ item.errors.0 }}</span></p>
                   </div>

              {% endfor %}
               <input type="submit" value="提交"><span style="color: red">{{ error }}</span>

           </form>
       </div>
   </div>
</div>
# 渲染方式三
<h2>通过form自动渲染三</h2>
<form action="" method="post">
  {{ form.as_p }}
  {#   {{ form.as_table }}#}
  {#   {{ form.as_ul }}#}

</form>

forms组件全局钩子,局部钩子(注册验证)

# 后端
from django import forms
from django.forms import widgets
from django.core.exceptions import ValidationError

class RegisterForm(forms.Form):
   name = forms.CharField(max_length=8, min_length=3, label='用户名',
                          error_messages={'max_length': '最长为8', 'min_length': '最短为3'},
                          widget=widgets.TextInput(attrs={'class':'form-control'}))
   password = forms.CharField(max_length=8, min_length=3, label='密码',
                              error_messages={'required': '该字段必填'},
                              widget=widgets.PasswordInput(attrs={'class':'form-control'}))
   re_password = forms.CharField(max_length=8, min_length=3, label='确认密码',
                                 widget=widgets.PasswordInput(attrs={'class':'form-control'}))
   email = forms.EmailField(label='邮箱',
                            error_messages={'required': '必填', 'invalid': '必须是邮箱格式'},
                            widget=widgets.TextInput(attrs={'class':'form-control'}))
   def clean_name(self):#name字段的局部钩子
       # 校验名字不能以sb开头
       name=self.cleaned_data.get('name')
       if name.startswith('sb'):
           # 校验不通过,必须抛异常,
           raise ValidationError('不能以sb开头')
       else:#校验通过,返回name对应的值

           return name

   def clean(self): # 全局钩子
       password=self.cleaned_data.get('password')
       re_password=self.cleaned_data.get('re_password')
       if re_password==password:
           #校验通过
           return self.cleaned_data
       else:
           self.add_error('re_password', '两次密码不一致')
           raise ValidationError('两次密码不一致')
   
from app01 import models
def register(request):
   if request.method == 'GET':
       # 生成一个空form对象
       register_form = RegisterForm()
       return render(request, 'register.html', {'form': register_form})

   else:
       # 实例化得到对象,传入要校验的数据
       # register_form=RegisterForm(data=request.POST)
       register_form = RegisterForm(request.POST)
       if register_form.is_valid():
           # 校验通过,存
           # 取出校验通过的数据
           print('校验通过')
           print(register_form.cleaned_data)
           register_form.cleaned_data.pop('re_password')
           models.User.objects.create(**register_form.cleaned_data)
           return HttpResponse('ok')

       else:
           # 校验不通过
           print('校验不通过')
           print(register_form.errors)
           # error=register_form.errors.get('__all__')[0]
           print(type(register_form.errors.as_json))
           from django.forms.utils import ErrorDict
           # name_error=register_form.errors.get('name')[0]
           return render(request, 'register.html', {'form': register_form})
# 前端
<h2>通过form自动渲染二(基本用这种)</h2>
<div class="container-fluid">
   <div class="row">
       <div class="col-md-6 col-md-offset-3">
           <form action="" method="post" novalidate> //novalidate 属性规定当提交表单时不对其
              {% for item in form %}
                   <div class="form-group">
                       <p>{{ item.label }}{{ item }} <span style="color: red">{{ item.errors.0 }}</span></p>
                   </div>

              {% endfor %}
               <input type="submit" value="提交">

           </form>
       </div>
   </div>
</div>

使用步骤

-写一个类,继承Form类
       -写字段,字段参数(限制该字段的长短)
       -错误信息中文:字段参数
       -widget:控制生成标签的属性
       -视图函数中:
      -实例化得到form对象时,把要校验的数据传入
           -is_valid():clean_data和errors就有值了
           -如果校验通过就存,不通过就给页面提示
      -渲染页面
  -for循环的方式渲染页面(在标签前后可以再加标签)
  -局部钩子
  -def clean_字段名(self):
      -校验规则
           -如果通过,return
           -如果不通过,抛异常
  -全局钩子(多个字段校验)
-def clean(self):
      -如果通过,return clean_data
           -如果不通过,抛异常

forms组件源码分析

1 为什么局部钩子要写成 clean_字段名,为什么要抛异常
2 入口在 is_valid()
3 校验流程
-先校验字段自己的规则(最大,最小,是否必填,是不是合法)
   -校验局部钩子函数
   -全局钩子校验
   
   
4 流程
-is_valid()---》return self.is_bound and not self.errors
   -self.errors:方法包装成了数据数据
  -一旦self._errors有值,就不进行校验了(之前调用过了)
   -self.full_clean():核心
  self._errors = ErrorDict()
       if not self.is_bound:  
           return
       self.cleaned_data = {}
       self._clean_fields()
       self._clean_form()
       self._post_clean()
       
       
   -self._clean_fields():核心代码,局部钩子执行位置
   
    value = field.clean(value)# 字段自己的校验规则
    self.cleaned_data[name] = value #把校验后数据放到cleaned_data
    if hasattr(self, 'clean_%s' % name): # 判断有没有局部钩子
       value = getattr(self, 'clean_%s' % name)() #执行局部钩子
       self.cleaned_data[name] = value #校验通过,把数据替换一下
  # 如果 校验不通过,会抛异常,会被捕获,捕获后执行
   self.add_error(name, e)
   
 - def _clean_form(self):#全局钩子执行位置
def _clean_form(self):
       try:
           #如果自己定义的form类中写了clean,他就会执行
           cleaned_data = self.clean()
       except ValidationError as e:
           self.add_error(None, e)