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:
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:
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:
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 inderive()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:
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:
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')| Option | When the formula is true |
|---|---|
applies_when | The field is in use. When false, it is not required and its value is stored empty. |
required_when | The field must have a value. |
locked_when | The 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
stagetolostwithout a reason fails withlost_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.