Commit bfff356d by Stephan Groß

Add better date / datetime validation (pull 2)

addition to #631 with update to master + timefield support
parent 8da83f0d
......@@ -185,12 +185,20 @@ Corresponds to `django.forms.fields.RegexField`
A date representation.
Uses `DATE_INPUT_FORMATS` to validate date.
Optionally takes `format` as parameter to replace the matching pattern.
Corresponds to `django.db.models.fields.DateField`
## DateTimeField
A date and time representation.
Uses `DATETIME_INPUT_FORMATS` to validate date_time.
Optionally takes `format` as parameter to replace the matching pattern.
Corresponds to `django.db.models.fields.DateTimeField`
When using `ModelSerializer` or `HyperlinkedModelSerializer`, note that any model fields with `auto_now=True` or `auto_now_add=True` will use serializer fields that are `read_only=True` by default.
......@@ -207,6 +215,10 @@ If you want to override this behavior, you'll need to declare the `DateTimeField
A time representation.
Uses `TIME_INPUT_FORMATS` to validate time.
Optionally takes `format` as parameter to replace the matching pattern.
Corresponds to `django.db.models.fields.TimeField`
## IntegerField
......
......@@ -44,6 +44,9 @@ You can determine your currently installed version using `pip freeze`:
* Bugfix for serializer data being uncacheable with pickle protocol 0.
* Bugfixes for model field validation edge-cases.
* Support `DATE_INPUT_FORMATS` for `DateField` validation
* Support `DATETIME_INPUT_FORMATS` for `DateTimeField` validation
* Support `TIME_INPUT_FORMATS` for `TimeField` validation
### 2.2.1
......
......@@ -19,6 +19,7 @@ from rest_framework.compat import BytesIO
from rest_framework.compat import six
from rest_framework.compat import smart_text
from rest_framework.compat import parse_time
from rest_framework.utils.dates import get_readable_date_format
def is_simple_callable(obj):
......@@ -447,13 +448,14 @@ class DateField(WritableField):
form_field_class = forms.DateField
default_error_messages = {
'invalid': _("'%s' value has an invalid date format. It must be "
"in YYYY-MM-DD format."),
'invalid_date': _("'%s' value has the correct format (YYYY-MM-DD) "
"but it is an invalid date."),
'invalid': _(u"Date has wrong format. Use one of these formats instead: %s"),
}
empty = None
def __init__(self, *args, **kwargs):
self.format = kwargs.pop('format', settings.DATE_INPUT_FORMATS)
super(DateField, self).__init__(*args, **kwargs)
def from_native(self, value):
if value in validators.EMPTY_VALUES:
return None
......@@ -468,15 +470,16 @@ class DateField(WritableField):
if isinstance(value, datetime.date):
return value
try:
parsed = parse_date(value)
if parsed is not None:
return parsed
except (ValueError, TypeError):
msg = self.error_messages['invalid_date'] % value
raise ValidationError(msg)
for format in self.format:
try:
parsed = datetime.datetime.strptime(value, format)
except (ValueError, TypeError):
pass
else:
return parsed.date()
msg = self.error_messages['invalid'] % value
date_input_formats = '; '.join(self.format)
msg = self.error_messages['invalid'] % get_readable_date_format(date_input_formats)
raise ValidationError(msg)
......@@ -486,16 +489,14 @@ class DateTimeField(WritableField):
form_field_class = forms.DateTimeField
default_error_messages = {
'invalid': _("'%s' value has an invalid format. It must be in "
"YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ] format."),
'invalid_date': _("'%s' value has the correct format "
"(YYYY-MM-DD) but it is an invalid date."),
'invalid_datetime': _("'%s' value has the correct format "
"(YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ]) "
"but it is an invalid date/time."),
'invalid': _(u"Datetime has wrong format. Use one of these formats instead: %s"),
}
empty = None
def __init__(self, *args, **kwargs):
self.format = kwargs.pop('format', settings.DATETIME_INPUT_FORMATS)
super(DateTimeField, self).__init__(*args, **kwargs)
def from_native(self, value):
if value in validators.EMPTY_VALUES:
return None
......@@ -516,23 +517,16 @@ class DateTimeField(WritableField):
value = timezone.make_aware(value, default_timezone)
return value
try:
parsed = parse_datetime(value)
if parsed is not None:
for format in self.format:
try:
parsed = datetime.datetime.strptime(value, format)
except (ValueError, TypeError):
pass
else:
return parsed
except (ValueError, TypeError):
msg = self.error_messages['invalid_datetime'] % value
raise ValidationError(msg)
try:
parsed = parse_date(value)
if parsed is not None:
return datetime.datetime(parsed.year, parsed.month, parsed.day)
except (ValueError, TypeError):
msg = self.error_messages['invalid_date'] % value
raise ValidationError(msg)
msg = self.error_messages['invalid'] % value
datetime_input_formats = '; '.join(self.format)
msg = self.error_messages['invalid'] % get_readable_date_format(datetime_input_formats)
raise ValidationError(msg)
......@@ -542,11 +536,14 @@ class TimeField(WritableField):
form_field_class = forms.TimeField
default_error_messages = {
'invalid': _("'%s' value has an invalid format. It must be a valid "
"time in the HH:MM[:ss[.uuuuuu]] format."),
'invalid': _(u"Time has wrong format. Use one of these formats instead: %s"),
}
empty = None
def __init__(self, *args, **kwargs):
self.format = kwargs.pop('format', settings.TIME_INPUT_FORMATS)
super(TimeField, self).__init__(*args, **kwargs)
def from_native(self, value):
if value in validators.EMPTY_VALUES:
return None
......@@ -554,13 +551,17 @@ class TimeField(WritableField):
if isinstance(value, datetime.time):
return value
try:
parsed = parse_time(value)
assert parsed is not None
return parsed
except (ValueError, TypeError):
msg = self.error_messages['invalid'] % value
raise ValidationError(msg)
for format in self.format:
try:
parsed = datetime.datetime.strptime(value, format)
except (ValueError, TypeError):
pass
else:
return parsed.time()
time_input_formats = '; '.join(self.format)
msg = self.error_messages['invalid'] % get_readable_date_format(time_input_formats)
raise ValidationError(msg)
class IntegerField(WritableField):
......
def get_readable_date_format(date_format):
mapping = [("%Y", "YYYY"),
("%y", "YY"),
("%m", "MM"),
("%b", "[Jan through Dec]"),
("%B", "[January through December]"),
("%d", "DD"),
("%H", "HH"),
("%M", "MM"),
("%S", "SS"),
("%f", "uuuuuu")]
for k, v in mapping:
date_format = date_format.replace(k, v)
return date_format
\ No newline at end of file
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