#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
---------------------------------+--------------------------------------
Description changed by michaeljohnbarr:

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):
>         """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?

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:2>
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 django-updates+unsubscr...@googlegroups.com.
To post to this group, send email to django-updates@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-updates/073.1889f4b5c14296082469957637d92614%40djangoproject.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to