表单验证

未匹配的标注
本文档最新版为 4.0,旧版本可能放弃维护,推荐阅读最新版!

验证

验证

简介

有很多时候,您需要验证来自表单或 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 个方法,当您运行规则命令时可以看到它们:passesmessagenegated_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 is_in truthy
active_domain isnt when
after_today is_past timezone
before_today is_future phone
contains json strong
email less_than
equals length
exists none
greater_than numeric
in_range required
ip string

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)

Email

检查输入值是一个有效的邮箱地址

"""
{
  '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')
)

本文章首发在 LearnKu.com 网站上。

本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://learnku.com/docs/masonite/2.3/ad...

译文地址:https://learnku.com/docs/masonite/2.3/ad...

上一篇 下一篇
贡献者:7
讨论数量: 0
发起讨论 只看当前版本


暂无话题~