我们要想使django中的表单验证前端传入的数据是否正确,我们需要用到Django中的forms。
常用的Field:
使用Field可以是对数据验证的第一步。你期望这个提交上来的数据是什么类型,那么就使用什么类型的Field。
1. CharField:
用来接收文本。
参数:
- max_length:这个字段值的最大长度。
- min_length:这个字段值的最小长度。
- required:如果没有写这个参数,即默认required=True,即这个字段不能为空。
- error_messages:在某个条件验证失败的时候,给出错误信息。
2. EmailField:
用来接收邮件,会自动验证邮件是否合法。
3. FloatField:
用来接收浮点类型,并且如果验证通过后,会将这个字段的值转换为浮点类型。
参数:
- max_value:最大的值。
- min_value:最小的值。
错误信息的key:required、invalid、max_value、min_value。
4. IntegerField:
用来接收整形,并且验证通过后,会将这个字段的值转换为整形。
参数:
- max_value:最大的值。
- min_value:最小的值。
5. URLField:
用来接收url格式的字符串。
字段中的error_messages
中的参数字段:
- required:传入的字段不能为空。
- invalid:字段不合法。
拥有最大长度和最小长度的才有下面两个错误信息
- min_length:少于设置的最短长度。
- max_length:大于设置的最大长度。
拥有最大值和最小值的才有下面两个错误信息
- max_value:超过最大值。
- min_value:没有达到最小值。
新建一个app,然后在app下面新建一个forms.py
的文件,在里面定义一个类,然后继承至djano.froms.Form
,然后在里面定义字段。
例如,我们定义一个用户名
,一个邮箱
,一个价格
,一个网址
。
forms.py中写入:
class MyForm(forms.Form):
name = forms.CharField(max_length=100,min_length=3,error_messages={'required':'用户名不能为空','min_length':'最少不能少于3个字符','max_length':'最多不能超过100个字符'})
email = forms.EmailField(error_messages={'invalid':'请输入正确的邮箱!','required':'邮箱不能为空'})
price = forms.FloatField(error_messages={'invalid':'请输入正确的数字','required':'价格不能为空'})
url = forms.URLField(error_messages={'invalid':'请输入正确的url~~','required':'url不能为空'})
error_messages
是可选参数,如果没有设置这个参数,当前端传入的数据不合法时,显示的错误信息默认时英文的,我们设置了error_messages参数,我们就能呢个自定义我们想要显示的错误信息了。
views中定义一个类视图
from django.http import HttpResponse
from django.shortcuts import render
from django.views.generic import View
from .forms import MyForm
class FormView(View):
def get(self,request):
return render(request,'form.html')
def post(self,request):
form = MyForm(request.POST)
if form.is_valid():
return HttpResponse('succrss')
else:
print(form.errors.get_json_data())
return HttpResponse('fail')
如果使用get请求访问这个类视图,就返回一个html页面,如果使用post请求:
对数据进行判断:
先使用我们定义的MyForm类接收前端传入的数据,然后赋值给form,再使用form的is_valid
方法对数据进行判断是否合法。如果传入合法,就返回一个success,否则返回fail,并且在控制台打印错误信息。
因为我们上面渲染了一个模板,所以我们需要先去定义一个form.html模板
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="" method="post">
<label>用户名:</label>
<input type="text" name="name"><br>
<label>邮箱:</label>
<input type="text" name="email"><br>
<label>价格:</label>
<input type="text" name="price"><br>
<label>网址:</label>
<input type="text" name="url"><br>
<input type="submit" value="提交"><br>
</form>
</body>
</html>
然后添加映射,输入数据就能查看对应的效果了,可以自行尝试输入不符合格式的数据。
这样我们就能对一些常用字段进行判断输入的数据是否合法了。
常用验证器:
在验证某个字段的时候,可以传递一个validators参数用来指定验证器,进一步对数据进行过滤。验证器有很多,但是很多验证器我们其实已经通过这个Field或者一些参数就可以指定了。比如EmailValidator,我们定义的EmailFiled这个字段在底层就会使用这个验证器来对数据进行验证,比如MaxValueValidator,定义了max_length之后在底层也会使用这个验证器进行验证。以下是一些常用的验证器:
- MaxValueValidator:验证最大值。
- MinValueValidator:验证最小值。
- MinLengthValidator:验证最小长度。
- MaxLengthValidator:验证最大长度。
- EmailValidator:验证是否是邮箱格式。
- URLValidator:验证是否是URL格式。
- RegexValidator:如果还需要更加复杂的验证,那么我们可以通过正则表达式的验证器:RegexValidator。
例如,我们来定义一个CharField字段,再来使用EmailValidator验证器实现对邮箱格式的验证。
from django.core import validators
test_email = forms.CharField(validators=[validators.EmailValidator(message='请输入正确的邮箱格式')])
将上面的代码添加至MyForm中,然后在前端新增加一个input
<label>test_email:</label>
<input type="text" name="test_email"><br>
就能够进行测试了,这里只是做一个验证器的演示,对email进行验证的时候还是用EmailFiled字段比较方便。
重点是在RegexValidator验证器上面,接下在我们使用正则表达式验证器对手机好嘛进行验证:
MyForm中添加字段
# 以1开头,接下来是3,4,5,6,7,8,然后再是9位数字
telephone = forms.CharField(validators=[validators.RegexValidator(r'1[345678]\d{9}',message='请输入正确的手机号吗')])
form.html中添加标签:
<label>电话号码:</label>
<input type="text" name="telephone"><br>
这样,就完成了对手机号码的验证了。
自定义验证:
有时候对一个字段验证,不是一个长度,一个正则表达式能够写清楚的,还需要一些其他复杂的逻辑,那么我们可以对某个字段,进行自定义的验证。比如在注册的表单验证中,我们想要验证手机号码是否已经被注册过了,那么这时候就需要在数据库中进行判断才知道。对某个字段进行自定义的验证方式是,定义一个方法,这个方法的名字定义规则是:clean_fieldname。如果验证失败,那么就抛出一个验证错误。
例如:
在写注册页面的时候,要验证用户表中手机号码之前是否在数据库中存在。
首先在当前app下创建一个models
from django.db import models
# Create your models here.
class User(models.Model):
name = models.CharField(max_length=100)
# 在数据库中,电话号码不能重复
telephone = models.CharField(max_length=11,unique=True)
然后将此app添加值settings中,配置settings,连接至数据库,然后执行makemigrations,migrate。
就创建好了一个user表。
新建一个register.html的文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>注册</title>
</head>
<body>
<form action="" method="post">
<table>
<tbody>
<tr>
<td>用户名:</td>
<td><input type="text" name="username"></td>
</tr>
<tr>
<td>电话号码:</td>
<td><input type="text" name="telephone"></td>
</tr>
<tr>
<td></td>
<td><input type="submit" value="提交"></td>
</tr>
</tbody>
</table>
</form>
</body>
</html>
然后在forms.py中定义一个RegisterForm类
from . import models
class RegisterForm(forms.Form):
username = forms.CharField(max_length=100,min_length=6,error_messages={'required':'用户名不能为空','min_length':'最少不能少于6个字符','max_length':'最多不能超过100个字符'})
telephone = forms.CharField(validators=[validators.RegexValidator(r'1[345678]\d{9}',message='请输入正确的手机号吗')])
# 在进行对telephone验证的时候就会自动调用这个方法
# 这个函数名字不能随便取,只能是 `clean_需要附件验证的字段名字`。
def clean_telephone(self):
telephone = self.cleaned_data.get('telephone')
exists = models.User.objects.filter(telephone=telephone).exists()
if exists:
# 验证失败抛出的异常
raise forms.ValidationError(message='此手机号码已经被注册')
# 如果验证没有问题,一定要记得将电话好嘛返回回去
return telephone
然后在views中定义一个类视图:
from . import models
from .forms import MessageBoardForm,MyForm,RegisterForm
class RegisterView(View):
def get(self,request):
return render(request,'register.html')
def post(self,request):
form = RegisterForm(request.POST)
if form.is_valid():
username = form.cleaned_data.get('username')
telephone = form.cleaned_data.get('telephone')
user = models.User.objects.create(name=username,telephone=telephone)
return HttpResponse('注册成功')
else:
print(form.errors.get_json_data())
return HttpResponse('注册失败')
然后添加映射
path('register/',views.RegisterView.as_view()),
这样,我们就对telephone这个字段进行了深度验证了,即判断当前注册的电话号码是否已经被注册了。
这里只是使用clean_telephone实现了对一个字段进行深入判断,那么如果我们向对多个数据进行判断呢?
这个时候我们就需要重写父类的clean
方法了
需求:在注册的时候需要输入两次密码,如果前后两次密码输入不成功,那么也不能成功的进行注册
首先完善前端页面,register.html
中在提交按的<tr>
标签上面添加两个<tr>
标签:
<tr>
<td>密码:</td>
<td><input type="password" name="pwd1"></td>
</tr>
<tr>
<td>确认密码:</td>
<td><input type="password" name="pwd2"></td>
</tr>
然后完善forms.py中的注册表单RegisterForm
class RegisterForm(forms.Form):
username = forms.CharField(max_length=100,min_length=6,error_messages={'required':'用户名不能为空','min_length':'最少不能少于6个字符','max_length':'最多不能超过100个字符'})
telephone = forms.CharField(validators=[validators.RegexValidator(r'1[345678]\d{9}',message='请输入正确的手机号吗')])
pwd1 = forms.CharField(max_length=16,min_length=6,error_messages={'required':'用户名不能为空','min_length':'最少不能少于6个字符','max_length':'最多不能超过16个字符'}) # 第一次输入的密码
pwd2 = forms.CharField(max_length=16,min_length=6,error_messages={'required':'用户名不能为空','min_length':'最少不能少于6个字符','max_length':'最多不能超过16个字符'}) # 第二次输入的密码
# 在进行对telephone验证的时候就会自动调用这个方法
def clean_telephone(self):
telephone = self.cleaned_data.get('telephone')
exists = models.User.objects.filter(telephone=telephone).exists()
if exists:
# 验证失败抛出的异常
raise forms.ValidationError(message='此手机号码已经被注册')
# 如果验证没有问题,一定要记得将电话好嘛返回回去
return telephone
# 对多个字段进行验证,重写clean方法
# 能来到这个方法,说明前面的所有字段都验证成功了的
def clean(self):
cleaned_data = super().clean()
pwd1 = cleaned_data.get('pwd1')
pwd2 = cleaned_data.get('pwd2')
if pwd1 != pwd2:
raise forms.ValidationError(message='两次输入的密码不一致')
return cleaned_data
在上面我们就重写了父类的clean方法,然后进行判断,如果没有问题,再将所有数据返回回去。
然后我们修改我们的models.py,将pwd这个字段添加进去。
from django.db import models
class User(models.Model):
name = models.CharField(max_length=100)
telephone = models.CharField(max_length=11)
pwd = models.CharField(max_length=20,null=True)
然后执行makemigrations,migrate。将pwd字段添加至表中。
最后,修改views中的RegisterView
视图,将验证过的数据存入数据库中。
只需要修改RegisterView
视图中的post
方法中的if
判断条件中的代码
if form.is_valid():
username = form.cleaned_data.get('username')
telephone = form.cleaned_data.get('telephone')
pwd = form.cleaned_data.get('pwd1')
user = models.User.objects.create(name=username,telephone=telephone,pwd=pwd)
return HttpResponse('注册成功')
这样,就实现了对两次密码输入的一致性进行了判断。
上面的代码中,我们只是将错误信息在后台打印了出来,但是我们需要将我们自定义的错误信息展示到前端去,让用户知道哪里错了,所以首先我们的先拿到错误信息。
我们可以在form.py
中的RegisterForm
中定义一个方法,然后我们需要数据的时候就直接调用这个方法就是了。
def get_errors(self):
# 通过self.errors.get_json_data()获取错误的信息
errors = self.errors.get_json_data()
# 新建一个错误信息的字典,存储错误信息的字段和对应的错误信息
new_errors = {}
# 遍历获取的错误信息字典
for key,message_dicts in errors.items():
# 定义一个错误信息的列表,因为一个字段可能出现了多个错误,
# 所以我们用一个列表将错误某个字段的所有错误信息存储起来
messages = []
# 遍历所有的错误信息,然后逐个添加至定义的messages列表中
for message in message_dicts:
message = message['message']
messages.append(message)
# 将获取的所有错误信息添加至自定义的字典中,并使用获取的key来对应
new_errors[key] = messages
# 返回自定义错误信息的字典
return new_errors
这样,我们就定义好了一个获取错误信息的函数,可以对照者前面我们打印出来的错误信息来理解这段代码。
然后,在views
中的post
方法中的else
中修改代码
else:
print(form.get_errors())
# print(form.errors.get_json_data())
return HttpResponse('注册失败')
然后我们就能在控制台看到我们自定义的错误信息了。
然后我们只需要用一个变量接收错误信息,然后就能使用render返回给前端了
else:
errors = form.get_errors()
print(errors)
return render(request,'register.html',{'errors':errors})
这样,我们就实现了一个较为完整的注册页面的后台了。
使用modelForm简化操作
大家在写表单的时候,会发现表单中的Field和模型中的Field基本上是一模一样的,而且表单中需要验证的数据,也就是我们模型中需要保存的。那么这时候我们就可以将模型中的字段和表单中的字段进行绑定。
在models中定义一个Book模型:
class Book(models.Model):
name = models.CharField(max_length=100)
page = models.IntegerField()
price = models.FloatField()
然后makemigrations,后在migrate。
由上面的知识可知,要使用表单验证数据,我们需要在forms中新建一个表单类,所以我们在forms.py中新建一个类:
from . import models
from django import forms
class AddBookForm(forms.ModelForm):
class Meta:
# 指定映射的模型
model = models.Book
# 指定模型中需要验证的字段,__all__表示全部
fields = '__all__'
# 指定只验证name和page字段
# fields = ['name','page']
# 指定不验证price字段
# exclude = ['price']
这样,我们就定义好一个表单验证器。
注意: fields属性和exclude属性有且只能有一个存在。
然后我们就需要在views中编写视图函数来接收数据了,在views中新建一个类视图:
from .forms import AddBookForm
from django.views.generic import View
class add_book(View):
def get(self,request):
return render(request,'add_book.html')
def post(self,request):
form = AddBookForm(request.POST)
if form.is_valid():
name = form.cleaned_data.get('name')
page = form.cleaned_data.get('page')
price = form.cleaned_data.get('price')
print(name,page,price)
return HttpResponse('success')
else:
print(form.errors.get_json_data())
return HttpResponse('Fail')
然后新建一个html文件add_book.html
,在body中写入一下代码:
<form action="" method="post">
<label>书名:</label>
<input type="text" name="name"><br>
<label>页数:</label>
<input type="text" name="page"><br>
<label>价格:</label>
<input type="text" name="price"><br>
<input type="submit" value="提交">
</form>
然后在urls中添加映射:
path('add_book/',views.add_book.as_view()),
然后输入网址即可进行测试了。
当我们输入错误的信息时,在后台打印出的信息时英文的,所以这个时候我们可以和上面一样,自己设置错误信息。
在我们刚才定义的表单验证器中修改代码:
class AddBookForm(forms.ModelForm):
# 对页数进行深入判断,这里只是演示一下clean_<name>方法,我们要实现这个需求可以直接使用验证器
def clean_page(self):
page = self.cleaned_data.get('page')
if page > 100:
raise forms.ValidationError('页数不能大于100页')
return page
class Meta:
# 指定映射的模型
model = models.Book
# 指定模型中需要验证的字段,__all__表示全部
fields = '__all__'
# fields = ['name','page']
# exclude = ['price']
# 错误信息,外层字典中的key是对应的字段名,value时字段名对应的错误信息的内层字典,
# 内层字典的key时对应错误类型,value对应错误信息,即发生相应错误时需要显示的信息。
error_messages = {
'page': {
'required':'页数不能为空!',
'invalid': '请输入一个可用的page参数'
},
'title':{
'max_length':'name不能超过100个字符',
},
'price':{
'max_value':'图书价格不能超过1000元',
}
}
这样我们就自定义好了我们的自定义返回的错误信息。
在views中的视图函数中,我们想要将数据存储到数据库中去,是不是需要将所有的数据都取出来,然后在调用模型的save方法或者create方法才能存储进去。如果我们使用的表单验证器,那么我们就不需要要将数据取出来在存入数据库了,直接调用表单的save方法就可以了,示例:修改视图中的代码:
class add_book(View):
def get(self,request):
return render(request,'add_book.html')
def post(self,request):
form = AddBookForm(request.POST)
if form.is_valid():
# name = form.cleaned_data.get('name')
# page = form.cleaned_data.get('page')
# price = form.cleaned_data.get('price')
# print(name,page,price)
form.save()
return HttpResponse('success')
else:
print(form.errors.get_json_data())
return HttpResponse('Fail')
==注意: == 使用save方法表单中的fields属性必须为
‘__all __’,否则会报错。
那么如果我们在表单中只选取了某些字段,那么我们应该怎样操作呢?
这个时候我们就可以在save中传入一个参数,commit=false
例如,当我们在实现注册页面的时候,前端需要传入两个密码pwd1和pwd2,只有两个密码相等时才可以进行成功注册。
那么我们这个时候使用form.save()方法肯定是会报错的。
这个时候我们就需要使用到commit=False
参数了。
示例,为了和上面的RegisterForm这个验证器视图形成对比,我们在forms中新建一个RegisterModelForm的验证器视图:
class RegisterModelForm(forms.ModelForm):
# 因为在Meta中我们只有name和telephone属性,所以我们需要自定义两个pwd属性来接收前端传入的值
pwd1 = forms.CharField(max_length=16, min_length=6,error_messages={'required': '密码不能为空', 'min_length': '密码最少不能少于6个字符','max_length': '密码最多不能超过16个字符'}) # 第一次输入的密码
pwd2 = forms.CharField(max_length=16, min_length=6,error_messages={'required': '确认密码不能为空', 'min_length': '密码最少不能少于6个字符','max_length': '密码最多不能超过16个字符'}) # 第二次输入的密码
def clean(self):
cleaned_data = super().clean()
pwd1 = cleaned_data.get('pwd1')
pwd2 = cleaned_data.get('pwd2')
if pwd1 != pwd2:
raise forms.ValidationError('两次输入密码不一致~~')
return cleaned_data
class Meta:
model = models.User
fields = ['name','telephone']
error_messages = {
'name': {
'required': 'username不能为空!',
},
'telephone': {
'required': 'telephone不能为空!',
'invalid': 'telephone必须为11个数字',
},
}
然后views中也是一样,我们定义一个RegisterModelFormView的视图,能和前面的RegisterView形成对比,
from .forms import MessageBoardForm,MyForm,RegisterForm,AddBookForm,RegisterModelForm
class RegisterModelFormView(View):
def get(self,request):
return render(request,'register.html')
def post(self,request):
form = RegisterModelForm(request.POST)
if form.is_valid():
user = form.save(commit=False)
user.pwd = form.cleaned_data.get('pwd1')
user.save()
return HttpResponse('success')
else:
print(form.errors.get_json_data())
return HttpResponse('Fail')
这里我们渲染的还时前面RegisterView中的Html模板,但是在前面我们定义的name是用username来传入数据的,但是我们现在是使用的form.Models在表单验证器中自动生成的字段,而在user模型中的字段为name,所以表单验证器中的字段也为name,所以我们是获取不到name的数据的,所以我们需要修改一下前端的代码
<form action="" method="post">
<table>
<tbody>
<tr>
<td>用户名:</td>
{# 这是RegisterForm使用的#}
{# <td><input type="text" name="username"></td>#}
{# 这是RegisterModelForm使用的#}
<td><input type="text" name="name"></td>
{% if errors.username %}
<td><p>{{ errors.username }}</p></td>
{% endif %}
</tr>
<tr>
<td>电话号码:</td>
<td><input type="text" name="telephone"></td>
{% if errors.telephone %}
<td><p>{{ errors.telephone }}</p></td>
{% endif %}
</tr>
<tr>
<td>密码:</td>
<td><input type="password" name="pwd1"></td>
{% if errors.pwd1 %}
<td><p>{{ errors.pwd1 }}</p></td>
{% endif %}
</tr>
<tr>
<td>确认密码:</td>
<td><input type="password" name="pwd2"></td>
{% if errors.pwd2 %}
<td><p>{{ errors.pwd2 }}</p></td>
{% endif %}
</tr>
<tr>
<td></td>
<td><input type="submit" value="提交"></td>
</tr>
</tbody>
</table>
</form>
然后在urls中添加映射,就实现了我们的需求。
注意: 在调用save方法的时候,如果传入一个commit=False,那么只会生成这个模型的对象,而不会把这个对象真正的插入到数据库中。比如上面一样。