day12 form组件


day12 form组件

今日内容

  • form组件前戏
  • form组件基本定义
  • form组件数据校验功能
  • form组件渲染标签
  • form组件提示信息
  • 数据校验进阶
  • form组件补充
  • form组件源码探索

form组件前戏

"""
练习
	页面上获取用户输入的用户名和密码
	然后判断用户名和密码是否符合一些条件 如果不符合则返回相应的提示信息
"""
自定义数据校验功能 主要步骤
1.搭建前端页面				标签渲染
2.校验数据是否合法			   数据校验
3.返回相应的提示信息			  提示信息
# 上述的三个步骤form组件都可以自动实现

form组件基本定义

class MyForm(forms.Form):
    # username字段最短3位最长8位
    username = forms.CharField(min_length=3, max_length=8, label='用户名')  
    # password字段最短3位最长8位
    password = forms.CharField(min_length=3, max_length=8, label='密码')
    # email字段必须符合邮箱格式
    email = forms.EmailField(label='邮箱')  # label参数可以自定义名称

form组件数据校验功能

1、传入待校验的数据
	obj = views.MyForm({'username':'meng','password':'123','email':'123@qq.com'})

2、判断所有的数据是否合规 # is_valid()
	obj.is_valid()  # 结果是布尔值(只有所有的数据全部合规才返回True)
    
3、 查看符合条件校验的数据
	obj.cleaned_data  # 返回结果是字典(是所有通过校验的数据)
    
4、查看不符合条件检验的数据和错误的原因
	obj.errors
    
''' 
1.在传递数据的时候,如果多传了额外字段,没有关系,orm不校验
2.form组件内部定义的字段数据 默认都是必填的
	可以通过修改参数required=False来控制是否必填
	email = forms.EmailField(required=False)
'''

form组件渲染标签

# 第一步先在后端穿衣form对象
def my_form(request):
    # 1.生成一个不传参数的form对象
    obj = MyForm()
    return render(request,'my_form.html',locals())
"""
总结:
    自动渲染的标签文本名称默认采用字段名首字母大写的形式
        可以通过label参数自定义名称
    form只渲染获取用户数据的标签 不渲染提交按钮 需要我们自己编写
"""

前端:
# 渲染标签的方式1
	{{ obj.as_p }}  # 封装程度太高 扩展不易 通常用于本地测试
    {{ obj.as_ul }}
    {{ obj.as_table }}
    '''
    总结:
        虽然可以不用自己写就能渲染出用户名等等,
        但是无法修改标签,扩展不易
        可以使用点as_标签,来决定使用什么标签
    '''
    
    
# 渲染标签的方式2
    
    {{ obj.username }}  # 扩展性高 但是书写麻烦
    '''
    总结:
        分两步写的用意是:用户名和输入框是分开的
        因为输入框中的id是自动生成的,只能使用参数获取
        username.id_for_label:获取uaername的id值绑定给后面的label用户名
        达成的效果:点用户名会直接进入到输入框中
    '''
    
    
# 渲染标签的方式3(使用频率较高)
	{% for foo in obj %}
        

{{ foo }} # 融合了方式1和方式2的优势(推荐使用)

{% endfor %} ''' 总结: 使用for循环可以把定义类中的所有字段都循环出来,不用自己创建 而且 融合了方式1和方式2的优势(推荐使用) '''

form组件提示信息

1、前端虽然有校验功能,但是太容易修改了,所以取消前端的校验
	
# form表单中novalidate参数是取消前端校验的 2、form组件如何渲染错误信息 {{ foo.errors.0 }} # 加上这个会在浏览器中,显示错误信息 3、自定义错误信息 class MyForm(forms.Form): # username字段最短3位最长8位 username = forms.CharField(min_length=3, max_length=8, label='用户名', error_messages={ 'min_length': '用户名最少3位', 'max_length': '用户名最多8位', 'required': '用户名不能为空' } ) # email字段必须符合邮箱格式 email = forms.EmailField(label='邮箱', error_messages={ 'required':'邮箱不能为空', 'invalid':'邮箱格式不正确' } ) # 还有一种方法把错误信息变成中文 在settings.py中修改字符编码:LANGUAGE='zh_hans' ''' 总结: 1.先取消前端校验功能,转为后端代码校验 2.在前端中添加{{ foo.errors.0 }}显示错误信息 3.自定义错误信息,用户名和密码的错误信息一致,邮箱设置invalid参数 '''

校验功能的核心逻辑

# views.py
def my_form(request):
    # 1.生成一个不传参数的form对象
    obj = MyForm()
    # 2.判断请求方式
    if request.method == 'POST':
        print(request.POST)  # 
        # 3.直接将数据传入类中
        obj = MyForm(request.POST)  # 传入参数的obj,必须和上面名字一样
        # 4.判断数据是否合规
        if obj.is_valid():
            print(obj.cleaned_data)
        else:
            print(obj.errors)
            # 5.让前端展示给用户看
    return render(request,'my_form.html',locals())




# my_form.html
  # novalidate:取消校验
	{% for foo in obj %}
        

{{ foo }} {{ foo.errors.0 }} # 显示错误信息

{% endfor %}

数据校验进阶

正则校验

