Skip to content

Commit

Permalink
Merge pull request #40 from bobslee/validation-required-simple
Browse files Browse the repository at this point in the history
Implementation of "simple" validation required.
  • Loading branch information
bobslee authored Oct 24, 2023
2 parents 7cd80cf + 90719b6 commit 2d4c90f
Show file tree
Hide file tree
Showing 7 changed files with 177 additions and 6 deletions.
11 changes: 10 additions & 1 deletion formiodata/components/component.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ def load(self, component_owner, parent=None, data=None, all_data=None):

self.builder.component_ids[self.id] = self

# parh
# path
self.set_builder_paths()
builder_path_keys = [p.key for p in self.builder_path]
builder_path_key = '.'.join(builder_path_keys)
Expand Down Expand Up @@ -330,3 +330,12 @@ def is_visible(self):
return self.conditionally_visible
else:
return not self.hidden

def validation_errors(self):
errors = {}
if self.required and not self.value:
msg_tmpl = '{{field}} is required'
if self.i18n.get(self.language):
msg_tmpl = self.i18n[self.language].get(msg_tmpl, msg_tmpl)
errors['required'] = msg_tmpl.replace('{{field}}', self.label)
return errors
16 changes: 16 additions & 0 deletions formiodata/components/grid_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,22 @@ def child_component_owner(self):
def initEmpty(self):
return self.raw.get('initEmpty')

def validation_errors(self):
errors = []
for row_idx, row in enumerate(self.rows):
row_errors = {}
for component_key, component in row.input_components.items():
component_errors = component.validation_errors()
if bool(component_errors):
# scalar (not grid) components retrieve a Dict
# from method validation_errors()
row_errors[component_key] = component_errors
if bool(row_errors):
errors.append(row_errors)
else:
errors.append({})
return errors

def render(self):
for row in self.rows:
row.render()
Expand Down
18 changes: 17 additions & 1 deletion formiodata/form.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import logging
import re

from collections import OrderedDict
from collections import defaultdict, OrderedDict

from formiodata.builder import Builder

Expand Down Expand Up @@ -120,6 +120,22 @@ def get_component_by_path(self, component_path):
component = components[path_node]
return component

def validation_errors(self):
"""
@return errors dict: Dictionary where key is component key and
value is a Dictionary with errors.
"""
errors = defaultdict(dict)
for component_key, component in self.input_components.items():
component_errors = component.validation_errors()
if isinstance(component_errors, dict):
for error_type, val in component_errors.items():
vals = {error_type: val}
errors[component_key].update(vals)
elif isinstance(component_errors, list):
errors[component_key] = component_errors
return errors

def render_components(self, force=False):
for key, component in self.input_components.items():
if force or component.html_component == "":
Expand Down
6 changes: 3 additions & 3 deletions tests/data/test_example_builder.json
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@
"allowCalculateOverride": false,
"validateOn": "change",
"validate": {
"required": false,
"required": true,
"pattern": "",
"customMessage": "",
"custom": "",
Expand Down Expand Up @@ -518,7 +518,7 @@
"maxLength": "",
"minLength": "",
"pattern": "",
"required": false,
"required": true,
"strictDateValidation": false,
"multiple": false,
"unique": false
Expand Down Expand Up @@ -2988,7 +2988,7 @@
"allowCalculateOverride": false,
"validateOn": "change",
"validate": {
"required": false,
"required": true,
"pattern": "",
"customMessage": "",
"custom": "",
Expand Down
41 changes: 41 additions & 0 deletions tests/data/test_example_form_validation_errors.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"firstName": "",
"email": "[email protected]",
"birthdate": "",
"appointmentDateTime": "",
"lastName": "",
"phoneNumber": "",
"cardinalDirection": "",
"favouriteSeason": "",
"favouriteFood": [],
"monthDayYear": "00/00/0000",
"monthYear": "00/00/0000",
"dayMonthYear": "00/00/0000",
"dayMonth": "00/00/0000",
"day": "00/00/0000",
"month": "00/00/0000",
"year": "00/00/0000",
"survey": null,
"signature": "",
"dataGrid": [
{
"textField": "",
"checkbox": false
},
{
"textField": "Some #1 text here",
"checkbox": false
},
{
"textField": "",
"checkbox": false
},
{
"textField": "Some #2 text here",
"checkbox": false
}
],
"uploadBase64": [],
"uploadUrl": [],
"submit": true
}
5 changes: 4 additions & 1 deletion tests/test_component.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ def setUp(self):

self.builder_i18n_nl = Builder(self.builder_json, language='nl', i18n=self._i18n())
self.form_i18n_nl = Form(self.form_json, self.builder_i18n_nl)
self.form_empty_i18n_nl = Form(self.form_empty_json, self.builder_i18n_nl)

def _i18n(self):
return {
Expand All @@ -46,8 +47,10 @@ def _i18n(self):
'Month Day Year': 'Maand dag jaar',
'Day Month Year': 'Dag maand jaar',
'May': 'Mei',
'Text Field': 'Tekstveld',
'Upload Base64': 'Upload binair naar ASCII',
'Upload Url': 'Upload naar locatie'
'Upload Url': 'Upload naar locatie',
'{{field}} is required': '{{field}} is verplicht'
}
}

Expand Down
86 changes: 86 additions & 0 deletions tests/test_validation_error_simple.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# Copyright Nova Code (http://www.novacode.nl)
# See LICENSE file for full licensing details.

from tests.utils import readfile
from test_component import ComponentTestCase

from formiodata.form import Form


class ValidationErrorSimpleTestCase(ComponentTestCase):

def setUp(self):
super(ValidationErrorSimpleTestCase, self).setUp()
self.form_validation_errors_json = readfile('data', 'test_example_form_validation_errors.json')
self.form_validation_errors = Form(self.form_validation_errors_json, self.builder)
self.form_validation_errors_i18n_nl = Form(self.form_validation_errors_json, self.builder_i18n_nl)

def test_required_components_in_builder(self):
firstName = self.builder.input_components['firstName']
self.assertTrue(firstName.required)

lastName = self.builder.input_components['lastName']
self.assertTrue(lastName.required)

email = self.builder.input_components['email']
self.assertFalse(email.required)

def test_required_components_form_validation_errors(self):
errors = self.form_validation_errors.validation_errors()

self.assertEqual(
errors['firstName']['required'],
'First Name is required'
)
self.assertEqual(
errors['lastName']['required'],
'Last Name is required'
)
self.assertEqual(
errors['dataGrid'][0]['textField']['required'],
'Text Field is required'
)
self.assertEqual(
errors['dataGrid'][1],
{}
)
self.assertEqual(
errors['dataGrid'][2]['textField']['required'],
'Text Field is required'
)
self.assertEqual(
errors['dataGrid'][3],
{}
)

def test_required_components_form_validation_errors_i18n_nl(self):
errors = self.form_validation_errors_i18n_nl.validation_errors()

self.assertEqual(
errors['firstName']['required'],
'Voornaam is verplicht'
)
self.assertEqual(
errors['lastName']['required'],
'Achternaam is verplicht'
)
self.assertEqual(
errors['dataGrid'][0]['textField']['required'],
'Tekstveld is verplicht'
)
self.assertEqual(
errors['dataGrid'][1],
{}
)
self.assertEqual(
errors['dataGrid'][2]['textField']['required'],
'Tekstveld is verplicht'
)
self.assertEqual(
errors['dataGrid'][3],
{}
)

def test_not_required_components_form(self):
errors = self.form_validation_errors_i18n_nl.validation_errors()
self.assertNotIn('email', errors)

0 comments on commit 2d4c90f

Please sign in to comment.