Commit 9157db5d by Stephan Groß

Add better date / datetime validation (pull 2)

addition to #631 with update to master + timefield support
parent 282af605
......@@ -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
......
......@@ -45,6 +45,9 @@ You can determine your currently installed version using `pip freeze`:
* Request authentication is no longer lazily evaluated, instead authentication is always run, which results in more consistent, obvious behavior. Eg. Supplying bad auth credentials will now always return an error response, even if no permissions are set on the view.
* 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