from django import forms
from django.core.validators import RegexValidator  # 需要导入这个模块才能使用正则
class MyForm(forms.Form):
    # phone字段检验手机号是否正确
    phone = forms.CharField(label='手机号',
            validators=[RegexValidator(r'^[0-9]+$', '请输入数字'),
                       (r'^(151|176|187|136|166|177|138|)[0-9]{8}$', '请输入正确的手机号')]
                       )
    
'''
总结:
	使用验证器validators参数,加上
'''

钩子函数之局部钩子

# 钩子函数:基本校验规则之后,产生了cleaned_data数据后才会执行钩子函数
# 局部钩子:校验用户名是否存在

class MyForm(forms.Form):
    # 在类中定义函数默认绑定给类
    def clean_username(self):
        # 获取用户名数据
        username = self.cleaned_data.get('username')
        # 校验用户名是否已存在
        if username == 'meng':
            # 添加用户名提示信息
            self.add_error('username', '用户名已存在')
            # 钩子函数把什么数据勾走,必须返回什么数据
            return username
'''
校验用户名时也可以跟数据库里校验
ret = models.User.objects.filter(name=username)  
# 判断数据库是否有这个用户名
if not ret:
	return username
else:
	raise ValidationError("该用户已注册!")
'''

钩子函数之全局钩子

# 全局钩子:多个字段进行校验
# 校验密码与确认密码是否一致

class MyForm(forms.Form):
    # 在类中定义函数默认绑定给类
    def clean(self):  # clean是全局钩子的意思
        # 获取密码
        password = self.cleaned_data.get('password')
        # 获取确认密码
        re_password = self.cleaned_data.get('re_password')
        # 比对密码是否一致
        if not password == re_password:
            # 添加提示信息
            self.add_error('re_password', '两次密码不一致')
        return self.cleaned_data
    
'''
总结:
	add_error:添加提示信息
	步骤基本和局部钩子一致,但是返回结果是cleaned_data,固定格式
	clean:就是钩注全部的意思,所以要返回全部
'''

form其他组件

initial:初始值

class MyForm(forms.Form):
    # username字段最短3位最长8位
    username = forms.CharField(
        					 min_length=3,
                               max_length=8,
                               label='用户名',
                               initial='meng'   # 加一个默认值
                               )

error_messages:自定义错误信息

class MyForm(forms.Form):
    # username字段最短3位最长8位
    username = forms.CharField(
        					 min_length=3, 
                               max_length=8,
                               label='用户名',
                               error_messages={  # 自定义错误信息
                                   'min_length': '用户名最少3位',
                                   'max_length': '用户名最多8位',
                                   'required': '用户名不能为空'
                               }
                               )

widget:修改标签的属性

class MyForm(forms.Form):
    password = forms.CharField(min_length=3,
                               max_length=8,
                               label='密码',
                               # 把密码输入框变成密文,并且里面还可以加bootstrap样式
                                widget=forms.widgets.PasswordInput(attrs={'class': 'form-control'}),  
                               error_messages={
                                   'min_length': '密码最少3位',
                                   'max_length': '密码名最多8位',
                                   'required': '密码不能为空',
                               }
                               )

radioSelec:性别设置

class MyForm(forms.Form):
    ...
    gender = forms.fields.ChoiceField(
        choices=((1, "男"), (2, "女"), (3, "保密")),
        label="性别",
        initial=3,
        widget=forms.widgets.RadioSelect()
    )

单选Select:单选框

class MyForm(forms.Form):
    ...
    hobby = forms.ChoiceField(
        choices=((1, "篮球"), (2, "足球"), (3, "双色球"), ),
        label="爱好",
        initial=3,
        widget=forms.widgets.Select()
    )

多选Select:多选框

class MyForm(forms.Form):
    ...
    hobby = forms.MultipleChoiceField(
        choices=((1, "篮球"), (2, "足球"), (3, "双色球"), ),
        label="爱好",
        initial=[1, 3],
        widget=forms.widgets.SelectMultiple()
    )

单选checkbox

class MyForm(forms.Form):
    ...
    keep = forms.ChoiceField(
        label="是否记住密码",
        initial="checked",
        widget=forms.widgets.CheckboxInput()
    )

多选checkbox

class MyForm(forms.Form):
    ...
    hobby = forms.MultipleChoiceField(
        choices=((1, "篮球"), (2, "足球"), (3, "双色球"),),
        label="爱好",
        initial=[1, 3],
        widget=forms.widgets.CheckboxSelectMultiple()
    )

form组件源码探索

'''阅读源码是提升编程思维的最佳方式!!!'''

# 突破口
# 1.点击is_valid()方法
    is_valid()
    # 因为cleaned_data和errors,都是通过is_valid()产生的
    
# 2.查看返回的两个方法
    def is_valid(self):
        # 如果两个都是True才能返回
        return self.is_bound and not self.errors 
    
    # is_bound封装的方法
        def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
        # data是传入类中的值,肯定为True
        self.is_bound = data is not None or files is not None
                     
    # 最后是这三个封装的方法
        def full_clean(self):          
            self._clean_fields()
            self._clean_form()  # 全局钩子
            self._post_clean()  # 开放的钩子接口

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.1, PS:protocol必须为both才能启用
 
SlugField(CharField)           数字,字母,下划线,减号(连字符)
    ...
 
UUIDField(CharField)           uuid类型

相关