Opened 10 years ago
Last modified 10 years ago
#24988 closed Cleanup/optimization
ValidationError fails to set code — at Initial Version
| 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
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 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?