Docs

Validation

Evlet checks every value before it saves, whoever sends it: the browser, the API or your own code. You add your own checks at three levels: on one field, across the whole record, and as rules that switch fields on and off.

#Checks on one field

Field options are the first line of checks. Evlet applies them on every save:

Python
customer_name = Text(required=True, max_length=120)
credit_days = Integer(default=30, minimum=0, maximum=120)

For a check of your own, pass a validator function. It gets the cleaned value, which is never empty, and raises ValueError to refuse it:

Python
def no_test_names(value):
    if value.lower().startswith('test'):
        raise ValueError('Use a real deal name')


class Deal(Model):
    title = Text(required=True, max_length=120, validator=no_test_names)

Saving a deal called "Test deal" fails with title: Use a real deal name.

#Checks across fields: validate()

A rule that reads several fields goes in a validate(self) method. Raise ValidationError with a dictionary from field name to message, so the form can show the message next to the right field:

Python
from evlet import Boolean, Email, Integer, Model, Rule, Text, ValidationError


class Customer(Model):
    # permissions and Meta as before
    customer_name = Text(required=True, max_length=120)
    email = Email()
    credit_days = Integer(default=30, minimum=0, maximum=120)
    active = Boolean(default=True)

    def validate(self):
        if not self.active and self.credit_days:
            raise ValidationError({'credit_days': 'An inactive customer gets no credit'})

Setting active to false on a customer who still has credit days fails with credit_days: An inactive customer gets no credit, and nothing is saved.

What to know about validate():

  • It runs on every create and update, last, after derive() and the formulas. So it sees the final values.
  • It is for checks only. A value you set inside validate() is not cleaned, worked out or saved. Build values in derive() instead.
  • It also runs before a move or an action is saved. The invoice on the next pages uses this to ask for an issue date before an invoice leaves draft:
Python
def validate(self):
    if self.issued_on is None and self.status != 'draft':
        raise ValidationError({'issued_on': 'Set the issue date'})

#Field rules: applies, required, locked

Some fields matter only in some cases. Three options take a formula that is true or false:

Python
from evlet import Currency, Model, Rule, Select, Text
from evlet.calc import F


class Deal(Model):
    permissions = {'admin': Rule(('read', 'create', 'update', 'delete'))}
    title = Text(required=True, max_length=120, validator=no_test_names)
    stage = Select(('open', 'won', 'lost'), default='open')
    value = Currency(currency='AED', default=0)
    lost_reason = Text(applies_when=F('stage') == 'lost', required_when=F('stage') == 'lost')
    discount = Currency(currency='AED', default=0, locked_when=F('stage') == 'won')
OptionWhen the formula is true
applies_whenThe field is in use. When false, it is not required and its value is stored empty.
required_whenThe field must have a value.
locked_whenThe value cannot change. The lock is read from the record as it was saved.

With these rules:

  • A new open deal keeps no lost_reason, even if one is sent. It is stored empty.
  • Setting stage to lost without a reason fails with lost_reason: This field is required.
  • Changing the discount of a won deal fails with discount: This field is locked and cannot be changed now.

The browser shows, hides and locks the fields as people type. The server checks the same rules on every save.

#What the error looks like

A failed save raises ValidationError in Python and answers 422 over HTTP. Each message is keyed by the field it belongs to, so the form can show it in the right place.