Commit e13040b9 authored by Nick Stenning's avatar Nick Stenning

Support reporting top-level form validation errors

When we call `form.validate()` and the validation fails, the raised
exception is a `deform.ValidationFailure`. This object has an `error`
attribute which represents the underlying validation error object for
the entire form.

Previously, if the validation failure were a result of top-level
validation errors (such as the fact that an unactivated user is trying
to log in), this error would be lost, because we only reported errors
for the forms fields, or "children".

This commit changes `h.accounts.views.validate_form` so that it converts
the entire `colander.Invalid` object into a dictionary using its
`asdict()` instance method. By doing this, we get two immediate
benefits:

- top-level validation errors are reported in the '' (empty string)
  field
- we avoid the need to aggregate form field errors by hand in ajax_form

In addition, we need to deal with this case on the frontend, so this
commit also changes the formRespond directive so that if no overall
"reason" is provided for failure, then an empty-string member of the
"errors" object can set the overall validation status (and error
message) for the form.
parent 3b988248
...@@ -4,9 +4,17 @@ ...@@ -4,9 +4,17 @@
# will contain the API error message. # will contain the API error message.
module.exports = -> module.exports = ->
(form, errors, reason) -> (form, errors, reason) ->
form.$setValidity('response', !reason)
form.responseErrorMessage = reason
for own field, error of errors for own field, error of errors
# If there's an empty-string field, it's a top-level form error. Set the
# overall form validity from this field, but only if there wasn't already
# a reason.
if !reason and field == ''
form.$setValidity('response', false)
form.responseErrorMessage = error
continue
form[field].$setValidity('response', false) form[field].$setValidity('response', false)
form[field].responseErrorMessage = error form[field].responseErrorMessage = error
form.$setValidity('response', !reason)
form.responseErrorMessage = reason
...@@ -38,6 +38,14 @@ describe 'form-respond', -> ...@@ -38,6 +38,14 @@ describe 'form-respond', ->
assert.equal(form.username.responseErrorMessage, 'must be at least 3 characters') assert.equal(form.username.responseErrorMessage, 'must be at least 3 characters')
assert.equal(form.password.responseErrorMessage, 'must be present') assert.equal(form.password.responseErrorMessage, 'must be present')
it 'sets the "response" error key if the form has a top-level error', ->
formRespond form, {'': 'Explosions!'}
assert.calledWith(form.$setValidity, 'response', false)
it 'adds an error message if the form has a top-level error', ->
formRespond form, {'': 'Explosions!'}
assert.equal(form.responseErrorMessage, 'Explosions!')
it 'sets the "response" error key if the form has a failure reason', -> it 'sets the "response" error key if the form has a failure reason', ->
formRespond form, null, 'fail' formRespond form, null, 'fail'
assert.calledWith(form.$setValidity, 'response', false) assert.calledWith(form.$setValidity, 'response', false)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment