一、Forms介绍
我们之前在HTML页面中利用form表单向后端提交数据时,都会写一些获取用户输入的标签并且用form标签把它们包起来。
与此同时我们在好多场景下都需要对用户的输入做校验,比如校验用户是否输入,输入的长度和格式等正不正确。如果用户输入的内容有错误就需要在页面上相应的位置显示显示对应的错误信息.。
Django form组件就实现了上面所述的功能。
总结一下,其实form组件的主要功能如下:
- 生成页面可用的HTML标签
- 对用户提交的数据进行校验
- 保留上次输入内容
二.Forms组件的校验功能
models.py
from django.db import models # Create your models here. class UserInfo(models.Model): name=models.CharField(max_length=32) pwd=models.CharField(max_length=32) email=models.EmailField() tel=models.CharField(max_length=32)
views.py
# Create your views here. from django import forms from django.forms import widgets from app01.models import UserInfo from django.core.exceptions import NON_FIELD_ERRORS, ValidationError # 示例1:定义规则类 # class UserForm(forms.Form): # 创建forms组件就是这个类,名字任意叫;定义的规则可以多写(多的忽略那里)不能少写 # name = forms.CharField(min_length=4) # email = forms.EmailField() # 示例2: class UserForm(forms.Form): name = forms.CharField(min_length=4, label="用户名") pwd = forms.CharField(min_length=4, label="密码") r_pwd = forms.CharField(min_length=4, label="确认密码") email = forms.EmailField(label="邮箱") tel = forms.CharField(label="电话") def reg(request): if request.method == "POST": # print(request.POST) # 示例1####################################################### ''' form = UserForm({"name":"mm",'email':"123@qq.com","xxx":"mm"})
# 类的实例化,可以给它传参,只会给你校验字段里边有的数值,没有的不会校验,有的校验完就会返回True;
# forms组件的规则是有几个就要写几个,多了无所谓不能少写,而且传来的也要符合字段规则 # 上面写得"xxx":"mm"可以忽略掉,判断是否合法只需要在类定义的规则全部能在上面找到就可以了 print(form.is_valid()) # 返回布尔值,判断是否合法 if form.is_valid(): # 若form = UserForm({"name":"mumu",'email':"123@qq.com","xxx":"mm"}) print(form.cleaned_data) # {'email': '123@qq.com', 'name': 'mumu'} else: # 若form = UserForm({"name":"mm",'email':"123@qq.com","xxx":"mm"}) print(form.cleaned_data) # {'email': '123@qq.com'} print(form.errors) # 错误的键作为键,错误信息作为值,{"name":["Ensure this value has at least 4 characters (it has 2)."]} # <ul class="errorlist"><li>name<ul class="errorlist"><li>Ensure this value has at least 4 characters (it has 2).</li></ul></li></ul> ''' ''' if 所有字段校验成功,则form.cleaned_data:{'email': '123@qq.com', 'name': 'mumu'} ''' # 示例2#######################################################
# 只要能传来这样一个字典,字典对应的键和值;键能跟它的字段匹配上,就可以做校验 form = UserForm(request.POST) # 前端form表单的name属性值应该与forms组件字段名称一致
print(form.is_valid()) # 返回布尔值,判断是否合法 if form.is_valid(): print(form.cleaned_data) # 这里全部都是对的键值对{'email': '123@qq.com', 'pwd': '1234', 'tel': '124764656', 'name': 'mumu', 'r_pwd': '1234'} else: print(form.cleaned_data) #这里两个字典,这里放正确的 {'email': '123@qq.com'} # print(form.errors) #这里放错误的 {"name":[......]} # print(type(form.errors)) # ErrorDict # # print(form.errors.get("name")) # print(type(form.errors.get("name"))) # ErrorList # # print(form.errors.get("name")[0]) # 取第一个,该用户已注册 return HttpResponse("OK") return render(request,"reg.html",locals()) # 这是get请求
reg.html
<html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <form action="" method="post"> {% csrf_token %} <p>用户名 <input type="text" name="name"></p> <p>密码 <input type="text" name="pwd"></p> <p>确认密码 <input type="text" name="r_pwd"></p> <p>邮箱 <input type="text" name="email"></p> <p>电话 <input type="text" name="tel"></p> <input type="submit"> </form> </body> </html>
三.渲染标签
reg.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h3>forms组件渲染方式1</h3>
{#这种方式直接帮助我们保证form表单的属性值与forms组件字段名称一致 ;是get请求的时候#} <form action="" method="post"> {% csrf_token %} <p>{{ form.name.label }} {# 这里的文字(用户名)可以不写,可以调用form.name.label得到,此时是英文名称,如果想得到中文的,可以在views中的UserForm类name中添加一个label="用户名"#} {{ form.name }} </p> <p>密码 {{ form.pwd }} </p> <p>确认密码 {{ form.r_pwd }} </p> <p>邮箱 {{ form.email }} </p> <p>电话 {{ form.tel }} </p> <input type="submit"> </form> <hr> <h3>forms组件渲染方式2</h3> <form action="" method="post"> {% csrf_token %} {% for field in form %} {# for循环这个form;拿到这个field(每个字段对象,等同于上面方式1的from.name,from.pwd...)#} <p> <label for="">{{ field.label }}</label> {{ field }} </p> {% endfor %} <input type="submit"> </form> <hr> <h3>forms组件渲染方式3</h3> <form action="" method="post"> {% csrf_token %} {{ form.as_p }} {# 不建议使用,类似的还有as_ul,这样是固定死了为p标签,如果以后想改成其他的如div就很麻烦#} <input type="submit"> </form> </body> </html
渲染错误信息
第一个form通过验证已经有一个个的数据了,区别在于它传到reg.html时候,它.name还是input标签,你点提交这个页面没有变;同时它也可以把你传的那个信息给渲染出来作为input标签的value值。
之所以能看到错误信息,是因为我post提交了构建了一个新form页面,在post请求下加了下面这个: return render(request, "reg.html", locals())
reg.html
<h3>forms组件渲染方式1</h3> 这种方式直接帮助我们保证form表单的属性值与forms组件字段名称一致 <form action="" method="post"> {% csrf_token %} <p>{{ form.name.label }} {# 这里的文字(用户名)可以不写,可以调用form.name.label得到,此时是英文名称,如果想得到中文的,可以在views中的UserForm类name中添加一个label="用户名" {{ form.name }} <span>{{ form.name.errors.0 }}</span> {# 显示错误信息 </p> <p>密码 {{ form.pwd }} <span>{{ form.pwd.errors.0 }}</span> </p> <p>确认密码 {{ form.r_pwd }} <span>{{ form.r_pwd.errors.0 }}</span> </p> <p>邮箱 {{ form.email }} <span>{{ form.email.errors.0}}</span> </p> <p>电话 {{ form.tel }} <span>{{ form.tel.errors.0 }}</span> </p> <input type="submit"> </form>
views.py
# -*- encoding:utf-8 -*- from django.shortcuts import render,HttpResponse # Create your views here. from django import forms class UserForm(forms.Form): name = forms.CharField(min_length=4, label="用户名") pwd = forms.CharField(min_length=4, label="密码") r_pwd = forms.CharField(min_length=4, label="确认密码") email = forms.EmailField(label="邮箱") tel = forms.CharField(label="电话") def reg(request): if request.method == "POST": # print(request.POST) ''' if 所有字段校验成功,则form.cleaned_data:{'email': '123@qq.com', 'name': 'mumu'} ''' form = UserForm(request.POST) # form表单的name属性值应该与forms组件字段名称一致 print(form.is_valid()) # 返回布尔值,判断是否合法 if form.is_valid(): print(form.cleaned_data) # {'email': '123@qq.com', 'pwd': '1234', 'tel': '124764656', 'name': 'mumu', 'r_pwd': '1234'} else: print(form.cleaned_data) # {'email': '123@qq.com'} # print(form.errors) # {"name":[......]} # print(type(form.errors)) # ErrorDict # # print(form.errors.get("name")) # print(type(form.errors.get("name"))) # ErrorList # # print(form.errors.get("name")[0]) # 取第一个 return render(request, "reg.html", locals()) # 如果校验失败后,它返回这个页面,可以把第一次输入的信息给保存了,作为value值;它走的是post请求 # return HttpResponse("OK") form = UserForm() # 未绑定数据表单对象,上边的form是绑定表单对象 # 这个from跟上边那个form=UserForm(request.POST)不是一个form;上边的form即使验证失败了它会给你保留你输入的values值 return render(request,"reg.html",locals()) # 把值传进来进行渲染;get请求
四.Forms组件的参数配置
reg.html
导入bootstrap优化页面
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <!-- 最新版本的 Bootstrap 核心 CSS 文件 --> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> </head> <body> {# 加了bootstrap后优化的#} <div class="container"> <div class="row"> <div class="col-md-6 col-lg-offset-3"> <h3>forms组件渲染方式1</h3> {# 这种方式直接帮助我们保证form表单的属性值与forms组件字段名称一致#} <form action="" method="post"> {% csrf_token %} <p>{{ form.name.label }} {# 这里的文字(用户名)可以不写,可以调用form.name.label得到,此时是英文名称,如果想得到中文的,可以在views中的UserForm类name中添加一个label="用户名" #} {{ form.name }} <span class="pull-right text-danger">{{ form.name.errors.0 }}</span> {# 显示错误信息 pull-right:右对齐,text-danger:标红#} </p> <p>密码 {{ form.pwd }} <span class="pull-right text-danger">{{ form.pwd.errors.0 }}</span> </p> <p>确认密码 {{ form.r_pwd }} <span class="pull-right text-danger">{{ form.r_pwd.errors.0 }}</span> </p> <p>邮箱 {{ form.email }} <span class="pull-right text-danger">{{ form.email.errors.0}}</span> </p> <p>电话 {{ form.tel }} <span class="pull-right text-danger">{{ form.tel.errors.0 }}</span> </p> <input type="submit"> </form> </div> </div> </div> </body> </html>
views.py
# -*- encoding:utf-8 -*- from django.shortcuts import render,HttpResponse # Create your views here. from django import forms from django.forms import widgets ''' forms组件的参数配置 引入:from django.forms import widgets 修改类型:密码明文变成密文 widget=widgets.PasswordInput() 错误信息变成中文 error_messages={"required":"该字段不能为空","invalid":"邮箱格式错误"} 使用引入的bootstrap中的类名 widget=widgets.TextInput(attrs={"class":"form_control"} ''' class UserForm(forms.Form): name = forms.CharField(min_length=4, label="用户名",error_messages={"required":"该字段不能为空"}, widget=widgets.TextInput(attrs={"class":"form-control"}) ) pwd = forms.CharField(min_length=4, label="密码", widget=widgets.PasswordInput(attrs={"class":"form-control"}) ) r_pwd = forms.CharField(min_length=4, label="确认密码", widget=widgets.PasswordInput(attrs={"class": "form-control"}) ) email = forms.EmailField(label="邮箱",error_messages={"required":"该字段不能为空","invalid":"邮箱格式错误"}, widget=widgets.TextInput(attrs={"class": "form-control"}) ) tel = forms.CharField(label="电话", widget=widgets.TextInput(attrs={"class": "form-control"}) ) def reg(request): if request.method == "POST": # print(request.POST) form = UserForm(request.POST) # form表单的name属性值应该与forms组件字段名称一致 print(form.is_valid()) # 返回布尔值,判断是否合法 if form.is_valid(): print(form.cleaned_data) # {'email': '123@qq.com', 'pwd': '1234', 'tel': '124764656', 'name': 'mumu', 'r_pwd': '1234'} else: print(form.cleaned_data) # {'email': '123@qq.com'} # print(form.errors) # {"name":[......]} # print(type(form.errors)) # ErrorDict # # print(form.errors.get("name")) # print(type(form.errors.get("name"))) # ErrorList # # print(form.errors.get("name")[0]) # 取第一个 return render(request, "reg.html", locals()) # return HttpResponse("OK") form = UserForm() return render(request,"reg.html",locals())
五.Forms组件校验的局部钩子
views.py
# -*- encoding:utf-8 -*- from django.shortcuts import render,HttpResponse # Create your views here. from django import forms from django.forms import widgets from app01.models import UserInfo from django.core.exceptions import NON_FIELD_ERRORS, ValidationError class UserForm(forms.Form): name = forms.CharField(min_length=4, label="用户名",error_messages={"required":"该字段不能为空"}, widget=widgets.TextInput(attrs={"class":"form-control"}) ) pwd = forms.CharField(min_length=4, label="密码", widget=widgets.PasswordInput(attrs={"class":"form-control"}) ) r_pwd = forms.CharField(min_length=4, label="确认密码", widget=widgets.PasswordInput(attrs={"class": "form-control"}) ) email = forms.EmailField(label="邮箱",error_messages={"required":"该字段不能为空","invalid":"邮箱格式错误"}, widget=widgets.TextInput(attrs={"class": "form-control"}) ) tel = forms.CharField(label="电话", widget=widgets.TextInput(attrs={"class": "form-control"}) ) ''' 局部校验,定义一个函数 首先引入数据库:from app01.models import UserInfo 接着引入报错的:from django.core.exceptions import NON_FIELD_ERRORS, ValidationError ''' # 判断用户名是否存在 def clean_name(self): val = self.cleaned_data.get("name") # 拿到上边字段第一次校验 ret = UserInfo.objects.filter(name=val) # 过滤数据库中是否有值 第二次校验 if not ret: return val else: raise ValidationError("该用户已注册!") # 抛出异常信息错误 # 判断手机号是否合法 def clean_tel(self): val = self.cleaned_data.get("tel") if len(val) == 11: return val else: raise ValidationError("手机号码非11位!") # 判断确认密码是否一致: # def clean_r_pwd(self): # val = self.cleaned_data.get("r_pwd") # val2 = self.cleaned_data.get("pwd") # if val == val2: # return val # else: # raise ValidationError("输入密码不一致!") def reg(request): if request.method == "POST": # print(request.POST) form = UserForm(request.POST) # form表单的name属性值应该与forms组件字段名称一致 print(form.is_valid()) # 返回布尔值,判断是否合法 if form.is_valid(): print(form.cleaned_data) # {'email': '123@qq.com', 'pwd': '1234', 'tel': '124764656', 'name': 'mumu', 'r_pwd': '1234'} else: print(form.cleaned_data) # {'email': '123@qq.com'} # print(form.errors) # {"name":[......]} # print(type(form.errors)) # ErrorDict # # print(form.errors.get("name")) # print(type(form.errors.get("name"))) # ErrorList # # print(form.errors.get("name")[0]) # 取第一个 return render(request, "reg.html", locals()) # return HttpResponse("OK") form = UserForm() return render(request,"reg.html",locals())
源码
六.全局钩子
forms可单独放在一个py文件里边,解耦
myforms.py
# -*- coding:utf-8 -*- from django import forms from django.forms import widgets from app01.models import UserInfo from django.core.exceptions import NON_FIELD_ERRORS, ValidationError class UserForm(forms.Form): name = forms.CharField(min_length=4, label="用户名",error_messages={"required":"该字段不能为空"}, widget=widgets.TextInput(attrs={"class":"form-control"}) ) pwd = forms.CharField(min_length=4, label="密码", widget=widgets.PasswordInput(attrs={"class":"form-control"}) ) r_pwd = forms.CharField(min_length=4, label="确认密码", widget=widgets.PasswordInput(attrs={"class": "form-control"}) ) email = forms.EmailField(label="邮箱",error_messages={"required":"该字段不能为空","invalid":"邮箱格式错误"}, widget=widgets.TextInput(attrs={"class": "form-control"}) ) tel = forms.CharField(label="电话", widget=widgets.TextInput(attrs={"class": "form-control"}) ) ''' 局部校验,定义一个函数 首先引入数据库:from app01.models import UserInfo 接着引入报错的:from django.core.exceptions import NON_FIELD_ERRORS, ValidationError ''' # 判断用户名是否存在 def clean_name(self): val = self.cleaned_data.get("name") ret = UserInfo.objects.filter(name=val) # 过滤数据库中是否有值 if not ret: return val else: raise ValidationError("该用户已注册!") # 判断手机号是否合法 def clean_tel(self): val = self.cleaned_data.get("tel") if len(val) == 11: return val else: raise ValidationError("手机号码非11位!") # 判断确认密码是否一致: # def clean_r_pwd(self): # val = self.cleaned_data.get("r_pwd") # val2 = self.cleaned_data.get("pwd") # if val == val2: # return val # else: # raise ValidationError("输入密码不一致!") # 全局钩子 def clean(self): pwd = self.cleaned_data.get("pwd") r_pwd = self.cleaned_data.get("r_pwd") if pwd and r_pwd: # 若两次输入密码都不对的时候则不需要进行判断两者是否相等 if pwd == r_pwd: return self.cleaned_data else: raise ValidationError("两次密码不一致!") else: return self.cleaned_data
views.py
# -*- encoding:utf-8 -*- from django.shortcuts import render,HttpResponse from app01.myforms import * # Create your views here. def reg(request): if request.method == "POST": # print(request.POST) form = UserForm(request.POST) # form表单的name属性值应该与forms组件字段名称一致 print(form.is_valid()) # 返回布尔值,判断是否合法 if form.is_valid(): print(form.cleaned_data) # {'email': '123@qq.com', 'pwd': '1234', 'tel': '124764656', 'name': 'mumu', 'r_pwd': '1234'} else: print(form.cleaned_data) # {'email': '123@qq.com'} # print(form.errors) # {"name":[......]} # print(type(form.errors)) # ErrorDict # # print(form.errors.get("name")) # print(type(form.errors.get("name"))) # ErrorList # # print(form.errors.get("name")[0]) # 取第一个 # 全局钩子错误 # print("error",form.errors.get("__all__")[0]) errors = form.errors.get("__all__") # 不在这里直接获取[0]的原因是,当没有错误信息时候会报错
# 有name,email字段错了就写name,email,全局错误就写__all__;拿到errors交给模板 return render(request, "reg.html", locals()) # return HttpResponse("OK") form = UserForm() return render(request,"reg.html",locals())
reg.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <!-- 最新版本的 Bootstrap 核心 CSS 文件 --> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> </head> <body> # 加了bootstrap后优化的#} <div class="container"> <div class="row"> <div class="col-md-6 col-lg-offset-3"> <h3>forms组件渲染方式1</h3> {# 这种方式直接帮助我们保证form表单的属性值与forms组件字段名称一致#} <form action="" method="post"> {% csrf_token %} <p>{{ form.name.label }} {# 这里的文字(用户名)可以不写,可以调用form.name.label得到,此时是英文名称,如果想得到中文的,可以在views中的UserForm类name中添加一个label="用户名" #} {{ form.name }} <span class="pull-right text-danger">{{ form.name.errors.0 }}</span> {# 显示错误信息 pull-right:右对齐,text-danger:标红#} </p> <p>密码 {{ form.pwd }} <span class="pull-right text-danger">{{ form.pwd.errors.0 }}</span> </p> <p>确认密码 {{ form.r_pwd }} <span class="pull-right text-danger">{{ form.r_pwd.errors.0 }}</span><span class="pull-right text-danger">{{ errors.0 }}</span> {#全局错误信息传递到模板里边 #} </p> <p>邮箱 {{ form.email }} <span class="pull-right text-danger">{{ form.email.errors.0}}</span> </p> <p>电话 {{ form.tel }} <span class="pull-right text-danger">{{ form.tel.errors.0 }}</span> </p> <input type="submit"> </form> </div> </div> </div> </body> </html>