#24988: ValidationError fails to set code
---------------------------------+--------------------------------------
     Reporter:  michaeljohnbarr  |                    Owner:  nobody
         Type:  Bug              |                   Status:  new
    Component:  Core (Other)     |                  Version:  1.8
     Severity:  Normal           |               Resolution:
     Keywords:  ValidationError  |             Triage Stage:  Unreviewed
    Has patch:  0                |      Needs documentation:  0
  Needs tests:  0                |  Patch needs improvement:  0
Easy pickings:  0                |                    UI/UX:  0
---------------------------------+--------------------------------------
Changes (by michaeljohnbarr):

 * needs_better_patch:   => 0
 * needs_tests:   => 0
 * needs_docs:   => 0


Old description:

> Assuming the following model:
>
> {{{
> class DateRange(models.Model):
>     start = models.DateField()
>     end = models.DateField
>
>     def clean(self):
>         super(DateRange, self).clean()
>
>         if self.start and self.end:
>             if self.start >= self.end:
>                 raise ValidationError(
>                     message={
>                         'start': _(
>                             '{start} must come before {end}.'.format(
>                                 start=self._meta.get_field(
>                                     'start'
>                                 ).verbose_name,
>                                 end=self._meta.get_field(
>                                     'end'
>                                 ).verbose_name,
>                             )
>                         ),
>                         'end': _(
>                             '{end} must come after {start}.'.format(
>                                 end=self._meta.get_field(
>                                     'end'
>                                 ).verbose_name,
>                                 start=self._meta.get_field(
>                                     'start'
>                                 ).verbose_name,
>                             )
>                         ),
>                     },
>                     code='invalid',
>                 )
> }}}
>
> After then validating the model with a ModelForm, the `code` fails to be
> set to `'invalid'` but is instead an empty string. I discovered this when
> I was programming tests:
>

> {{{
>     def test_start_before_end_validation(self):
>         """An employee is not allowed to be their own manager."""
>         TestForm = modelform_factory(
>             model=DateRange,
>             fields=('start', 'end')
>         )
>
>         form = TestForm(
>             data=dict(
>                 start=date(2015, 1, 1),
>                 end=date(1900, 12, 31)     # WHOOPS!
>             )
>         )
>
>         self.assertEqual(first=form.is_valid(), second=False)
>
>         # The below fails because the code is '' - we have to leave off
> the code
>         self.assertTrue(form.has_error(field='start'), code='invalid')
>         self.assertTrue(form.has_error(field='end'), code='invalid')
> }}}
>
> Upon inspection of the the `ValidationError` code, I discovered that that
> any `ValidationError` that is raised with a type of `dict` or a `list` as
> the `message` will never set the code:
>

> {{{
>         if isinstance(message, dict):
>             self.error_dict = {}
>             for field, messages in message.items():
>                 if not isinstance(messages, ValidationError):
>                     messages = ValidationError(messages)
>                 self.error_dict[field] = messages.error_list
>
>         elif isinstance(message, list):
>             self.error_list = []
>             for message in message:
>                 # Normalize plain strings to instances of
> ValidationError.
>                 if not isinstance(message, ValidationError):
>                     message = ValidationError(message)
>                 if hasattr(message, 'error_dict'):
> self.error_list.extend(sum(message.error_dict.values(), []))
>                 else:
>                     self.error_list.extend(message.error_list)
> }}}
>
> According to the
> [https://docs.djangoproject.com/en/1.8/ref/forms/validation/#raising-
> validationerror documentation on raising ValidationError], it is
> suggested to "Provide a descriptive error code to the constructor." Is
> this a bug?
>
> I believe the "fix" would be as simple as to pass `code=code` following
> the message/messages, but I may be wrong. Thoughts?

New description:

 Assuming the following model:

 {{{
 class DateRange(models.Model):
     start = models.DateField()
     end = models.DateField

     def clean(self):
         super(DateRange, self).clean()

         if self.start and self.end:
             if self.start >= self.end:
                 raise ValidationError(
                     message={
                         'start': _(
                             '{start} must come before {end}.'.format(
                                 start=self._meta.get_field(
                                     'start'
                                 ).verbose_name,
                                 end=self._meta.get_field(
                                     'end'
                                 ).verbose_name,
                             )
                         ),
                         'end': _(
                             '{end} must come after {start}.'.format(
                                 end=self._meta.get_field(
                                     'end'
                                 ).verbose_name,
                                 start=self._meta.get_field(
                                     'start'
                                 ).verbose_name,
                             )
                         ),
                     },
                     code='invalid',
                 )
 }}}

 After then validating the model with a ModelForm, the `code` fails to be
 set to `'invalid'` but is instead an empty string. I discovered this when
 I was programming tests:


 {{{
     def test_start_before_end_validation(self):
         """Start date must come before end date."""
         TestForm = modelform_factory(
             model=DateRange,
             fields=('start', 'end')
         )

         form = TestForm(
             data=dict(
                 start=date(2015, 1, 1),
                 end=date(1900, 12, 31)     # WHOOPS!
             )
         )

         self.assertEqual(first=form.is_valid(), second=False)

         # The below fails because the code is '' - we have to leave off
 the code
         self.assertTrue(form.has_error(field='start'), code='invalid')
         self.assertTrue(form.has_error(field='end'), code='invalid')
 }}}

 Upon inspection of the the `ValidationError` code, I discovered that that
 any `ValidationError` that is raised with a type of `dict` or a `list` as
 the `message` will never set the code:


 {{{
         if isinstance(message, dict):
             self.error_dict = {}
             for field, messages in message.items():
                 if not isinstance(messages, ValidationError):
                     messages = ValidationError(messages)
                 self.error_dict[field] = messages.error_list

         elif isinstance(message, list):
             self.error_list = []
             for message in message:
                 # Normalize plain strings to instances of ValidationError.
                 if not isinstance(message, ValidationError):
                     message = ValidationError(message)
                 if hasattr(message, 'error_dict'):
 self.error_list.extend(sum(message.error_dict.values(), []))
                 else:
                     self.error_list.extend(message.error_list)
 }}}

 According to the
 [https://docs.djangoproject.com/en/1.8/ref/forms/validation/#raising-
 validationerror documentation on raising ValidationError], it is suggested
 to "Provide a descriptive error code to the constructor." Is this a bug?

 I believe the "fix" would be as simple as to pass `code=code` following
 the message/messages, but I may be wrong. Thoughts?

--

--
Ticket URL: <https://code.djangoproject.com/ticket/24988#comment:1>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To post to this group, send email to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-updates/073.bf1b4bebe3c38dc4bd8c32c3679f6019%40djangoproject.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to