From 9157db5da0b5601793d1a9f24e9cb10670a82be2 Mon Sep 17 00:00:00 2001 From: Stephan Groß Date: Tue, 26 Feb 2013 11:09:54 +0100 Subject: Add better date / datetime validation (pull 2) addition to #631 with update to master + timefield support --- rest_framework/fields.py | 87 ++++++++++++++++++++++++------------------------ 1 file changed, 44 insertions(+), 43 deletions(-) (limited to 'rest_framework/fields.py') diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 86c3a837..2260c430 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -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): -- cgit v1.2.3 From b2165cc76a97ee09c0ff109df2140e2950549a19 Mon Sep 17 00:00:00 2001 From: Stephan Groß Date: Tue, 26 Feb 2013 11:35:39 +0100 Subject: Fix for python 3 support - thanks @Linovia --- rest_framework/fields.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'rest_framework/fields.py') diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 2260c430..d38b726a 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -448,7 +448,7 @@ class DateField(WritableField): form_field_class = forms.DateField default_error_messages = { - 'invalid': _(u"Date has wrong format. Use one of these formats instead: %s"), + 'invalid': _("Date has wrong format. Use one of these formats instead: %s"), } empty = None @@ -489,7 +489,7 @@ class DateTimeField(WritableField): form_field_class = forms.DateTimeField default_error_messages = { - 'invalid': _(u"Datetime has wrong format. Use one of these formats instead: %s"), + 'invalid': _("Datetime has wrong format. Use one of these formats instead: %s"), } empty = None @@ -536,7 +536,7 @@ class TimeField(WritableField): form_field_class = forms.TimeField default_error_messages = { - 'invalid': _(u"Time has wrong format. Use one of these formats instead: %s"), + 'invalid': _("Time has wrong format. Use one of these formats instead: %s"), } empty = None -- cgit v1.2.3 From f208d8d2bbe2f418caa51199070f703fba544d49 Mon Sep 17 00:00:00 2001 From: Stephan Groß Date: Fri, 1 Mar 2013 13:16:00 +0100 Subject: Add drf settings + output format + testcases --- rest_framework/fields.py | 40 +++++++++++++++++++++++++++++----------- 1 file changed, 29 insertions(+), 11 deletions(-) (limited to 'rest_framework/fields.py') diff --git a/rest_framework/fields.py b/rest_framework/fields.py index d38b726a..bb77164a 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -13,12 +13,12 @@ from django import forms from django.forms import widgets from django.utils.encoding import is_protected_type from django.utils.translation import ugettext_lazy as _ -from rest_framework.compat import parse_date, parse_datetime + from rest_framework.compat import timezone 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.settings import api_settings from rest_framework.utils.dates import get_readable_date_format @@ -453,7 +453,8 @@ class DateField(WritableField): empty = None def __init__(self, *args, **kwargs): - self.format = kwargs.pop('format', settings.DATE_INPUT_FORMATS) + self.input_formats = kwargs.pop('input_formats', api_settings.DATE_INPUT_FORMATS) + self.output_format = kwargs.pop('output_format', api_settings.DATE_OUTPUT_FORMAT) super(DateField, self).__init__(*args, **kwargs) def from_native(self, value): @@ -470,7 +471,7 @@ class DateField(WritableField): if isinstance(value, datetime.date): return value - for format in self.format: + for format in self.input_formats: try: parsed = datetime.datetime.strptime(value, format) except (ValueError, TypeError): @@ -478,10 +479,15 @@ class DateField(WritableField): else: return parsed.date() - date_input_formats = '; '.join(self.format) + date_input_formats = '; '.join(self.input_formats) msg = self.error_messages['invalid'] % get_readable_date_format(date_input_formats) raise ValidationError(msg) + def to_native(self, value): + if self.output_format is not None: + return value.strftime(self.output_format) + return value.isoformat() + class DateTimeField(WritableField): type_name = 'DateTimeField' @@ -494,7 +500,8 @@ class DateTimeField(WritableField): empty = None def __init__(self, *args, **kwargs): - self.format = kwargs.pop('format', settings.DATETIME_INPUT_FORMATS) + self.input_formats = kwargs.pop('input_formats', api_settings.DATETIME_INPUT_FORMATS) + self.output_format = kwargs.pop('output_format', api_settings.DATETIME_OUTPUT_FORMAT) super(DateTimeField, self).__init__(*args, **kwargs) def from_native(self, value): @@ -517,7 +524,7 @@ class DateTimeField(WritableField): value = timezone.make_aware(value, default_timezone) return value - for format in self.format: + for format in self.input_formats: try: parsed = datetime.datetime.strptime(value, format) except (ValueError, TypeError): @@ -525,10 +532,15 @@ class DateTimeField(WritableField): else: return parsed - datetime_input_formats = '; '.join(self.format) + datetime_input_formats = '; '.join(self.input_formats) msg = self.error_messages['invalid'] % get_readable_date_format(datetime_input_formats) raise ValidationError(msg) + def to_native(self, value): + if self.output_format is not None: + return value.strftime(self.output_format) + return value.isoformat() + class TimeField(WritableField): type_name = 'TimeField' @@ -541,7 +553,8 @@ class TimeField(WritableField): empty = None def __init__(self, *args, **kwargs): - self.format = kwargs.pop('format', settings.TIME_INPUT_FORMATS) + self.input_formats = kwargs.pop('input_formats', api_settings.TIME_INPUT_FORMATS) + self.output_format = kwargs.pop('output_format', api_settings.TIME_OUTPUT_FORMAT) super(TimeField, self).__init__(*args, **kwargs) def from_native(self, value): @@ -551,7 +564,7 @@ class TimeField(WritableField): if isinstance(value, datetime.time): return value - for format in self.format: + for format in self.input_formats: try: parsed = datetime.datetime.strptime(value, format) except (ValueError, TypeError): @@ -559,10 +572,15 @@ class TimeField(WritableField): else: return parsed.time() - time_input_formats = '; '.join(self.format) + time_input_formats = '; '.join(self.input_formats) msg = self.error_messages['invalid'] % get_readable_date_format(time_input_formats) raise ValidationError(msg) + def to_native(self, value): + if self.output_format is not None: + return value.strftime(self.output_format) + return value.isoformat() + class IntegerField(WritableField): type_name = 'IntegerField' -- cgit v1.2.3 From a9d36d4726fc8eea02184b089ee6ed1d02e4c75e Mon Sep 17 00:00:00 2001 From: Stephan Groß Date: Fri, 1 Mar 2013 15:03:27 +0100 Subject: Add docs update - part 1 --- rest_framework/fields.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) (limited to 'rest_framework/fields.py') diff --git a/rest_framework/fields.py b/rest_framework/fields.py index bb77164a..3eaa532a 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -452,9 +452,9 @@ class DateField(WritableField): } empty = None - def __init__(self, *args, **kwargs): - self.input_formats = kwargs.pop('input_formats', api_settings.DATE_INPUT_FORMATS) - self.output_format = kwargs.pop('output_format', api_settings.DATE_OUTPUT_FORMAT) + def __init__(self, input_formats=None, output_format=None, *args, **kwargs): + self.input_formats = input_formats or api_settings.DATE_INPUT_FORMATS + self.output_format = output_format or api_settings.DATE_OUTPUT_FORMAT super(DateField, self).__init__(*args, **kwargs) def from_native(self, value): @@ -499,9 +499,9 @@ class DateTimeField(WritableField): } empty = None - def __init__(self, *args, **kwargs): - self.input_formats = kwargs.pop('input_formats', api_settings.DATETIME_INPUT_FORMATS) - self.output_format = kwargs.pop('output_format', api_settings.DATETIME_OUTPUT_FORMAT) + def __init__(self, input_formats=None, output_format=None, *args, **kwargs): + self.input_formats = input_formats or api_settings.DATETIME_INPUT_FORMATS + self.output_format = output_format or api_settings.DATETIME_OUTPUT_FORMAT super(DateTimeField, self).__init__(*args, **kwargs) def from_native(self, value): @@ -552,9 +552,9 @@ class TimeField(WritableField): } empty = None - def __init__(self, *args, **kwargs): - self.input_formats = kwargs.pop('input_formats', api_settings.TIME_INPUT_FORMATS) - self.output_format = kwargs.pop('output_format', api_settings.TIME_OUTPUT_FORMAT) + def __init__(self, input_formats=None, output_format=None, *args, **kwargs): + self.input_formats = input_formats or api_settings.TIME_INPUT_FORMATS + self.output_format = output_format or api_settings.TIME_OUTPUT_FORMAT super(TimeField, self).__init__(*args, **kwargs) def from_native(self, value): -- cgit v1.2.3 From 9c964cf37b6936e907d250306e15c3f116591e7c Mon Sep 17 00:00:00 2001 From: Stephan Groß Date: Fri, 1 Mar 2013 16:24:25 +0100 Subject: Add new ISO8601 setting + integration --- rest_framework/fields.py | 83 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 56 insertions(+), 27 deletions(-) (limited to 'rest_framework/fields.py') diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 3eaa532a..c3e83c5e 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -11,9 +11,11 @@ from django.core.exceptions import ValidationError from django.conf import settings from django import forms from django.forms import widgets +from django.utils.dateparse import parse_date, parse_datetime, parse_time from django.utils.encoding import is_protected_type from django.utils.translation import ugettext_lazy as _ +from rest_framework import ISO8601 from rest_framework.compat import timezone from rest_framework.compat import BytesIO from rest_framework.compat import six @@ -472,21 +474,30 @@ class DateField(WritableField): return value for format in self.input_formats: - try: - parsed = datetime.datetime.strptime(value, format) - except (ValueError, TypeError): - pass + if format.lower() == ISO8601: + try: + parsed = parse_date(value) + except (ValueError, TypeError): + pass + else: + if parsed is not None: + return parsed else: - return parsed.date() + try: + parsed = datetime.datetime.strptime(value, format) + except (ValueError, TypeError): + pass + else: + return parsed.date() - date_input_formats = '; '.join(self.input_formats) + date_input_formats = '; '.join(self.input_formats).replace(ISO8601, 'YYYY-MM-DD') msg = self.error_messages['invalid'] % get_readable_date_format(date_input_formats) raise ValidationError(msg) def to_native(self, value): - if self.output_format is not None: - return value.strftime(self.output_format) - return value.isoformat() + if self.output_format.lower() == ISO8601: + return value.isoformat() + return value.strftime(self.output_format) class DateTimeField(WritableField): @@ -525,21 +536,30 @@ class DateTimeField(WritableField): return value for format in self.input_formats: - try: - parsed = datetime.datetime.strptime(value, format) - except (ValueError, TypeError): - pass + if format.lower() == ISO8601: + try: + parsed = parse_datetime(value) + except (ValueError, TypeError): + pass + else: + if parsed is not None: + return parsed else: - return parsed + try: + parsed = datetime.datetime.strptime(value, format) + except (ValueError, TypeError): + pass + else: + return parsed - datetime_input_formats = '; '.join(self.input_formats) + datetime_input_formats = '; '.join(self.input_formats).replace(ISO8601, 'YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ]') msg = self.error_messages['invalid'] % get_readable_date_format(datetime_input_formats) raise ValidationError(msg) def to_native(self, value): - if self.output_format is not None: - return value.strftime(self.output_format) - return value.isoformat() + if self.output_format.lower() == ISO8601: + return value.isoformat() + return value.strftime(self.output_format) class TimeField(WritableField): @@ -565,21 +585,30 @@ class TimeField(WritableField): return value for format in self.input_formats: - try: - parsed = datetime.datetime.strptime(value, format) - except (ValueError, TypeError): - pass + if format.lower() == ISO8601: + try: + parsed = parse_time(value) + except (ValueError, TypeError): + pass + else: + if parsed is not None: + return parsed else: - return parsed.time() + try: + parsed = datetime.datetime.strptime(value, format) + except (ValueError, TypeError): + pass + else: + return parsed.time() - time_input_formats = '; '.join(self.input_formats) + time_input_formats = '; '.join(self.input_formats).replace(ISO8601, 'HH:MM[:ss[.uuuuuu]]') msg = self.error_messages['invalid'] % get_readable_date_format(time_input_formats) raise ValidationError(msg) def to_native(self, value): - if self.output_format is not None: - return value.strftime(self.output_format) - return value.isoformat() + if self.output_format.lower() == ISO8601: + return value.isoformat() + return value.strftime(self.output_format) class IntegerField(WritableField): -- cgit v1.2.3 From 12905449a5591cef2b2fc94d68cc273dc6df0463 Mon Sep 17 00:00:00 2001 From: Stephan Groß Date: Fri, 1 Mar 2013 16:59:47 +0100 Subject: Add format class attributes --- rest_framework/fields.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) (limited to 'rest_framework/fields.py') diff --git a/rest_framework/fields.py b/rest_framework/fields.py index c3e83c5e..21ab92ed 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -453,10 +453,12 @@ class DateField(WritableField): 'invalid': _("Date has wrong format. Use one of these formats instead: %s"), } empty = None + input_formats = api_settings.DATE_INPUT_FORMATS + output_format = api_settings.DATE_OUTPUT_FORMAT def __init__(self, input_formats=None, output_format=None, *args, **kwargs): - self.input_formats = input_formats or api_settings.DATE_INPUT_FORMATS - self.output_format = output_format or api_settings.DATE_OUTPUT_FORMAT + self.input_formats = input_formats if input_formats is not None else self.input_formats + self.output_format = output_format if output_format is not None else self.output_format super(DateField, self).__init__(*args, **kwargs) def from_native(self, value): @@ -509,10 +511,12 @@ class DateTimeField(WritableField): 'invalid': _("Datetime has wrong format. Use one of these formats instead: %s"), } empty = None + input_formats = api_settings.DATETIME_INPUT_FORMATS + output_format = api_settings.DATETIME_OUTPUT_FORMAT def __init__(self, input_formats=None, output_format=None, *args, **kwargs): - self.input_formats = input_formats or api_settings.DATETIME_INPUT_FORMATS - self.output_format = output_format or api_settings.DATETIME_OUTPUT_FORMAT + self.input_formats = input_formats if input_formats is not None else self.input_formats + self.output_format = output_format if output_format is not None else self.output_format super(DateTimeField, self).__init__(*args, **kwargs) def from_native(self, value): @@ -571,10 +575,12 @@ class TimeField(WritableField): 'invalid': _("Time has wrong format. Use one of these formats instead: %s"), } empty = None + input_formats = api_settings.TIME_INPUT_FORMATS + output_format = api_settings.TIME_OUTPUT_FORMAT def __init__(self, input_formats=None, output_format=None, *args, **kwargs): - self.input_formats = input_formats or api_settings.TIME_INPUT_FORMATS - self.output_format = output_format or api_settings.TIME_OUTPUT_FORMAT + self.input_formats = input_formats if input_formats is not None else self.input_formats + self.output_format = output_format if output_format is not None else self.output_format super(TimeField, self).__init__(*args, **kwargs) def from_native(self, value): -- cgit v1.2.3 From 5e5cd6f7f7357d68d5c10500ec0379e49d679202 Mon Sep 17 00:00:00 2001 From: Stephan Groß Date: Fri, 1 Mar 2013 17:15:39 +0100 Subject: Fix for django 1.3 compatibility --- rest_framework/fields.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'rest_framework/fields.py') diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 21ab92ed..87e0161f 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -11,12 +11,11 @@ from django.core.exceptions import ValidationError from django.conf import settings from django import forms from django.forms import widgets -from django.utils.dateparse import parse_date, parse_datetime, parse_time from django.utils.encoding import is_protected_type from django.utils.translation import ugettext_lazy as _ from rest_framework import ISO8601 -from rest_framework.compat import timezone +from rest_framework.compat import timezone, parse_date, parse_datetime, parse_time from rest_framework.compat import BytesIO from rest_framework.compat import six from rest_framework.compat import smart_text -- cgit v1.2.3