Form组件
手动实现登录校验的功能
前端代码
<form action="" method="post"> {% csrf_token %} <p>username:<input type="text" name="username" value="{{ username }}"><span>{{ back_dic.username }}</span></p> <p>password:<input type="text" name="password" value="{{ password }}"><span>{{ back_dic.password }}</span></p> <p><input type="submit"></p> </form>
后端代码
def register(request): back_dic = { "username":"", "password":"" } username = "" password = "" if request.method == 'POST': username = request.POST.get("username") password = request.POST.get("password") if "666"in username: # 不符合,就把提示信息加入到字典中,在页面中相应的地方渲染 back_dic["username"] = "喊6剁手" if len(password) < 3: back_dic["password"] = "密码太短" return render(request, "register.html",locals()) # 除了大字典传参渲染页面之外, locals()也可以传参渲染,并且locals()会把定义的变量全部传过去
上述代码我们一共实现三个功能
1. 渲染页面
2. 对数据进行校验
3. 展示提示信息
form组件实现的功能
form组件主要也是帮我们实现上面三个功能
1. 对数据进行校验
数据校验分为前端校验和后端校验两种,前端校验可以不做,后端校验必须做
因为前端很脆弱,别人可容易就会越过你的校验,如果我们后端不做校验
就会很危险,后端校验如下
# 使用form组件的第一步就是需要我们自己写一个类去继承forms.Form from django import forms class MyForm(forms.Form): username = forms.CharField(max_length=6, label="用户名") # max_length表示限制条件, label表示文本信息 password = forms.CharField(max_length=6, min_length=3, label="密码", error_messages={"min_length":"密码太短"}) def register(request): # 生成一个form对象 form_obj = MyForm() if request.method == 'POST': # 前端传过来数据之后 # 1. 给form组件传参,字典的形式 form_obj = MyForm(request.POST) # 2.判断数据是否全部合法 is_valid() print(form_obj.is_valid()) # is_valid() 方法会校验你传入的数据是否符合定义的限制 # 只要有一个不符合就会返回False # 3. 查看所有校验通过的数据cleaned_data print(form_obj.cleaned_data) # 4. 查看所有没通过的数据及其报错信息errors print(form_obj.errors) return render(request, "register.html", locals())
2. 渲染页面
渲染页面主要有三种方式
第一种渲染标签方式(本地)
{{ form_obj.as_p }}
直接通过forms对象点as_p 就可以生成所有被p包裹的标签,
如果点as_ul,就会生成被li包裹的标签,并且在展示时,默认会把label标签的首字母大写
但是这种方法的封装程度太高,不方便我们后期添加样式,所以基本上作本地测试用
第二种渲染标签方式(可扩展性高)
<p>{{ form_obj.username.label }}{{ form_obj.username }}</p> <p>{{ form_obj.password.label }}{{ form_obj.password }}</p>
我们自己定义标签,通过forms对象点出单独的标签,这种方式的扩展性高
但是如果我们forms对象的字段比较多的话,一个一个点比较麻烦
第三种渲染标签的方式(常用)
{% for foo in form_obj %}
{{ foo.label }}{{ foo }}
{% endfor %}
我们可以循环forms对象,得到是每一个字段对应的标签,这样我们就可以
通过点语法得到我们想要的标签(扩展性高且不用写太多代码)
3. 展示错误信息
当我们渲染完标签之后,我们会发现当我们输入错误信息的时候,浏览器端
会提示我们报错信息,如下
这种提示错误信息的方式其实是forms组件在前端做了判断,并展示出报错信息
前面我们也提到过,既然后端进行了数据校验,那么我们可不可以自定义提示信息呢
如果我们想展示后端错误信息,那首先需要让前端停止校验
<form action="" method="post" novalidate>
novalidate的意思就是让前端停止校验
首选我们需要在每个字段中添加错误条件及其对应的提示 error_messages
password = forms.CharField(max_length=6, min_length=3, label="密码",
error_messages={
"min_length":"密码太短",
"max_length":"密码太长"
})
然后我们在前端,可以通过.errors拿到报错信息的一个列表,索引0取出信息
{% for foo in form_obj %} <p>{{ foo.label }}{{ foo }} {{ foo.errors }}</p> {% endfor %}
这样就可以展示出我们自己的提示信息了
还有一点之前忘了补充:
那就是如果我们不用forms组件去提示错误信息,那也会把用户之前输入的
信息全部刷新掉,这种体验是非常差的,而我们的forms组件则会保留之前的信息
form组件的hook函数(钩子函数)
上面展示的错误提示都是一些系统里面自带的校验规则,例如最大、最小长度
那我们想自己定义一些规则,例如用户名不能包含666,这就需要用到钩子函数
钩子函数分为两种,局部钩子与全局钩子
局部钩子
局部钩子只能对单个字段进行二次校验
def clean_username(self): # 局部钩子通过clean_字段名 定义, 只要在上面出现过的字段,在这都会有提示 username = self.cleaned_data.get("username") # 然后我们取值需要从校验成功这里面取值 if "666" in username: # 进行判断,错误就通过add_error添加到错误信息中 self.add_error("username", "不能包含666") # add_error("字段名", "错误信息") return username # 为了健壮性, 就把username返出去
全局钩子
全局钩子可以对多个字段联合起来校验
def clean(self): # 全局钩子直接通过clean来定义 password = self.cleaned_data.get("password") confirm_password = self.cleaned_data.get("confirm_password") if password != confirm_password: self.add_error("confirm_password", "两次密码不一致") return self.cleaned_data
不管是全局钩子,还是局部钩子,都只有在通过了第一次校验之后,才会进入钩子函数
forms组件类中一些其他字段及参数
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类型 Django Form内置字段