表单验证
验证
验证
简介
有很多时候,您需要验证来自表单或 JSON 请求的输入。采用某种形式的后端验证是明智的,因为它能保证您构建更为安全的应用程序。Masonite 提供了一种非常灵活和流畅的方法来验证这些数据。
验证是基于规则的,您可以向规则传递一个键或键列表。然后验证将使用所有规则并将它们应用到字典中进行验证。
您可以在这里看到 可用规则列表。
验证请求
验证传入的表单或 JSON 数据非常简单。您需要做的就是导入 Validator
类,解析它,并使用必要的规则方法。
在您的控制器方法中,整个代码片段如下所示:
from masonite.validation import Validator
def show(self, request: Request, validate: Validator):
"""
Incoming Input: {
'user': 'username123',
'email': 'user@example.com',
'terms': 'on'
}
"""
errors = request.validate(
validate.required(['user', 'email']),
validate.accepted('terms')
)
if errors:
request.session.flash('errors', errors)
return request.back()
这个验证的作用是“用户和电子邮件是必需的,而且必须接受条款。” (更多关于可用的规则和它们的含义,稍后再说。)
注意,您可以传入一个单独的值,也可以传入一个值的列表。
创建规则
有时您可能会遇到这样的情况:由于 Masonite 自带的规则无法满足您的需求,您需要创建一个新的规则,或者您有一个比较特殊的需求,您需要为它建立一个规则。
在这种情况下,您可以创建一个新规则。
规则命令
您可以很容易地通过命令创建一个新的规则模板。
$ craft rule equals_masonite
规则使用小写类名并没有特殊的原因。主要原因是当您最终将它作为方法使用时,如果您选择像下面那样将规则注册到验证类中,可以提高可读性。
这将在 app/rules/equals_masonite.py
文件中创建一个规则模板,如下所示:
class equals_masonite(BaseValidation):
"""一个规则名称验证类"""
def passes(self, attribute, key, dictionary):
"""该规则通过验证的标准
...
"""
return attribute
def message(self, key):
"""该规则验证失败时显示的信息
...
"""
return '{} is required'.format(key)
def negated_message(self, key):
"""当使用 'isnt()' 之类的否定规则时,显示该规则被否定的信息。
...
"""
return '{} is not required'.format(key)
构建我们的规则
我们的规则类需要 3 个方法,当您运行规则命令时可以看到它们:passes
、 message
和 negated_message
方法。
Passes 方法
passes 方法需要为该规则所传递的用例返回布尔值。
举个例子,如果您需要制定一个值总是等于 Masonite 的规则,那么您可以这样写:
def passes(self, attribute, key, dictionary):
"""该规则通过验证的标准
...
"""
return attribute == 'Masonite'
当验证这样的字典时:
{
'name': 'Masonite'
}
那么
attribute
代表值(Masonite
)key
代表字典的键(name
)dictionary
代表完整的字典,方便您后续做任何额外的检查。
Message 方法
message
方法需要返回一个字符串作为错误信息。如果您正在制定上述规则,那么我们的接下来的规则如下所示:
def passes(self, attribute, key, dictionary):
"""该规则通过验证的标准
...
"""
return attribute == 'Masonite'
def message(self, key):
return '{} must be equal to Masonite'.format(key)
Negated Message 方法
当这个规则被否定时,negated message
方法需要返回一条消息。一般作为 message
方法的否定声明。
def passes(self, attribute, key, dictionary):
"""该规则通过验证的标准
...
"""
return attribute == 'Masonite'
def message(self, key):
return '{} must be equal to Masonite'.format(key)
def negated_message(self, key):
return '{} must not be equal to Masonite'.format(key)
注册我们的规则
现在规则已经创建完毕,我们有两种方式来使用它,您可以任选其一。
导入我们的规则
我们可以直接导入到控制器方法中:
from masonite.validation import Validator
from app.rules.equals_masonite import equals_masonite
def show(self, request: Request, validate: Validator):
"""
Incoming Input: {
'user': 'username123',
'company': 'Masonite'
}
"""
valid = request.validate(
validate.required(['user', 'company']),
equals_masonite('company')
)
或者,我们可以注册规则,并像往常一样在验证器类中使用它。
注册规则
在任何服务提供者的引导方法中(最好是 wsgi=False 的服务提供者,以防止它在每次请求中运行),我们可以向验证器类注册我们的规则。
如果您还没有 Provider,我们可以专门为添加自定义规则创建一个:
$ craft provider RuleProvider
然后,在此规则提供程序的引导方法中,我们可以解析和注册我们的规则,看起来像:
from app.rules.equals_masonite import equals_masonite
from masonite.validation import Validator
class RuleProvider(ServiceProvider):
"""Provides Services To The Service Container
"""
wsgi = False
...
def boot(self, validator: Validator):
"""Boots services required by the container
"""
validator.register(equals_masonite)
现在,不用导入规则,我们照常可以使用它:
from masonite.validation import Validator
def show(self, request: Request, validate: Validator):
"""
Incoming Input: {
'user': 'username123',
'company': 'Masonite'
}
"""
valid = request.validate(
validate.required(['user', 'company']),
validate.equals_masonite('company')
)
请注意,我们一直在调用该方法,就好像它一直在验证类中一样。
为 Masonite 创建包含新规则的软件包时,注册规则特别有用。
使用 Validator 类
除了验证请求类,我们还可以直接使用验证器类。如果您需要验证自己的字典,这将很有用:
from masonite.validation import Validator
def show(self, validator: Validator):
"""
Incoming Input: {
'user': 'username123',
'company': 'Masonite'
}
"""
valid = validator.validate({
'user': 'username123',
'company': 'Masonite'
},
validate.required(['user', 'company']),
validate.equals_masonite('company')
)
只需将字典作为第一个参数,然后将每个规则作为自己的参数即可。
使用装饰器
Masonite 验证具有一个方便的装饰器,您可以在控制器方法上使用它。如果验证不正确,这将防止将控制器方法全部命中:
from masonite.validation.decorators import validate
from masonite.validation import required
@validate(required('name'))
def show(self, view: View):
return view.render(..)
这里将返回JSON作为response。你也可以使用重定向:
from masonite.validation.decorators import validate
from masonite.validation import required
@validate(required('name'), redirect='/login')
def show(self, view: View):
return view.render(..)
也可以回退到请求地址(如果在模板中使用了 {{ back() }}
模板辅助)
from masonite.validation.decorators import validate
from masonite.validation import required
@validate(required('name'), back=True)
def show(self, view: View):
return view.render(..)
所有这些重定向都会携带错误信息和输入信息。所以你可以在模板中使用
{{ old() }}
模板辅助来获取到之前输入内容。
规则集
规则集是自包含多个规则类。你可以使用它来重用的验证逻辑。例如,你在应用使用了多次相同的验证逻辑,那么你就可以使用规则集来复用代码了。
规则集命令
通过使用如下方式创建规则集:
$ craft rule:enclosure AcceptedTerms
文件生成至app/rules目录类似如下:
from masonite.validation import RuleEnclosure
...
class AcceptedTerms(RuleEnclosure):
def rules(self):
""" ... """
return [
# Rules go here
]
创建规则集
可以使用添加规则来填充该列表:
from masonite.validation import required, accepted
class LoginForm(RuleEnclosure):
def rules(self):
""" ... """
return [
required(['email', 'terms']),
accepted('terms')
]
使用如下方式来使用规则集:
from app.rules.LoginForm import AcceptedTerms
def show(self, request: Request):
"""
Incoming Input: {
'user': 'username123',
'email': 'user@example.com',
'terms': 'on'
}
"""
errors = request.validate(AcceptedTerms)
if errors:
request.session.flash('errors', errors)
return request.back()
也可以在这基础上附加其他的规则:
from app.rules.LoginForm import AcceptedTerms
from masonite.validations import email
def show(self, request: Request):
"""
Incoming Input: {
'user': 'username123',
'email': 'user@example.com',
'terms': 'on'
}
"""
errors = request.validate(
AcceptedTerms,
email('email')
)
if errors:
request.session.flash('errors', errors)
return request.back()
消息包
错误消息过多会造成字典结构过大。
因此,Masonite验证提供了MessageBag
类帮你包装错误消息。使用类似如下:
from masonite.validation import MessageBag
# ...
def show(self, request: Request):
errors = request.validate(
email('email')
)
errors = MessageBag(errors)
获取所有的错误消息
你可以很容易的使用all()
方法来获取所有错误消息:
errors = MessageBag(errors)
errors.all()
"""
{
'email': ['Your email is required'],
'name': ['Your name is required']
}
"""
检查是否有错误
errors = MessageBag(errors)
errors.any() #== True
检查错误信息是否为空
This is just the opposite of the any()
method.
errors = MessageBag(errors)
errors.empty() #== False
检查是否存在指定错误
errors = MessageBag(errors)
errors.has('email') #== True
获取第一个错误键值
errors = MessageBag(errors)
errors.all()
"""
{
'email': ['Your email is required'],
'name': ['Your name is required']
}
"""
errors.first()
"""
{
'email': ['Your email is required']
}
"""
获取错误条数
errors = MessageBag(errors)
errors.count() #== 2
转换为JSON
errors = MessageBag(errors)
errors.json()
"""
'{"email": ["Your email is required"],"name": ["Your name is required"]}'
"""
获取指定键条数
errors = MessageBag(errors)
errors.amount('email') #== 1
获取指定键错误提示
errors = MessageBag(errors)
errors.amount('email')
"""
['Your email is required']
"""
获取所有错误键
errors = MessageBag(errors)
errors.errors()
"""
['email', 'name']
"""
获取所有错误提示
errors = MessageBag(errors)
errors.messages()
"""
['Your email is required', 'Your name is required']
"""
合并字典
你还可以合并新的键值对错误到消息包中:
errors = MessageBag(errors)
errors.merge({'key': 'value'})
模板助手
你可以在模板助手的bag()
来获取错误列表。你HTML模板中你可以使用来类似如下操作:
@if(bag().any())
<div class="bg-yellow-200 text-yellow-800 px-4 py-2">
<ul>
@for message in bag().messages()
<li>{{ message }}</li>
@endfor
</ul>
</div>
@endif
这将会在列表中列出所有的错误。
嵌套验证
有时候你所验证的键可能不是在最顶层字典,类似下面的例子。这种情况使用点来定位到字典内部结构:
"""
{
'domain': 'http://google.com',
'email': 'user@example.com'
'user': {
'id': 1,
'email': 'user@example.com',
'status': {
'active': 1,
'banned': 0
}
}
}
"""
errors = request.validate(
validate.required('user.email'),
validate.truthy('user.status.active')
)
注意这里的点符号。每个.
意味着字典的下一级别位置。
带有列表的嵌套验证
有时候你的要验证的结构包含列表,你需要列表每个元素都满足验证规则。例如,你要验证的结构是包含街道和ID的列表。
这种情况你可以使用*符号验证:
"""
{
'domain': 'http://google.com',
'email': 'user@example.com'
'user': {
'id': 1,
'email': 'user@example.com',
'addresses': [{
'id': 1, 'street': 'A Street',
'id': 2, 'street': 'B Street'
}]
}
}
"""
以下是确保街道street是必填字段:
errors = request.validate(
validate.required('user.addresses.*.street'),
validate.integer('user.addresses.*.id'),
)
自定义消息
所有的错误消息均为通用消息。大部分时间你可能想返回更准确的错误内容。
每个规则都可以接收一个messages关键字参数可以用来自动错误消息。
"""
{
'terms': 'off',
'active': 'on',
}
"""
validate.accepted(['terms', 'active'], messages = {
'terms': 'You must check the terms box on the bottom',
'active': 'Make sure you are active'
})
现在将会返回你所提供的消息内容,而不是通用的错误消息。
未提供的自定义消息内容的键将依然返回通用消息内容。
异常
默认,Masonite在验证失败时并不会抛出异常。你可以在验证失败时手动抛出一个ValueError
异常:
"""
{
'domain': 'http://google.com',
'email': 'user@example.com'
'user': {
'id': 1,
'email': 'user@example.com',
'status': {
'active': 1,
'banned': 0
}
}
}
"""
errors = request.validate(
validate.required('user.email', raises=True),
validate.truthy('user.status.active')
)
现在如果必填规则验证失败将会抛出一个ValueError
异常。你可以用以下方式来捕获异常:
try:
errors = request.validate(
validate.required('user.email', raises=True),
validate.truthy('user.status.active')
)
except ValueError as e:
str(e) #== 'user.email is required'
自定义异常
你可以通过提供字典键配对的方式来指定要抛出的异常:
try:
errors = request.validate(
validate.required(['user.email', 'user.id'], raises={
'user.id': AttributeError,
'user.email': CustomException
}),
)
except AttributeError as e:
str(e) #== 'user.id is required'
except CustomException as e:
str(e) #== 'user.email is required'
其他未指明异常将在验证失败时会抛出ValueError
异常。
验证字符串
除了使用上面的验证方法外,你也可以通过使用管道符号隔开的方式在字符串中进行声明。例如以下是两种验证方式是相同的:
# Normal
errors = request.validate(
validate.required(['email', 'username', 'password', 'bio']),
validate.accepted('terms'),
validate.length('bio', min=5, max=50),
validate.strong('password')
)
# With Strings
errors = request.validate({
'email': 'required',
'username': 'required',
'password': 'required|strong',
'bio': 'required|length:5,50'
'terms': 'accepted'
})
这些规则是等效的,用户可以选择自己舒适的方式来实现。
规则列表
Accepted
accepted规则在检查checkbox是否选中时非常有用。当一个checkbox提交,它通常伴随值有on
,1或是yes。
"""
{
'terms': 'on'
}
"""
validate.accepted('terms')
Active_domain
它用来所传递的域名是可以被DNS解析。你也可以用它来验证邮箱地址。它首先搜索的domain.com。但是Masonite将会自动移除http://
, https://
和 www
。
"""
{
'domain': 'http://google.com',
'email': 'user@example.com'
}
"""
validate.active_domain(['domain', 'email'])
After_today
确认输入的日期是当前时间之后。
"""
{
'date': '2019-10-20', # Or date in the future
}
"""
validate.after_today('date')
也可以在验证时传入时区:
"""
{
'date': '2019-10-20', # Or date in the future
}
"""
validate.after_today('date', tz='America/New_York')
Before_today
用来验证当前日期的之前日期。
"""
{
'date': '2019-10-20', # Or date in the past
}
"""
validate.before_today('date')
同样可以传入时区:
"""
{
'date': '2019-10-20', # Or date in the past
}
"""
validate.before_today('date', tz='America/New_York')
Contains
它用来验证值(列表或者字符串)里包含某个值。例如你想验证字符串里是否包含Masonite子字符串:
"""
{
'description': 'Masonite is an amazing framework'
}
"""
validate.contains('description', 'Masonite')
Confirmed
该规则用来确认值的。是通过key_confirmation
来比对键的。
例如,你想确认password
那么你就需要对应一个键为password_confirmation
进行匹配。
"""
{
'password': 'secret',
'password_confirmation': 'secret'
}
"""
validate.confirmed('password')
Does_not
用来在使用一组规则不匹配情况下。同样还有一个then()
方法,两个可进行组合使用。
"""
{
'age': 15,
'email': 'user@email.com',
'terms': 'on'
}
"""
validate.does_not(
validate.exists('user')
).then(
validate.accepted('terms'),
)
Equals
用来检查必须是匹配的值
"""
{
'age': 25
}
"""
validate.equals('age', 25)
检查输入值是一个有效的邮箱地址
"""
{
'domain': 'http://google.com',
'email': 'user@example.com'
}
"""
validate.email('email')
Exists
检查输入存在该键
"""
{
'email': 'user@example.com',
'terms': 'on'
'age': 18
}
"""
validate.exists('terms')
在同when配合使用时非常有用:
"""
{
'email': 'user@example.com',
'terms': 'on'
'age': 18
}
"""
validate.when(
validate.exists('terms')
).then(
validate.greater_than('age', 18)
)
Greater_than
用来确保某个键大于指定的值。
"""
{
'age': 25
}
"""
validate.greater_than('age', 18)
In_range
检查值在某个区间。
"""
{
'attendees': 54
}
"""
validate.in_range('attendees', min=24, max=64)
Ip
检查输入是否有效的IPv4地址:
"""
{
'address': '78.281.291.8'
}
"""
validate.ip('address')
Is_in
检查某个值在指定列表值中
"""
{
'age': 5
}
"""
validate.is_in('age', [2,4,5])
注意这里列表包含了5这个值。
Isnt
用来否定规则。如果你需要相反的规则,那么你可以把其他规则放入。
例如你需要is_in
相反规则:
"""
{
'age': 5
}
"""
validate.isnt(
validate.is_in('age', [2,4,5])
)
这里会产生错误,因为age输入值在规则列表里所提供的值,而这里使用了 相反规则。
Is_future
检查传递的日期是否是在未来。就算是5分钟后也能验证通过。
"""
{
'date': '2019-10-20', # Or date in the future
}
"""
validate.is_future('date')
可以接收时间参数:
"""
{
'date': '2019-10-20', # Or date in the future
}
"""
validate.is_future('date', tz='America/New_York')
Is_past
检查日期是过去。就算是5分钟前也能验证通过。
"""
{
'date': '2019-10-20', # Or date in the future
}
"""
validate.is_past('date')
可以接收时区参数:
"""
{
'date': '2019-10-20', # Or date in the future
}
"""
validate.is_past('date', tz='America/New_York')
Json
验证输入值为JSON对象
"""
{
'user': 1,
'payload': '[{"email": "user@email.com"}]'
}
"""
validate.json('payload')
Length
指定输入字符串的长度
"""
{
'user': 1,
'description': 'this is a long description'
}
"""
validate.length('description', min=5, max=35)
Less_than
指定输入值需要小于某值
"""
{
'age': 25
}
"""
validate.less_than('age', 18)
None
确认值为None
"""
{
'age': 25,
'active': None
}
"""
validate.none('active')
Numeric
确保输入值为数值
"""
{
'age': 25,
'active': None
}
"""
validate.numeric('age')
One_of
有时你只需要几个字段仅一个所必填。
"""
{
'user': 'Joe',
'email': 'user@example.com,
'phone': '123-456-7890'
}
"""
validate.one_of(['user', 'accepted', 'location'])
这里会通过,因为存在user
键
Phone
检查有效的手机号码各式:
"""
{
'phone': '876-827-9271'
}
"""
validate.phone('phone', pattern='123-456-7890')
可选模式有:
123-456-7890
(123)456-7890
Required
检查字典中存在该键,如果不存在就生成错误信息
"""
{
'age': 25,
'email': 'user@email.com'
}
"""
validate.required(['age', 'email'])
String
确保输入值为字符串
"""
{
'age': 25,
'email': 'user@email.com'
}
"""
validate.string('email')
Strong
strong用来确保字符串输入是"强"字符串。
它在密码中非常有用。当确保输入密码必须至少8个字符串,存在至少2个大写字母以及2个特殊字符。
"""
{
'email': 'user@email.com'
'password': 'SeCreT!!'
}
"""
validate.strong('password', length=8, special=2, uppercase=3)
Timezone
检查输入是有效的时区
"""
{
'timezone': 'America/New_York'
}
"""
validate.timezone('timezone')
Truthy
确保输入是一个真值。
"""
{
'active': 1,
'email': 'user@email.com'
}
"""
validate.truthy('active')
When
条件规则。这在你使用组合规则时,例如只有当前置规则生效才验证后置规则时很有用。
例如你想验证输入年龄18岁才验证是否接受了条款。
"""
{
'age': 15,
'email': 'user@email.com',
'terms': 'on'
}
"""
validate.when(
validate.less_than('age', 18)
).then(
validate.required('terms'),
validate.accepted('terms')
)
本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。