Opened 9 years ago

Last modified 9 years ago

#24988 closed Cleanup/optimization

ValidationError fails to set code — at Version 1

Reported by: Michael Barr Owned by: nobody
Component: Documentation Version: 1.8
Severity: Normal Keywords: ValidationError
Cc: Loic Bistuer Triage Stage: Accepted
Has patch: yes Needs documentation: no
Needs tests: no Patch needs improvement: yes
Easy pickings: no UI/UX: no

Description (last modified by Michael Barr)

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 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?

Change History (1)

comment:1 by Michael Barr, 9 years ago

Description: modified (diff)
Note: See TracTickets for help on using tickets.
Back to Top