请求验证
有许多来自表单和json请求需要进行验证。一些后台验证的方法可以让你构建更加安全的应用。 Masonite 提供了一些非常灵活和流畅的方法验证这些数据。
当你传递一个或者多个规则的关键字,会取得基于这些规则的验证器。验证器将会使用相应的规则,并直接进行验证。
提示:你可以查看 可用的规则.
验证请求
对来自于表单或者 JSON 数据进行验证非常简单。你需要做的是引入 Validator
类,引入它,然后使用必需的规则方法。
下面的控制器方法会展示整个流程:
from masonite.validation import Validator
from masonite.request import Request
from masonite.response import Response
def show(self, request: Request, response: Response, validate: Validator):
"""
传入进来的数据如下:
Incoming Input: {
'user': 'username123',
'email': 'user@example.com',
'terms': 'on'
}
"""
errors = request.validate(
validate.required(['user', 'email']),
validate.accepted('terms')
)
if errors:
return response.back().with_errors(errors)
这个验证将会 「请求一定含有 user 和 email , terms 的选项是否点上」(其它可用的规则与此类似)
提示:注意,你可以传入单个或者多个值
在视图中显示错误
你可以像这样方便地显示验证错误:
@if session().has('errors'):
<div class="bg-yellow-400">
<div class="bg-yellow-200 text-yellow-800 px-4 py-2">
<ul>
@for key, error_list in session().get('errors').items():
@for error in error_list
<li>{{ error }}</li>
@endfor
@endfor
</ul>
</div>
</div>
@endif
如果你想定制处理错误的方法,你需要添加 ShareErrorsInSessionMiddleware
中间件到你的路由中。 errors
将会作为 MessageBag 实例注入到视图中,让你更方便地处理错误:
@if errors.any():
<div class="bg-yellow-400">
<div class="bg-yellow-200 text-yellow-800 px-4 py-2">
<ul>
@for key, message in errors.all().items()
<li>{{ message }}</li>
@endfor
</ul>
</div>
</div>
@endif
<form method="post" action="/contact">
{{ csrf_field }}
<div>
<label for="name">Name</label>
<input type="text" name="name" placeholder="Name">
@if errors.has('name')
<span>{{ errors.get('name')[0] }}</span>
@endif
</div>
<div>
<label for="email">Email</label>
<input type="email" name="email" placeholder="Email">
@if errors.has('email')
<span>{{ errors.get('email')[0] }}</span>
@endif
</div>
<div>
<label for="message">Message</label>
<textarea name="message" placeholder="Message"></textarea>
@if errors.has('message')
<span>{{ errors.get('message')[0] }}</span>
@endif
</div>
<button type="submit">Send</button>
</form>
创建一个规则
有时候,你可能会花费时间去建立新规则,因为 Masonite 不包含你需要的规则,或者自带的规则并不合适。
接下来,你可以创建新规则。
规则命令
运行以下命令,可以轻松地创建新规则的模板:
终端
$ python 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 方法需要返回一个信息。简单地说,就是反向的 message
方法:
def passes(self, attribute, key, dictionary):
"""The passing criteria for this rule.
...
"""
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')
)
或者像普通验证器的类那样,注册和使用它。
注册规则
在任何的服务提供者的 boot 方法(最好就是,每个请求都禁止 wsgi=False
的提供者 )。可以在验证器类中,注册我们的规则。
如果你还没有提供者,可以自己创建一个,然后添加自定义的规则:
终端
$ python craft provider RuleProvider
在规则提供者的 boot 方法中,可以引入和注册我们的规则,就像这样:
from app.rules.equals_masonite import equals_masonite
from masonite.validation import Validator
class RuleProvider(ServiceProvider):
"""Provides Services To The Service Container
"""
def __init__(self, application):
self.application = application
def register(self, validator: Validator):
"""Boots services required by the container
"""
self.application.make('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 包,包含新规则时,注册规则是非常有用。
使用验证器类
为了验证请求类,也可以直接使用验证器类。如果你需要验证指定的字典,这是非常有用:
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')
)
只要把字典作为第一个参数,需要的规则作为其余的参数。
规则附件
规则附件是规则的自包装类。可以使用它们帮助重用验证逻辑。例如,如果你发现经常使用相同的规则,可以使用一个附件,把这些规则放在一起,然后在代码中重用它们。
规则附件指令
可以运行下面的指令创建规则附件:
$ python 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 masonite.request import Request
from masonite.response import Response
from app.rules.LoginForm import AcceptedTerms
def show(self, request: Request, response: Response):
"""
Incoming Input: {
'user': 'username123',
'email': 'user@example.com',
'terms': 'on'
}
"""
errors = request.validate(AcceptedTerms)
if errors:
request.session.flash('errors', errors)
return response.back()
也可以在此基础上添加其它规则:
from app.rules.LoginForm import AcceptedTerms
from masonite.validations import email
from masonite.request import Request
from masonite.response import Response
def show(self, request: Request, response: Response):
"""
Incoming Input: {
'user': 'username123',
'email': 'user@example.com',
'terms': 'on'
}
"""
errors = request.validate(
AcceptedTerms,
email('email')
)
if errors:
return response.back().with_errors(errors)
信息包
如果有许多需要专门指定的错误信息,会形成一个很大的字典。
因此, Masonite 的验证推出了 MessageBag
类,可以把你定制的错误信息包含进来。就像这样:
from masonite.validation import MessageBag
# ...
def show(self, request: Request):
errors = request.validate(
email('email')
) #== <masonite.validation.MessageBag>
取得包中所有错误信息:
可以简单地使用 all()
方法取得包中所有错误信息:
errors.all()
"""
{
'email': ['Your email is required'],
'name': ['Your name is required']
}
"""
检查包中是否有错误信息
errors.any() #== True
查看包是不是空的
这就是 any()
方法的反值。
errors.empty() #== False
查看是否有指定的错误信息:
errors.has('email') #== True
取得第一个错误信息:
errors.all()
"""
{
'email': ['Your email is required'],
'name': ['Your name is required']
}
"""
errors.first()
"""
{
'email': ['Your email is required']
}
"""
取得错误信息的数量:
errors.count() #== 2
把错误信息的格式转换成 JSON :
errors.json()
"""
'{"email": ["Your email is required"],"name": ["Your name is required"]}'
"""
取得指定错误信息的数量:
errors.amount('email') #== 1
取得指定的错误信息:
errors.get('email')
"""
['Your email is required']
"""
取得所有错误
errors.errors()
"""
['email', 'name']
"""
取得所有信息:
errors.messages()
"""
['Your email is required', 'Your name is required']
"""
添加字典
你也可以以字典的形式添加错误信息到包中:
errors.merge({'key': 'value'})
验证下层信息
有时候,你需要检查的值并不在字典的第一层。可以使用 . 操作符进入更深层:
"""
{
'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 遇到验证失败时,并不会抛出异常。当验证失败时,你可以强制 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
抛出清晰的异常错误。
验证字符串
除了可以像之前那样使用,你也可以使用 pipe 风格分割的字符串。例如,下面两个验证是相等的:
# 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
当检查一个 checkbox 是否选上时, accepted 规则是非常有用的。当一个 checkbox 选上时,通常会有一个值 on
,因此,这个规则将会检查,确保值为 on , 1 或者 yes 。
"""
{
'terms': 'on'
}
"""
validate.accepted('terms')
Active_domain
这个用于验证传过来的域名能否被 DNS 解析。你也可以传入 email 地址。推荐搜索的格式是 domain.com 但 Masonite 将会自动去除 http://
, https://
和 www
。
"""
{
'domain': 'http://google.com',
'email': 'user@example.com'
}
"""
validate.active_domain(['domain', 'email'])
After_today
确保日期是在今天之后的日期。在这个例子,确保日期在 2019-10-21 或者之后。
"""
{
'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
确保日期是在今天之前的日期。在这个例子,确保日期在 2019-10-19 或者之前。
"""
{
'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')
Confirmed
这个规则用于验证一个键是 「确认过」 的。使用 key_confirmation
方便地表示键。
例如,如果你需要确认 password
,就需要设置 password_confirmation
。
"""
{
'password': 'secret',
'password_confirmation': 'secret'
}
"""
validate.confirmed('password')
Contains
验证值存在于迭代中(像列表或者字符串) 。下面的例子检查字符串是否包含 Masonite :
"""
{
'description': 'Masonite is an amazing framework'
}
"""
validate.contains('description', 'Masonite')
Date
验证值是一个有效的日期。 Pendulum 模块进行验证。它支持 RFC 3339 格式, 大部分的 ISO 8601 格式和其它通用格式。
"""
{
'date': '1975-05-21T22:00:00'
}
"""
validate.date('date')
Different
用于检查值与另外字段的值的区别。 matches 相反的验证规则。
"""
{
'first_name': 'Sam',
'last_name': 'Gamji'
}
"""
validate.different('first_name', 'last_name')
Distinct
用于检查一个数组是否包含详细的内容。
"""
{
'users': ['mark', 'joe', 'joe']
}
"""
validate.distinct('users') # would fail
"""
{
'users': [
{
'id': 1,
'name': 'joe'
},
{
'id': 2,
'name': 'mark'
},
]
}
"""
validate.distinct('users.*.id') # would pass
Does_not
当规则不通过时,运行另外的规则。包含一个 then()
方法。可以视为 when 的相反。
"""
{
'age': 15,
'email': 'user@email.com',
'terms': 'on'
}
"""
validate.does_not(
validate.exists('user')
).then(
validate.accepted('terms'),
)
验证值是否为 email 地址。
"""
{
'domain': 'http://google.com',
'email': 'user@example.com'
}
"""
validate.email('email')
Equals
用于确保字典里的值等于指定的值。
"""
{
'age': 25
}
"""
validate.equals('age', 25)
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)
)
File
用于检查值是否有效文件。
"""
{
'document': '/my/doc.pdf'
}
"""
validate.file('document')
可以附加不同表示文件大小的格式,检查文件尺寸:
validate.file('document', 1024) # 检查是否有效文件和最大尺寸是 1K ( 1024 bytes )
validate.file('document', '1K') # 检查是否有效文件和最大尺寸是 1K ( 1024 bytes ),1k 或者 1KB 都可以
validate.file('document', '15M') # 检查是否有效文件和最大尺寸是15M
文件类型可以通过 MIME 类型列表检查:
validate.file('document', mimes=['jpg', 'png'])
可以一次检查全部:
validate.file('document', mimes=['pdf', 'txt'], size='4MB')
对于图像和视频文件,推荐直接使用 image 和 video 验证规则。
Greater_than
用于验证一个值大于指定的值
"""
{
'age': 25
}
"""
validate.greater_than('age', 18)
Image
用于验证值为有效的图像。
"""
{
'avatar': '/my/picture.png'
}
"""
validate.image('avatar')
有效的图像类型,是所有以 image/
开头的所有 MIME 类型。更多的细节,可以查看 mimetypes
python 包, mimetypes.types_map
给出所有 MIME 类型。
另外可以作为基本的文件验证器检查图像的大小
validate.image('avatar', size="2MB")
In_range
检查整数是否在范围内
"""
{
'attendees': 54
}
"""
validate.in_range('attendees', min=24, max=64)
Ip
检查输入的是否有效的 IPV4 地址:
"""
{
'address': '78.281.291.8'
}
"""
validate.ip('address')
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_list
检查值是不是一个列表(一个 Python 列表实例)
"""
{
'tags': [1,3,7]
}
"""
validate.is_list('tags')
*
星号也可以使用
"""
{
'name': 'Joe',
'discounts_ref': [1,2,3]
}
"""
validate.is_list('discounts_ref.*')
Is_in
检查一个值是否在指定值中
"""
{
'age': 5
}
"""
validate.is_in('age', [2,4,5])
注意 5 是怎样在列表中
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')
Isnt
这将会取反所有规则。因此,可以向这个规则中添加你想要取得反向结果的规则。
例如,取得 is_in
反向结果:
"""
{
'age': 5
}
"""
validate.isnt(
validate.is_in('age', [2,4,5])
)
这将会得到一个错误,因为现在要确保 age 的值 不在 列表中。
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)
Matches
确保值与其它字段的值匹配
"""
{
'user1': {
'role': 'admin'
},
'user2': {
'role': 'admin'
}
}
"""
validate.matches('user1.role', 'user2.role')
None
确保值为 None
"""
{
'age': 25,
'active': None
}
"""
validate.none('active')
Numeric
确保值为数字类型
"""
{
'age': 25,
'active': None
}
"""
validate.numeric('age')
One_of
有时候,你只需要某些字段中的一个。至少包含一个字段。
Sometimes you will want only one of several fields to be required. At least one of them need to be required.
"""
{
'user': 'Joe',
'email': 'user@example.com,
'phone': '123-456-7890'
}
"""
validate.one_of(['user', 'accepted', 'location'])
这将会通过,因为有 user
。
Phone
你也可以使用 phone 验证器去验证最常见的手机号码格式:
"""
{
'phone': '876-827-9271'
}
"""
validate.phone('phone', pattern='123-456-7890')
允许使用的 patterns 有:
123-456-7890
(123)456-7890
Postal Code
每个国家有它们自己的邮政编码。我们为超过 130 个国家编写了正则表达式,你可以使用逗号分隔不同国家,指定邮政编码:
"""
{
'zip': '123456'
}
"""
validate.postal_code('zip', "US,IN,GB")
请在 "alpha-2 code" 查看可用的国家格式。
Regex
有时候,在某些字段想要更加复杂的验证。这个规则允许直接使用正则表达式。接下来的例子,检查 username
的值验证用户名 。(排除特殊字符,在 3 到 16 个字符之间)。
"""
{
'username': 'masonite_user_1'
}
"""
validate.regex('username', pattern='^[a-z0-9_-]{3,16}$'))
Required
检查值是否在字典中,并且不为 null 。如果键不存在将会产生错误。只检查值是否在字典中出现,可以使用 exists 。
"""
{
'age': 25,
'email': 'user@email.com',
'first_name': ''
}
"""
validate.required(['age', 'email'])
validate.required('first_name') # would fail
validate.required('last_name') # would fail
Required If
如果另外一个字段有值,才去检查该值是否存在,并且非空。
"""
{
'first_name': 'Sam',
'last_name': 'Gamji'
}
"""
validate.required_if('first_name', 'last_name', 'Gamji')
Required With
其它指定的任意一个字段有值,才去验证该值是否存在,并且非空。
"""
{
'first_name': 'Sam',
'last_name': 'Gamji'
'email': 'samgamji@lotr.com'
}
"""
validate.required_with('email', ['last_name', 'nick_name'])
String
验证该值是否为字符串。
"""
{
'age': 25,
'email': 'user@email.com'
}
"""
validate.string('email')
Strong
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
用于验证一个值是否为真值。像简单的 if 语句那样,有值就会通过。
"""
{
'active': 1,
'email': 'user@email.com'
}
"""
validate.truthy('active')
Uuid
验证值是否为有效的 UUID。可以选择验证的UUID版本标准为(1,3,4 或者 5 ),默认为版本 4 。 (记录在 RFC 4122 )。
"""
{
'doc_id': 'c1d38bb1-139e-4099-8a20-61a2a0c9b996'
}
"""
# check value is a valid UUID4
validate.uuid('doc_id')
# check value is a valid UUID3
validate.uuid('doc_id', 3) # or '3'
Video
验证该值是否为有效的视频文件。
"""
{
'document': '/my/movie.mp4'
}
"""
validate.video('document')
所有 video/
开头的 MIME 类型都是有效的视频类型。更多的细节,可以查看 mimetypes
python 包, mimetypes.types_map
给出所有 MIME 类型。
另外可以作为基本的文件验证器检查视频的大小。
validate.video('document', size="2MB")
When
条件规则。如果第一个规则通过了,然后想运行指定规则,可以使用它。
例如,如果你想在用户不够 18 岁时,才检查条款。
"""
{
'age': 15,
'email': 'user@email.com',
'terms': 'on'
}
"""
validate.when(
validate.less_than('age', 18)
).then(
validate.required('terms'),
validate.accepted('terms')
)
用于检查某个值与另一个字段的值是否不同。它是 [matches] 的相反操作。
本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。