fields: custom error message for django/regex validation (#50772)

This commit is contained in:
Lauréline Guérin 2021-02-04 09:48:00 +01:00
parent 08893a339c
commit 669b338187
No known key found for this signature in database
GPG Key ID: 1FAB9B9B4F93D473
3 changed files with 112 additions and 9 deletions

View File

@ -1501,8 +1501,13 @@ def test_form_edit_string_field_validation(pub):
resp = resp.click('Edit', href='1/')
resp.form['validation$type'] = 'Regular Expression'
resp.form['validation$value_regex'] = r'\d+'
resp.form['validation$error_message'] = 'Foo Error'
resp = resp.form.submit('submit').follow()
assert FormDef.get(formdef.id).fields[0].validation == {'type': 'regex', 'value': r'\d+'}
assert FormDef.get(formdef.id).fields[0].validation == {
'type': 'regex',
'value': r'\d+',
'error_message': 'Foo Error',
}
resp = resp.click('Edit', href='1/')
resp.form['validation$type'] = 'None'
@ -1512,8 +1517,13 @@ def test_form_edit_string_field_validation(pub):
resp = resp.click('Edit', href='1/')
resp.form['validation$type'] = 'Django Condition'
resp.form['validation$value_django'] = 'value|decimal < 20'
resp.form['validation$error_message'] = 'Bar Error'
resp = resp.form.submit('submit').follow()
assert FormDef.get(formdef.id).fields[0].validation == {'type': 'django', 'value': 'value|decimal < 20'}
assert FormDef.get(formdef.id).fields[0].validation == {
'type': 'django',
'value': 'value|decimal < 20',
'error_message': 'Bar Error',
}
resp = resp.click('Edit', href='1/')
resp.form['validation$type'] = 'Django Condition'

View File

@ -743,6 +743,18 @@ def test_wcsextrastringwidget_regex_validation():
widget.field = fakefield
mock_form_submission(req, widget, {'test': '12,34'})
assert widget.has_error()
assert widget.error == 'invalid value'
widget = WcsExtraStringWidget('test', value='foo', required=False)
fakefield.validation = {
'type': 'regex',
'value': r'\d+(\.\d{1,2})?',
'error_message': 'Foo Bar Custom Error',
}
widget.field = fakefield
mock_form_submission(req, widget, {'test': '12,34'})
assert widget.has_error()
assert widget.error == 'Foo Bar Custom Error'
def test_wcsextrastringwidget_builtin_validation():
@ -761,6 +773,7 @@ def test_wcsextrastringwidget_builtin_validation():
widget.field = fakefield
mock_form_submission(req, widget, {'test': 'az'})
assert widget.has_error()
assert widget.error == 'Only digits are allowed'
fakefield.validation = {'type': 'zipcode-fr'}
widget = WcsExtraStringWidget('test', value='foo', required=False)
@ -777,6 +790,7 @@ def test_wcsextrastringwidget_builtin_validation():
widget.field = fakefield
mock_form_submission(req, widget, {'test': '1234'})
assert widget.has_error()
assert widget.error == 'Invalid zip code'
def test_wcsextrastringwidget_phone():
@ -798,6 +812,7 @@ def test_wcsextrastringwidget_phone():
widget.field = fakefield
mock_form_submission(req, widget, {'test': invalid_case})
assert widget.has_error()
assert widget.error == 'Invalid phone number'
# and check it gets a special HTML input type
widget = WcsExtraStringWidget('test', value='foo', required=False)
@ -827,6 +842,7 @@ def test_wcsextrastringwidget_phone_fr():
widget.field = fakefield
mock_form_submission(req, widget, {'test': 'az'})
assert widget.has_error()
assert widget.error == 'Invalid phone number'
widget = WcsExtraStringWidget('test', value='foo', required=False)
widget.field = fakefield
@ -858,6 +874,7 @@ def test_wcsextrastringwidget_siren_validation():
widget.field = fakefield
mock_form_submission(req, widget, {'test': '443170130'})
assert widget.has_error()
assert widget.error == 'Invalid SIREN code'
widget = WcsExtraStringWidget('test', value='foo', required=False)
widget.field = fakefield
@ -899,6 +916,7 @@ def test_wcsextrastringwidget_siret_validation():
widget.field = fakefield
mock_form_submission(req, widget, {'test': '44317013900037'})
assert widget.has_error()
assert widget.error == 'Invalid SIRET code'
widget = WcsExtraStringWidget('test', value='foo', required=False)
widget.field = fakefield
@ -940,6 +958,7 @@ def test_wcsextrastringwidget_nir_validation():
widget.field = fakefield
mock_form_submission(req, widget, {'test': '42'})
assert widget.has_error()
assert widget.error == 'Invalid NIR'
widget = WcsExtraStringWidget('test', value='foo', required=False)
widget.field = fakefield
@ -1016,6 +1035,7 @@ def test_wcsextrastringwidget_iban_validation():
widget.field = fakefield
mock_form_submission(req, widget, {'test': iban.replace(' ', '')})
assert widget.has_error()
assert widget.error == 'Invalid IBAN'
def test_wcsextrastringwidget_django_validation():
@ -1039,6 +1059,18 @@ def test_wcsextrastringwidget_django_validation():
widget.field = fakefield
mock_form_submission(req, widget, {'test': 'az'})
assert widget.has_error()
assert widget.error == 'invalid value'
widget = WcsExtraStringWidget('test', value='foo', required=False)
fakefield.validation = {
'type': 'django',
'value': 'value|decimal and value|decimal < 20',
'error_message': 'Foo Bar Custom Error',
}
widget.field = fakefield
mock_form_submission(req, widget, {'test': 'az'})
assert widget.has_error()
assert widget.error == 'Foo Bar Custom Error'
def test_widgetdict_widget():

View File

@ -1051,28 +1051,48 @@ class ValidationCondition(Condition):
class ValidationWidget(CompositeWidget):
validation_methods = collections.OrderedDict(
[
('digits', {'title': N_('Digits'), 'regex': r'\d+', 'html_inputmode': 'numeric'}),
(
'digits',
{
'title': N_('Digits'),
'regex': r'\d+',
'error_message': N_('Only digits are allowed'),
'html_inputmode': 'numeric',
},
),
(
'phone',
{'title': N_('Phone Number'), 'regex': r'\+?[-\(\)\d\.\s/]+', 'html_input_type': 'tel'},
{
'title': N_('Phone Number'),
'regex': r'\+?[-\(\)\d\.\s/]+',
'error_message': N_('Invalid phone number'),
'html_input_type': 'tel',
},
),
(
'phone-fr',
{
'title': N_('Phone Number (France)'),
'function': 'validate_phone_fr',
'error_message': N_('Invalid phone number'),
'html_input_type': 'tel',
},
),
(
'zipcode-fr',
{'title': N_('Zip Code (France)'), 'regex': r'\d{5}', 'html_inputmode': 'numeric'},
{
'title': N_('Zip Code (France)'),
'regex': r'\d{5}',
'error_message': N_('Invalid zip code'),
'html_inputmode': 'numeric',
},
),
(
'siren-fr',
{
'title': N_('SIREN Code (France)'),
'function': 'validate_siren',
'error_message': N_('Invalid SIREN code'),
'html_inputmode': 'numeric',
},
),
@ -1081,11 +1101,15 @@ class ValidationWidget(CompositeWidget):
{
'title': N_('SIRET Code (France)'),
'function': 'validate_siret',
'error_message': N_('Invalid SIRET code'),
'html_inputmode': 'numeric',
},
),
('nir-fr', {'title': N_('NIR (France)'), 'function': 'validate_nir'}),
('iban', {'title': N_('IBAN'), 'function': 'validate_iban'}),
(
'nir-fr',
{'title': N_('NIR (France)'), 'error_message': N_('Invalid NIR'), 'function': 'validate_nir'},
),
('iban', {'title': N_('IBAN'), 'function': 'validate_iban', 'error_message': N_('Invalid IBAN')}),
('regex', {'title': N_('Regular Expression')}),
('django', {'title': N_('Django Condition')}),
]
@ -1130,6 +1154,20 @@ class ValidationWidget(CompositeWidget):
'data-dynamic-display-value': validation_labels.get('django'),
},
)
self.add(
StringWidget,
'error_message',
size=80,
value=value.get('error_message') if value.get('type') in ['regex', 'django'] else None,
title=_('Custom error message'),
hint=_('This message will be be displayed if validation fails.'),
attrs={
'data-dynamic-display-child-of': 'validation$type',
'data-dynamic-display-value-in': '|'.join(
[validation_labels.get('regex'), validation_labels.get('django')]
),
},
)
self._parsed = False
def _parse(self, request):
@ -1140,14 +1178,23 @@ class ValidationWidget(CompositeWidget):
value = self.get('value_%s' % type_)
if value:
values['value'] = value
if type_ in ['regex', 'django']:
error_message = self.get('error_message')
if error_message:
values['error_message'] = error_message
self.value = values or None
def render_content(self):
r = TemplateIO(html=True)
for widget in self.get_widgets():
inlines = ['type', 'value_regex', 'value_django']
for name in inlines:
widget = self.get_widget(name)
r += widget.render_error(widget.get_error())
for widget in self.get_widgets():
for name in inlines:
widget = self.get_widget(name)
r += widget.render_content()
widget = self.get_widget('error_message')
r += widget.render()
return r.getvalue()
@classmethod
@ -1170,6 +1217,17 @@ class ValidationWidget(CompositeWidget):
if validation_method and 'function' in validation_method:
return getattr(misc, validation_method['function'])
@classmethod
def get_validation_error_message(cls, validation):
pattern = cls.get_validation_pattern(validation)
if validation['type'] == 'regex' and pattern:
return validation.get('error_message')
if validation['type'] == 'django' and validation.get('value'):
return validation.get('error_message')
validation_method = cls.validation_methods.get(validation['type'])
if validation_method and 'error_message' in validation_method:
return validation_method['error_message']
@classmethod
def get_validation_pattern(cls, validation):
validation_method = cls.validation_methods.get(validation['type'])
@ -1214,6 +1272,9 @@ class WcsExtraStringWidget(StringWidget):
StringWidget._parse(self, request)
if self.field and self.field.validation and self.value is not None:
self.validation_function = ValidationWidget.get_validation_function(self.field.validation)
self.validation_function_error_message = ValidationWidget.get_validation_error_message(
self.field.validation
)
if self.validation_function and not self.validation_function(self.value):
self.error = self.validation_function_error_message or _('invalid value')