From bfff356dd36f7d14d35d8a854cd314e60cf25efa 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 --- docs/api-guide/fields.md | 12 ++ docs/topics/release-notes.md | 3 + rest_framework/fields.py | 87 ++++++----- rest_framework/tests/fields.py | 345 +++++++++++++++++++++++++++++++++++++++-- rest_framework/utils/dates.py | 14 ++ 5 files changed, 404 insertions(+), 57 deletions(-) create mode 100644 rest_framework/utils/dates.py diff --git a/docs/api-guide/fields.md b/docs/api-guide/fields.md index 8c28273b..a3dc4fe2 100644 --- a/docs/api-guide/fields.md +++ b/docs/api-guide/fields.md @@ -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 diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md index 06dc79a6..31ff68dd 100644 --- a/docs/topics/release-notes.md +++ b/docs/topics/release-notes.md @@ -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 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): diff --git a/rest_framework/tests/fields.py b/rest_framework/tests/fields.py index 34f61678..d21e247d 100644 --- a/rest_framework/tests/fields.py +++ b/rest_framework/tests/fields.py @@ -3,9 +3,13 @@ General serializer field tests. """ from __future__ import unicode_literals import datetime + +import django from django.db import models from django.test import TestCase from django.core import validators +from django.utils import unittest + from rest_framework import serializers @@ -18,6 +22,21 @@ class CharPrimaryKeyModel(models.Model): id = models.CharField(max_length=20, primary_key=True) +class DateObject(object): + def __init__(self, date): + self.date = date + + +class DateTimeObject(object): + def __init__(self, date_time): + self.date_time = date_time + + +class TimeObject(object): + def __init__(self, time): + self.time = time + + class TimestampedModelSerializer(serializers.ModelSerializer): class Meta: model = TimestampedModel @@ -28,6 +47,66 @@ class CharPrimaryKeyModelSerializer(serializers.ModelSerializer): model = CharPrimaryKeyModel +class DateObjectSerializer(serializers.Serializer): + date = serializers.DateField() + + def restore_object(self, attrs, instance=None): + if instance is not None: + instance.date = attrs['date'] + return instance + return DateObject(**attrs) + + +class DateObjectCustomFormatSerializer(serializers.Serializer): + date = serializers.DateField(format=("%Y", "%Y -- %m")) + + def restore_object(self, attrs, instance=None): + if instance is not None: + instance.date = attrs['date'] + return instance + return DateObject(**attrs) + + +class DateTimeObjectSerializer(serializers.Serializer): + date_time = serializers.DateTimeField() + + def restore_object(self, attrs, instance=None): + if instance is not None: + instance.date_time = attrs['date_time'] + return instance + return DateTimeObject(**attrs) + + +class DateTimeObjectCustomFormatSerializer(serializers.Serializer): + date_time = serializers.DateTimeField(format=("%Y", "%Y %H:%M")) + + def restore_object(self, attrs, instance=None): + if instance is not None: + instance.date_time = attrs['date_time'] + return instance + return DateTimeObject(**attrs) + + +class TimeObjectSerializer(serializers.Serializer): + time = serializers.TimeField() + + def restore_object(self, attrs, instance=None): + if instance is not None: + instance.time = attrs['time'] + return instance + return TimeObject(**attrs) + + +class TimeObjectCustomFormatSerializer(serializers.Serializer): + time = serializers.TimeField(format=("%H -- %M", "%H%M%S")) + + def restore_object(self, attrs, instance=None): + if instance is not None: + instance.time = attrs['time'] + return instance + return TimeObject(**attrs) + + class TimeFieldModel(models.Model): clock = models.TimeField() @@ -59,37 +138,275 @@ class BasicFieldTests(TestCase): serializer = CharPrimaryKeyModelSerializer() self.assertEquals(serializer.fields['id'].read_only, False) - def test_TimeField_from_native(self): + +class DateFieldTest(TestCase): + def test_valid_default_date_input_formats(self): + serializer = DateObjectSerializer(data={'date': '1984-07-31'}) + self.assertTrue(serializer.is_valid()) + + serializer = DateObjectSerializer(data={'date': '07/31/1984'}) + self.assertTrue(serializer.is_valid()) + + serializer = DateObjectSerializer(data={'date': '07/31/84'}) + self.assertTrue(serializer.is_valid()) + + serializer = DateObjectSerializer(data={'date': 'Jul 31 1984'}) + self.assertTrue(serializer.is_valid()) + + serializer = DateObjectSerializer(data={'date': 'Jul 31, 1984'}) + self.assertTrue(serializer.is_valid()) + + serializer = DateObjectSerializer(data={'date': '31 Jul 1984'}) + self.assertTrue(serializer.is_valid()) + + serializer = DateObjectSerializer(data={'date': '31 Jul 1984'}) + self.assertTrue(serializer.is_valid()) + + serializer = DateObjectSerializer(data={'date': 'July 31 1984'}) + self.assertTrue(serializer.is_valid()) + + serializer = DateObjectSerializer(data={'date': 'July 31, 1984'}) + self.assertTrue(serializer.is_valid()) + + serializer = DateObjectSerializer(data={'date': '31 July 1984'}) + self.assertTrue(serializer.is_valid()) + + serializer = DateObjectSerializer(data={'date': '31 July, 1984'}) + self.assertTrue(serializer.is_valid()) + + def test_valid_custom_date_input_formats(self): + serializer = DateObjectCustomFormatSerializer(data={'date': '1984'}) + self.assertTrue(serializer.is_valid()) + + serializer = DateObjectCustomFormatSerializer(data={'date': '1984 -- 07'}) + self.assertTrue(serializer.is_valid()) + + def test_wrong_default_date_input_format(self): + serializer = DateObjectSerializer(data={'date': 'something wrong'}) + self.assertFalse(serializer.is_valid()) + self.assertEquals(serializer.errors, {'date': [u'Date has wrong format. Use one of these formats instead: ' + u'YYYY-MM-DD; MM/DD/YYYY; MM/DD/YY; [Jan through Dec] DD YYYY; ' + u'[Jan through Dec] DD, YYYY; DD [Jan through Dec] YYYY; ' + u'DD [Jan through Dec], YYYY; [January through December] DD YYYY; ' + u'[January through December] DD, YYYY; DD [January through December] YYYY; ' + u'DD [January through December], YYYY']}) + + def test_wrong_custom_date_input_format(self): + serializer = DateObjectCustomFormatSerializer(data={'date': '07/31/1984'}) + self.assertFalse(serializer.is_valid()) + self.assertEquals(serializer.errors, {'date': [u'Date has wrong format. Use one of these formats instead: YYYY; YYYY -- MM']}) + + def test_from_native(self): + f = serializers.DateField() + result = f.from_native('1984-07-31') + + self.assertEqual(datetime.date(1984, 7, 31), result) + + def test_from_native_datetime_date(self): + """ + Make sure from_native() accepts a datetime.date instance. + """ + f = serializers.DateField() + result = f.from_native(datetime.date(1984, 7, 31)) + + self.assertEqual(result, datetime.date(1984, 7, 31)) + + def test_from_native_empty(self): + f = serializers.DateField() + result = f.from_native('') + + self.assertEqual(result, None) + + def test_from_native_invalid_date(self): + f = serializers.DateField() + + try: + f.from_native('1984-42-31') + except validators.ValidationError as e: + self.assertEqual(e.messages, [u'Date has wrong format. Use one of these formats instead: ' + u'YYYY-MM-DD; MM/DD/YYYY; MM/DD/YY; [Jan through Dec] DD YYYY; ' + u'[Jan through Dec] DD, YYYY; DD [Jan through Dec] YYYY; ' + u'DD [Jan through Dec], YYYY; [January through December] DD YYYY; ' + u'[January through December] DD, YYYY; DD [January through December] YYYY; ' + u'DD [January through December], YYYY']) + else: + self.fail("ValidationError was not properly raised") + + +class DateTimeFieldTest(TestCase): + def test_valid_default_date_time_input_formats(self): + serializer = DateTimeObjectSerializer(data={'date_time': '1984-07-31 04:31:59'}) + self.assertTrue(serializer.is_valid()) + + serializer = DateTimeObjectSerializer(data={'date_time': '1984-07-31 04:31'}) + self.assertTrue(serializer.is_valid()) + + serializer = DateTimeObjectSerializer(data={'date_time': '1984-07-31'}) + self.assertTrue(serializer.is_valid()) + + serializer = DateTimeObjectSerializer(data={'date_time': '07/31/1984 04:31:59'}) + self.assertTrue(serializer.is_valid()) + + serializer = DateTimeObjectSerializer(data={'date_time': '07/31/1984 04:31'}) + self.assertTrue(serializer.is_valid()) + + serializer = DateTimeObjectSerializer(data={'date_time': '07/31/1984'}) + self.assertTrue(serializer.is_valid()) + + serializer = DateTimeObjectSerializer(data={'date_time': '07/31/84 04:31:59'}) + self.assertTrue(serializer.is_valid()) + + serializer = DateTimeObjectSerializer(data={'date_time': '07/31/84 04:31'}) + self.assertTrue(serializer.is_valid()) + + serializer = DateTimeObjectSerializer(data={'date_time': '07/31/84'}) + self.assertTrue(serializer.is_valid()) + + @unittest.skipUnless(django.VERSION >= (1, 4), "django < 1.4 don't have microseconds in default settings") + def test_valid_default_date_time_input_formats_for_django_gte_1_4(self): + serializer = DateTimeObjectSerializer(data={'date_time': '1984-07-31 04:31:59.123456'}) + self.assertTrue(serializer.is_valid()) + + serializer = DateTimeObjectSerializer(data={'date_time': '07/31/1984 04:31:59.123456'}) + self.assertTrue(serializer.is_valid()) + + serializer = DateTimeObjectSerializer(data={'date_time': '07/31/84 04:31:59.123456'}) + self.assertTrue(serializer.is_valid()) + + def test_valid_custom_date_time_input_formats(self): + serializer = DateTimeObjectCustomFormatSerializer(data={'date_time': '1984'}) + self.assertTrue(serializer.is_valid()) + + serializer = DateTimeObjectCustomFormatSerializer(data={'date_time': '1984 04:31'}) + self.assertTrue(serializer.is_valid()) + + @unittest.skipUnless(django.VERSION >= (1, 4), "django < 1.4 don't have microseconds in default settings") + def test_wrong_default_date_time_input_format_for_django_gte_1_4(self): + serializer = DateTimeObjectSerializer(data={'date_time': 'something wrong'}) + self.assertFalse(serializer.is_valid()) + self.assertEquals(serializer.errors, {'date_time': [u'Datetime has wrong format. Use one of these formats instead: ' + u'YYYY-MM-DD HH:MM:SS; YYYY-MM-DD HH:MM:SS.uuuuuu; YYYY-MM-DD HH:MM; ' + u'YYYY-MM-DD; MM/DD/YYYY HH:MM:SS; MM/DD/YYYY HH:MM:SS.uuuuuu; ' + u'MM/DD/YYYY HH:MM; MM/DD/YYYY; MM/DD/YY HH:MM:SS; ' + u'MM/DD/YY HH:MM:SS.uuuuuu; MM/DD/YY HH:MM; MM/DD/YY']}) + + @unittest.skipUnless(django.VERSION < (1, 4), "django >= 1.4 have microseconds in default settings") + def test_wrong_default_date_time_input_format_for_django_lt_1_4(self): + serializer = DateTimeObjectSerializer(data={'date_time': 'something wrong'}) + self.assertFalse(serializer.is_valid()) + self.assertEquals(serializer.errors, {'date_time': [u'Datetime has wrong format. Use one of these formats instead:' + u' YYYY-MM-DD HH:MM:SS; YYYY-MM-DD HH:MM; YYYY-MM-DD; ' + u'MM/DD/YYYY HH:MM:SS; MM/DD/YYYY HH:MM; MM/DD/YYYY; ' + u'MM/DD/YY HH:MM:SS; MM/DD/YY HH:MM; MM/DD/YY']}) + + def test_wrong_custom_date_time_input_format(self): + serializer = DateTimeObjectCustomFormatSerializer(data={'date_time': '07/31/84 04:31'}) + self.assertFalse(serializer.is_valid()) + self.assertEquals(serializer.errors, {'date_time': [u'Datetime has wrong format. Use one of these formats instead: YYYY; YYYY HH:MM']}) + + def test_from_native(self): + f = serializers.DateTimeField() + result = f.from_native('1984-07-31 04:31') + + self.assertEqual(datetime.datetime(1984, 7, 31, 4, 31), result) + + def test_from_native_datetime_datetime(self): + """ + Make sure from_native() accepts a datetime.date instance. + """ + f = serializers.DateTimeField() + result = f.from_native(datetime.datetime(1984, 7, 31)) + + self.assertEqual(result, datetime.datetime(1984, 7, 31)) + + def test_from_native_empty(self): + f = serializers.DateTimeField() + result = f.from_native('') + + self.assertEqual(result, None) + + @unittest.skipUnless(django.VERSION >= (1, 4), "django < 1.4 don't have microseconds in default settings") + def test_from_native_invalid_datetime(self): + f = serializers.DateTimeField() + + try: + f.from_native('1984-42-31 04:31') + except validators.ValidationError as e: + self.assertEqual(e.messages, [u'Datetime has wrong format. Use one of these formats instead: ' + u'YYYY-MM-DD HH:MM:SS; YYYY-MM-DD HH:MM:SS.uuuuuu; YYYY-MM-DD HH:MM; ' + u'YYYY-MM-DD; MM/DD/YYYY HH:MM:SS; MM/DD/YYYY HH:MM:SS.uuuuuu; ' + u'MM/DD/YYYY HH:MM; MM/DD/YYYY; MM/DD/YY HH:MM:SS; ' + u'MM/DD/YY HH:MM:SS.uuuuuu; MM/DD/YY HH:MM; MM/DD/YY']) + else: + self.fail("ValidationError was not properly raised") + + @unittest.skipUnless(django.VERSION < (1, 4), "django >= 1.4 have microseconds in default settings") + def test_from_native_invalid_datetime(self): + f = serializers.DateTimeField() + + try: + f.from_native('1984-42-31 04:31') + except validators.ValidationError as e: + self.assertEqual(e.messages, [u'Datetime has wrong format. Use one of these formats instead:' + u' YYYY-MM-DD HH:MM:SS; YYYY-MM-DD HH:MM; YYYY-MM-DD; ' + u'MM/DD/YYYY HH:MM:SS; MM/DD/YYYY HH:MM; MM/DD/YYYY; ' + u'MM/DD/YY HH:MM:SS; MM/DD/YY HH:MM; MM/DD/YY']) + else: + self.fail("ValidationError was not properly raised") + + +class TimeFieldTest(TestCase): + def test_valid_default_time_input_formats(self): + serializer = TimeObjectSerializer(data={'time': '04:31'}) + self.assertTrue(serializer.is_valid()) + + serializer = TimeObjectSerializer(data={'time': '04:31:59'}) + self.assertTrue(serializer.is_valid()) + + def test_valid_custom_time_input_formats(self): + serializer = TimeObjectCustomFormatSerializer(data={'time': '04 -- 31'}) + self.assertTrue(serializer.is_valid()) + + serializer = TimeObjectCustomFormatSerializer(data={'time': '043159'}) + self.assertTrue(serializer.is_valid()) + + def test_wrong_default_time_input_format(self): + serializer = TimeObjectSerializer(data={'time': 'something wrong'}) + self.assertFalse(serializer.is_valid()) + self.assertEquals(serializer.errors, {'time': [u'Time has wrong format. Use one of these formats instead: HH:MM:SS; HH:MM']}) + + def test_wrong_custom_time_input_format(self): + serializer = TimeObjectCustomFormatSerializer(data={'time': '04:31'}) + self.assertFalse(serializer.is_valid()) + self.assertEquals(serializer.errors, {'time': [u'Time has wrong format. Use one of these formats instead: HH -- MM; HHMMSS']}) + + def test_from_native(self): f = serializers.TimeField() - result = f.from_native('12:34:56.987654') + result = f.from_native('12:34:56') - self.assertEqual(datetime.time(12, 34, 56, 987654), result) + self.assertEqual(datetime.time(12, 34, 56), result) - def test_TimeField_from_native_datetime_time(self): + def test_from_native_datetime_time(self): """ Make sure from_native() accepts a datetime.time instance. """ f = serializers.TimeField() result = f.from_native(datetime.time(12, 34, 56)) + self.assertEqual(result, datetime.time(12, 34, 56)) - def test_TimeField_from_native_empty(self): + def test_from_native_empty(self): f = serializers.TimeField() result = f.from_native('') + self.assertEqual(result, None) - def test_TimeField_from_native_invalid_time(self): + def test_from_native_invalid_time(self): f = serializers.TimeField() try: f.from_native('12:69:12') except validators.ValidationError as e: - self.assertEqual(e.messages, ["'12:69:12' value has an invalid " - "format. It must be a valid time " - "in the HH:MM[:ss[.uuuuuu]] format."]) + self.assertEqual(e.messages, ["Time has wrong format. Use one of these formats instead: HH:MM:SS; HH:MM"]) else: - self.fail("ValidationError was not properly raised") - - def test_TimeFieldModelSerializer(self): - serializer = TimeFieldModelSerializer() - self.assertTrue(isinstance(serializer.fields['clock'], serializers.TimeField)) + self.fail("ValidationError was not properly raised") \ No newline at end of file diff --git a/rest_framework/utils/dates.py b/rest_framework/utils/dates.py new file mode 100644 index 00000000..f094f72d --- /dev/null +++ b/rest_framework/utils/dates.py @@ -0,0 +1,14 @@ +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 -- cgit v1.2.3 From 956a65f5af2bfc19313dcc7c104eed3ff8cc58d0 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(-) 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 36d5b24b76af2d98efff9e8f414ec36e2a902994 Mon Sep 17 00:00:00 2001 From: Stephan Groß Date: Tue, 26 Feb 2013 11:42:50 +0100 Subject: Fix for python 3 support --- rest_framework/tests/fields.py | 72 +++++++++++++++++++++--------------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/rest_framework/tests/fields.py b/rest_framework/tests/fields.py index d21e247d..e76079c9 100644 --- a/rest_framework/tests/fields.py +++ b/rest_framework/tests/fields.py @@ -184,17 +184,17 @@ class DateFieldTest(TestCase): def test_wrong_default_date_input_format(self): serializer = DateObjectSerializer(data={'date': 'something wrong'}) self.assertFalse(serializer.is_valid()) - self.assertEquals(serializer.errors, {'date': [u'Date has wrong format. Use one of these formats instead: ' - u'YYYY-MM-DD; MM/DD/YYYY; MM/DD/YY; [Jan through Dec] DD YYYY; ' - u'[Jan through Dec] DD, YYYY; DD [Jan through Dec] YYYY; ' - u'DD [Jan through Dec], YYYY; [January through December] DD YYYY; ' - u'[January through December] DD, YYYY; DD [January through December] YYYY; ' - u'DD [January through December], YYYY']}) + self.assertEquals(serializer.errors, {'date': ['Date has wrong format. Use one of these formats instead: ' + 'YYYY-MM-DD; MM/DD/YYYY; MM/DD/YY; [Jan through Dec] DD YYYY; ' + '[Jan through Dec] DD, YYYY; DD [Jan through Dec] YYYY; ' + 'DD [Jan through Dec], YYYY; [January through December] DD YYYY; ' + '[January through December] DD, YYYY; DD [January through December] YYYY; ' + 'DD [January through December], YYYY']}) def test_wrong_custom_date_input_format(self): serializer = DateObjectCustomFormatSerializer(data={'date': '07/31/1984'}) self.assertFalse(serializer.is_valid()) - self.assertEquals(serializer.errors, {'date': [u'Date has wrong format. Use one of these formats instead: YYYY; YYYY -- MM']}) + self.assertEquals(serializer.errors, {'date': ['Date has wrong format. Use one of these formats instead: YYYY; YYYY -- MM']}) def test_from_native(self): f = serializers.DateField() @@ -223,12 +223,12 @@ class DateFieldTest(TestCase): try: f.from_native('1984-42-31') except validators.ValidationError as e: - self.assertEqual(e.messages, [u'Date has wrong format. Use one of these formats instead: ' - u'YYYY-MM-DD; MM/DD/YYYY; MM/DD/YY; [Jan through Dec] DD YYYY; ' - u'[Jan through Dec] DD, YYYY; DD [Jan through Dec] YYYY; ' - u'DD [Jan through Dec], YYYY; [January through December] DD YYYY; ' - u'[January through December] DD, YYYY; DD [January through December] YYYY; ' - u'DD [January through December], YYYY']) + self.assertEqual(e.messages, ['Date has wrong format. Use one of these formats instead: ' + 'YYYY-MM-DD; MM/DD/YYYY; MM/DD/YY; [Jan through Dec] DD YYYY; ' + '[Jan through Dec] DD, YYYY; DD [Jan through Dec] YYYY; ' + 'DD [Jan through Dec], YYYY; [January through December] DD YYYY; ' + '[January through December] DD, YYYY; DD [January through December] YYYY; ' + 'DD [January through December], YYYY']) else: self.fail("ValidationError was not properly raised") @@ -284,25 +284,25 @@ class DateTimeFieldTest(TestCase): def test_wrong_default_date_time_input_format_for_django_gte_1_4(self): serializer = DateTimeObjectSerializer(data={'date_time': 'something wrong'}) self.assertFalse(serializer.is_valid()) - self.assertEquals(serializer.errors, {'date_time': [u'Datetime has wrong format. Use one of these formats instead: ' - u'YYYY-MM-DD HH:MM:SS; YYYY-MM-DD HH:MM:SS.uuuuuu; YYYY-MM-DD HH:MM; ' - u'YYYY-MM-DD; MM/DD/YYYY HH:MM:SS; MM/DD/YYYY HH:MM:SS.uuuuuu; ' - u'MM/DD/YYYY HH:MM; MM/DD/YYYY; MM/DD/YY HH:MM:SS; ' - u'MM/DD/YY HH:MM:SS.uuuuuu; MM/DD/YY HH:MM; MM/DD/YY']}) + self.assertEquals(serializer.errors, {'date_time': ['Datetime has wrong format. Use one of these formats instead: ' + 'YYYY-MM-DD HH:MM:SS; YYYY-MM-DD HH:MM:SS.uuuuuu; YYYY-MM-DD HH:MM; ' + 'YYYY-MM-DD; MM/DD/YYYY HH:MM:SS; MM/DD/YYYY HH:MM:SS.uuuuuu; ' + 'MM/DD/YYYY HH:MM; MM/DD/YYYY; MM/DD/YY HH:MM:SS; ' + 'MM/DD/YY HH:MM:SS.uuuuuu; MM/DD/YY HH:MM; MM/DD/YY']}) @unittest.skipUnless(django.VERSION < (1, 4), "django >= 1.4 have microseconds in default settings") def test_wrong_default_date_time_input_format_for_django_lt_1_4(self): serializer = DateTimeObjectSerializer(data={'date_time': 'something wrong'}) self.assertFalse(serializer.is_valid()) - self.assertEquals(serializer.errors, {'date_time': [u'Datetime has wrong format. Use one of these formats instead:' - u' YYYY-MM-DD HH:MM:SS; YYYY-MM-DD HH:MM; YYYY-MM-DD; ' - u'MM/DD/YYYY HH:MM:SS; MM/DD/YYYY HH:MM; MM/DD/YYYY; ' - u'MM/DD/YY HH:MM:SS; MM/DD/YY HH:MM; MM/DD/YY']}) + self.assertEquals(serializer.errors, {'date_time': ['Datetime has wrong format. Use one of these formats instead: ' + 'YYYY-MM-DD HH:MM:SS; YYYY-MM-DD HH:MM; YYYY-MM-DD; ' + 'MM/DD/YYYY HH:MM:SS; MM/DD/YYYY HH:MM; MM/DD/YYYY; ' + 'MM/DD/YY HH:MM:SS; MM/DD/YY HH:MM; MM/DD/YY']}) def test_wrong_custom_date_time_input_format(self): serializer = DateTimeObjectCustomFormatSerializer(data={'date_time': '07/31/84 04:31'}) self.assertFalse(serializer.is_valid()) - self.assertEquals(serializer.errors, {'date_time': [u'Datetime has wrong format. Use one of these formats instead: YYYY; YYYY HH:MM']}) + self.assertEquals(serializer.errors, {'date_time': ['Datetime has wrong format. Use one of these formats instead: YYYY; YYYY HH:MM']}) def test_from_native(self): f = serializers.DateTimeField() @@ -326,31 +326,31 @@ class DateTimeFieldTest(TestCase): self.assertEqual(result, None) @unittest.skipUnless(django.VERSION >= (1, 4), "django < 1.4 don't have microseconds in default settings") - def test_from_native_invalid_datetime(self): + def test_from_native_invalid_datetime_for_django_gte_1_4(self): f = serializers.DateTimeField() try: f.from_native('1984-42-31 04:31') except validators.ValidationError as e: - self.assertEqual(e.messages, [u'Datetime has wrong format. Use one of these formats instead: ' - u'YYYY-MM-DD HH:MM:SS; YYYY-MM-DD HH:MM:SS.uuuuuu; YYYY-MM-DD HH:MM; ' - u'YYYY-MM-DD; MM/DD/YYYY HH:MM:SS; MM/DD/YYYY HH:MM:SS.uuuuuu; ' - u'MM/DD/YYYY HH:MM; MM/DD/YYYY; MM/DD/YY HH:MM:SS; ' - u'MM/DD/YY HH:MM:SS.uuuuuu; MM/DD/YY HH:MM; MM/DD/YY']) + self.assertEqual(e.messages, ['Datetime has wrong format. Use one of these formats instead: ' + 'YYYY-MM-DD HH:MM:SS; YYYY-MM-DD HH:MM:SS.uuuuuu; YYYY-MM-DD HH:MM; ' + 'YYYY-MM-DD; MM/DD/YYYY HH:MM:SS; MM/DD/YYYY HH:MM:SS.uuuuuu; ' + 'MM/DD/YYYY HH:MM; MM/DD/YYYY; MM/DD/YY HH:MM:SS; ' + 'MM/DD/YY HH:MM:SS.uuuuuu; MM/DD/YY HH:MM; MM/DD/YY']) else: self.fail("ValidationError was not properly raised") @unittest.skipUnless(django.VERSION < (1, 4), "django >= 1.4 have microseconds in default settings") - def test_from_native_invalid_datetime(self): + def test_from_native_invalid_datetime_for_django_lt_1_4(self): f = serializers.DateTimeField() try: f.from_native('1984-42-31 04:31') except validators.ValidationError as e: - self.assertEqual(e.messages, [u'Datetime has wrong format. Use one of these formats instead:' - u' YYYY-MM-DD HH:MM:SS; YYYY-MM-DD HH:MM; YYYY-MM-DD; ' - u'MM/DD/YYYY HH:MM:SS; MM/DD/YYYY HH:MM; MM/DD/YYYY; ' - u'MM/DD/YY HH:MM:SS; MM/DD/YY HH:MM; MM/DD/YY']) + self.assertEqual(e.messages, ['Datetime has wrong format. Use one of these formats instead: ' + 'YYYY-MM-DD HH:MM:SS; YYYY-MM-DD HH:MM; YYYY-MM-DD; ' + 'MM/DD/YYYY HH:MM:SS; MM/DD/YYYY HH:MM; MM/DD/YYYY; ' + 'MM/DD/YY HH:MM:SS; MM/DD/YY HH:MM; MM/DD/YY']) else: self.fail("ValidationError was not properly raised") @@ -373,12 +373,12 @@ class TimeFieldTest(TestCase): def test_wrong_default_time_input_format(self): serializer = TimeObjectSerializer(data={'time': 'something wrong'}) self.assertFalse(serializer.is_valid()) - self.assertEquals(serializer.errors, {'time': [u'Time has wrong format. Use one of these formats instead: HH:MM:SS; HH:MM']}) + self.assertEquals(serializer.errors, {'time': ['Time has wrong format. Use one of these formats instead: HH:MM:SS; HH:MM']}) def test_wrong_custom_time_input_format(self): serializer = TimeObjectCustomFormatSerializer(data={'time': '04:31'}) self.assertFalse(serializer.is_valid()) - self.assertEquals(serializer.errors, {'time': [u'Time has wrong format. Use one of these formats instead: HH -- MM; HHMMSS']}) + self.assertEquals(serializer.errors, {'time': ['Time has wrong format. Use one of these formats instead: HH -- MM; HHMMSS']}) def test_from_native(self): f = serializers.TimeField() -- cgit v1.2.3 From 876a58e8bf566b9f05a1f6c921660cf3512ae3af 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 ++- rest_framework/settings.py | 21 ++ rest_framework/tests/fields.py | 503 ++++++++++++++++++------------------- rest_framework/tests/filterset.py | 13 +- rest_framework/tests/pagination.py | 4 +- rest_framework/tests/serializer.py | 2 +- 6 files changed, 310 insertions(+), 273 deletions(-) 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' diff --git a/rest_framework/settings.py b/rest_framework/settings.py index b7aa0bbe..717496ea 100644 --- a/rest_framework/settings.py +++ b/rest_framework/settings.py @@ -76,6 +76,27 @@ DEFAULTS = { 'URL_FORMAT_OVERRIDE': 'format', 'FORMAT_SUFFIX_KWARG': 'format', + + # Input and output formats + 'DATE_INPUT_FORMATS': ( + '%Y-%m-%d', # '1984-07-31' + ), + 'DATE_OUTPUT_FORMAT': None, + + 'DATETIME_INPUT_FORMATS': ( + '%Y-%m-%d', # '1984-07-31' + '%Y-%m-%d %H:%M', # '1984-07-31 04:31' + '%Y-%m-%d %H:%M:%S', # '1984-07-31 04:31:59' + '%Y-%m-%d %H:%M:%S.%f', # '1984-07-31 04:31:59.000200' + ), + 'DATETIME_OUTPUT_FORMAT': None, + + 'TIME_INPUT_FORMATS': ( + '%H:%M', # '04:31' + '%H:%M:%S', # '04:31:59' + '%H:%M:%S.%f', # '04:31:59.000200' + ), + 'TIME_OUTPUT_FORMAT': None, } diff --git a/rest_framework/tests/fields.py b/rest_framework/tests/fields.py index e76079c9..ddedd6f9 100644 --- a/rest_framework/tests/fields.py +++ b/rest_framework/tests/fields.py @@ -4,11 +4,9 @@ General serializer field tests. from __future__ import unicode_literals import datetime -import django from django.db import models from django.test import TestCase from django.core import validators -from django.utils import unittest from rest_framework import serializers @@ -22,21 +20,6 @@ class CharPrimaryKeyModel(models.Model): id = models.CharField(max_length=20, primary_key=True) -class DateObject(object): - def __init__(self, date): - self.date = date - - -class DateTimeObject(object): - def __init__(self, date_time): - self.date_time = date_time - - -class TimeObject(object): - def __init__(self, time): - self.time = time - - class TimestampedModelSerializer(serializers.ModelSerializer): class Meta: model = TimestampedModel @@ -47,66 +30,6 @@ class CharPrimaryKeyModelSerializer(serializers.ModelSerializer): model = CharPrimaryKeyModel -class DateObjectSerializer(serializers.Serializer): - date = serializers.DateField() - - def restore_object(self, attrs, instance=None): - if instance is not None: - instance.date = attrs['date'] - return instance - return DateObject(**attrs) - - -class DateObjectCustomFormatSerializer(serializers.Serializer): - date = serializers.DateField(format=("%Y", "%Y -- %m")) - - def restore_object(self, attrs, instance=None): - if instance is not None: - instance.date = attrs['date'] - return instance - return DateObject(**attrs) - - -class DateTimeObjectSerializer(serializers.Serializer): - date_time = serializers.DateTimeField() - - def restore_object(self, attrs, instance=None): - if instance is not None: - instance.date_time = attrs['date_time'] - return instance - return DateTimeObject(**attrs) - - -class DateTimeObjectCustomFormatSerializer(serializers.Serializer): - date_time = serializers.DateTimeField(format=("%Y", "%Y %H:%M")) - - def restore_object(self, attrs, instance=None): - if instance is not None: - instance.date_time = attrs['date_time'] - return instance - return DateTimeObject(**attrs) - - -class TimeObjectSerializer(serializers.Serializer): - time = serializers.TimeField() - - def restore_object(self, attrs, instance=None): - if instance is not None: - instance.time = attrs['time'] - return instance - return TimeObject(**attrs) - - -class TimeObjectCustomFormatSerializer(serializers.Serializer): - time = serializers.TimeField(format=("%H -- %M", "%H%M%S")) - - def restore_object(self, attrs, instance=None): - if instance is not None: - instance.time = attrs['time'] - return instance - return TimeObject(**attrs) - - class TimeFieldModel(models.Model): clock = models.TimeField() @@ -140,273 +63,347 @@ class BasicFieldTests(TestCase): class DateFieldTest(TestCase): - def test_valid_default_date_input_formats(self): - serializer = DateObjectSerializer(data={'date': '1984-07-31'}) - self.assertTrue(serializer.is_valid()) - - serializer = DateObjectSerializer(data={'date': '07/31/1984'}) - self.assertTrue(serializer.is_valid()) - - serializer = DateObjectSerializer(data={'date': '07/31/84'}) - self.assertTrue(serializer.is_valid()) - - serializer = DateObjectSerializer(data={'date': 'Jul 31 1984'}) - self.assertTrue(serializer.is_valid()) - - serializer = DateObjectSerializer(data={'date': 'Jul 31, 1984'}) - self.assertTrue(serializer.is_valid()) - - serializer = DateObjectSerializer(data={'date': '31 Jul 1984'}) - self.assertTrue(serializer.is_valid()) - - serializer = DateObjectSerializer(data={'date': '31 Jul 1984'}) - self.assertTrue(serializer.is_valid()) - - serializer = DateObjectSerializer(data={'date': 'July 31 1984'}) - self.assertTrue(serializer.is_valid()) - - serializer = DateObjectSerializer(data={'date': 'July 31, 1984'}) - self.assertTrue(serializer.is_valid()) - - serializer = DateObjectSerializer(data={'date': '31 July 1984'}) - self.assertTrue(serializer.is_valid()) - - serializer = DateObjectSerializer(data={'date': '31 July, 1984'}) - self.assertTrue(serializer.is_valid()) - - def test_valid_custom_date_input_formats(self): - serializer = DateObjectCustomFormatSerializer(data={'date': '1984'}) - self.assertTrue(serializer.is_valid()) - - serializer = DateObjectCustomFormatSerializer(data={'date': '1984 -- 07'}) - self.assertTrue(serializer.is_valid()) + """ + Tests for the DateFieldTest from_native() and to_native() behavior + """ - def test_wrong_default_date_input_format(self): - serializer = DateObjectSerializer(data={'date': 'something wrong'}) - self.assertFalse(serializer.is_valid()) - self.assertEquals(serializer.errors, {'date': ['Date has wrong format. Use one of these formats instead: ' - 'YYYY-MM-DD; MM/DD/YYYY; MM/DD/YY; [Jan through Dec] DD YYYY; ' - '[Jan through Dec] DD, YYYY; DD [Jan through Dec] YYYY; ' - 'DD [Jan through Dec], YYYY; [January through December] DD YYYY; ' - '[January through December] DD, YYYY; DD [January through December] YYYY; ' - 'DD [January through December], YYYY']}) - - def test_wrong_custom_date_input_format(self): - serializer = DateObjectCustomFormatSerializer(data={'date': '07/31/1984'}) - self.assertFalse(serializer.is_valid()) - self.assertEquals(serializer.errors, {'date': ['Date has wrong format. Use one of these formats instead: YYYY; YYYY -- MM']}) - - def test_from_native(self): + def test_from_native_string(self): + """ + Make sure from_native() accepts default iso input formats. + """ f = serializers.DateField() - result = f.from_native('1984-07-31') + result_1 = f.from_native('1984-07-31') - self.assertEqual(datetime.date(1984, 7, 31), result) + self.assertEqual(datetime.date(1984, 7, 31), result_1) def test_from_native_datetime_date(self): """ Make sure from_native() accepts a datetime.date instance. """ f = serializers.DateField() - result = f.from_native(datetime.date(1984, 7, 31)) + result_1 = f.from_native(datetime.date(1984, 7, 31)) + + self.assertEqual(result_1, datetime.date(1984, 7, 31)) + + def test_from_native_custom_format(self): + """ + Make sure from_native() accepts custom input formats. + """ + f = serializers.DateField(input_formats=['%Y -- %d']) + result = f.from_native('1984 -- 31') + + self.assertEqual(datetime.date(1984, 1, 31), result) - self.assertEqual(result, datetime.date(1984, 7, 31)) + def test_from_native_invalid_default_on_custom_format(self): + """ + Make sure from_native() don't accept default formats if custom format is preset + """ + f = serializers.DateField(input_formats=['%Y -- %d']) + + try: + f.from_native('1984-07-31') + except validators.ValidationError as e: + self.assertEqual(e.messages, ["Date has wrong format. Use one of these formats instead: YYYY -- DD"]) + else: + self.fail("ValidationError was not properly raised") def test_from_native_empty(self): + """ + Make sure from_native() returns None on empty param. + """ f = serializers.DateField() result = f.from_native('') self.assertEqual(result, None) def test_from_native_invalid_date(self): + """ + Make sure from_native() raises a ValidationError on passing an invalid date. + """ f = serializers.DateField() try: - f.from_native('1984-42-31') + f.from_native('1984-13-31') except validators.ValidationError as e: - self.assertEqual(e.messages, ['Date has wrong format. Use one of these formats instead: ' - 'YYYY-MM-DD; MM/DD/YYYY; MM/DD/YY; [Jan through Dec] DD YYYY; ' - '[Jan through Dec] DD, YYYY; DD [Jan through Dec] YYYY; ' - 'DD [Jan through Dec], YYYY; [January through December] DD YYYY; ' - '[January through December] DD, YYYY; DD [January through December] YYYY; ' - 'DD [January through December], YYYY']) + self.assertEqual(e.messages, ["Date has wrong format. Use one of these formats instead: YYYY-MM-DD"]) else: self.fail("ValidationError was not properly raised") + def test_from_native_invalid_format(self): + """ + Make sure from_native() raises a ValidationError on passing an invalid format. + """ + f = serializers.DateField() -class DateTimeFieldTest(TestCase): - def test_valid_default_date_time_input_formats(self): - serializer = DateTimeObjectSerializer(data={'date_time': '1984-07-31 04:31:59'}) - self.assertTrue(serializer.is_valid()) - - serializer = DateTimeObjectSerializer(data={'date_time': '1984-07-31 04:31'}) - self.assertTrue(serializer.is_valid()) - - serializer = DateTimeObjectSerializer(data={'date_time': '1984-07-31'}) - self.assertTrue(serializer.is_valid()) - - serializer = DateTimeObjectSerializer(data={'date_time': '07/31/1984 04:31:59'}) - self.assertTrue(serializer.is_valid()) - - serializer = DateTimeObjectSerializer(data={'date_time': '07/31/1984 04:31'}) - self.assertTrue(serializer.is_valid()) + try: + f.from_native('1984 -- 31') + except validators.ValidationError as e: + self.assertEqual(e.messages, ["Date has wrong format. Use one of these formats instead: YYYY-MM-DD"]) + else: + self.fail("ValidationError was not properly raised") - serializer = DateTimeObjectSerializer(data={'date_time': '07/31/1984'}) - self.assertTrue(serializer.is_valid()) + def test_to_native(self): + """ + Make sure to_native() returns isoformat as default. + """ + f = serializers.DateField() - serializer = DateTimeObjectSerializer(data={'date_time': '07/31/84 04:31:59'}) - self.assertTrue(serializer.is_valid()) + result_1 = f.to_native(datetime.date(1984, 7, 31)) - serializer = DateTimeObjectSerializer(data={'date_time': '07/31/84 04:31'}) - self.assertTrue(serializer.is_valid()) + self.assertEqual('1984-07-31', result_1) - serializer = DateTimeObjectSerializer(data={'date_time': '07/31/84'}) - self.assertTrue(serializer.is_valid()) + def test_to_native_custom_format(self): + """ + Make sure to_native() returns correct custom format. + """ + f = serializers.DateField(output_format="%Y - %m.%d") - @unittest.skipUnless(django.VERSION >= (1, 4), "django < 1.4 don't have microseconds in default settings") - def test_valid_default_date_time_input_formats_for_django_gte_1_4(self): - serializer = DateTimeObjectSerializer(data={'date_time': '1984-07-31 04:31:59.123456'}) - self.assertTrue(serializer.is_valid()) + result_1 = f.to_native(datetime.date(1984, 7, 31)) - serializer = DateTimeObjectSerializer(data={'date_time': '07/31/1984 04:31:59.123456'}) - self.assertTrue(serializer.is_valid()) + self.assertEqual('1984 - 07.31', result_1) - serializer = DateTimeObjectSerializer(data={'date_time': '07/31/84 04:31:59.123456'}) - self.assertTrue(serializer.is_valid()) - def test_valid_custom_date_time_input_formats(self): - serializer = DateTimeObjectCustomFormatSerializer(data={'date_time': '1984'}) - self.assertTrue(serializer.is_valid()) +class DateTimeFieldTest(TestCase): + """ + Tests for the DateTimeField from_native() and to_native() behavior + """ - serializer = DateTimeObjectCustomFormatSerializer(data={'date_time': '1984 04:31'}) - self.assertTrue(serializer.is_valid()) + def test_from_native_string(self): + """ + Make sure from_native() accepts default iso input formats. + """ + f = serializers.DateTimeField() + result_1 = f.from_native('1984-07-31') + result_2 = f.from_native('1984-07-31 04:31') + result_3 = f.from_native('1984-07-31 04:31:59') + result_4 = f.from_native('1984-07-31 04:31:59.000200') - @unittest.skipUnless(django.VERSION >= (1, 4), "django < 1.4 don't have microseconds in default settings") - def test_wrong_default_date_time_input_format_for_django_gte_1_4(self): - serializer = DateTimeObjectSerializer(data={'date_time': 'something wrong'}) - self.assertFalse(serializer.is_valid()) - self.assertEquals(serializer.errors, {'date_time': ['Datetime has wrong format. Use one of these formats instead: ' - 'YYYY-MM-DD HH:MM:SS; YYYY-MM-DD HH:MM:SS.uuuuuu; YYYY-MM-DD HH:MM; ' - 'YYYY-MM-DD; MM/DD/YYYY HH:MM:SS; MM/DD/YYYY HH:MM:SS.uuuuuu; ' - 'MM/DD/YYYY HH:MM; MM/DD/YYYY; MM/DD/YY HH:MM:SS; ' - 'MM/DD/YY HH:MM:SS.uuuuuu; MM/DD/YY HH:MM; MM/DD/YY']}) + self.assertEqual(datetime.datetime(1984, 7, 31), result_1) + self.assertEqual(datetime.datetime(1984, 7, 31, 4, 31), result_2) + self.assertEqual(datetime.datetime(1984, 7, 31, 4, 31, 59), result_3) + self.assertEqual(datetime.datetime(1984, 7, 31, 4, 31, 59, 200), result_4) - @unittest.skipUnless(django.VERSION < (1, 4), "django >= 1.4 have microseconds in default settings") - def test_wrong_default_date_time_input_format_for_django_lt_1_4(self): - serializer = DateTimeObjectSerializer(data={'date_time': 'something wrong'}) - self.assertFalse(serializer.is_valid()) - self.assertEquals(serializer.errors, {'date_time': ['Datetime has wrong format. Use one of these formats instead: ' - 'YYYY-MM-DD HH:MM:SS; YYYY-MM-DD HH:MM; YYYY-MM-DD; ' - 'MM/DD/YYYY HH:MM:SS; MM/DD/YYYY HH:MM; MM/DD/YYYY; ' - 'MM/DD/YY HH:MM:SS; MM/DD/YY HH:MM; MM/DD/YY']}) + def test_from_native_datetime_datetime(self): + """ + Make sure from_native() accepts a datetime.datetime instance. + """ + f = serializers.DateTimeField() + result_1 = f.from_native(datetime.datetime(1984, 7, 31)) + result_2 = f.from_native(datetime.datetime(1984, 7, 31, 4, 31)) + result_3 = f.from_native(datetime.datetime(1984, 7, 31, 4, 31, 59)) + result_4 = f.from_native(datetime.datetime(1984, 7, 31, 4, 31, 59, 200)) - def test_wrong_custom_date_time_input_format(self): - serializer = DateTimeObjectCustomFormatSerializer(data={'date_time': '07/31/84 04:31'}) - self.assertFalse(serializer.is_valid()) - self.assertEquals(serializer.errors, {'date_time': ['Datetime has wrong format. Use one of these formats instead: YYYY; YYYY HH:MM']}) + self.assertEqual(result_1, datetime.datetime(1984, 7, 31)) + self.assertEqual(result_2, datetime.datetime(1984, 7, 31, 4, 31)) + self.assertEqual(result_3, datetime.datetime(1984, 7, 31, 4, 31, 59)) + self.assertEqual(result_4, datetime.datetime(1984, 7, 31, 4, 31, 59, 200)) - def test_from_native(self): - f = serializers.DateTimeField() - result = f.from_native('1984-07-31 04:31') + def test_from_native_custom_format(self): + """ + Make sure from_native() accepts custom input formats. + """ + f = serializers.DateTimeField(input_formats=['%Y -- %H:%M']) + result = f.from_native('1984 -- 04:59') - self.assertEqual(datetime.datetime(1984, 7, 31, 4, 31), result) + self.assertEqual(datetime.datetime(1984, 1, 1, 4, 59), result) - def test_from_native_datetime_datetime(self): + def test_from_native_invalid_default_on_custom_format(self): """ - Make sure from_native() accepts a datetime.date instance. + Make sure from_native() don't accept default formats if custom format is preset """ - f = serializers.DateTimeField() - result = f.from_native(datetime.datetime(1984, 7, 31)) + f = serializers.DateTimeField(input_formats=['%Y -- %H:%M']) - self.assertEqual(result, datetime.datetime(1984, 7, 31)) + try: + f.from_native('1984-07-31 04:31:59') + except validators.ValidationError as e: + self.assertEqual(e.messages, ["Datetime has wrong format. Use one of these formats instead: YYYY -- HH:MM"]) + else: + self.fail("ValidationError was not properly raised") def test_from_native_empty(self): + """ + Make sure from_native() returns None on empty param. + """ f = serializers.DateTimeField() result = f.from_native('') self.assertEqual(result, None) - @unittest.skipUnless(django.VERSION >= (1, 4), "django < 1.4 don't have microseconds in default settings") - def test_from_native_invalid_datetime_for_django_gte_1_4(self): + def test_from_native_invalid_datetime(self): + """ + Make sure from_native() raises a ValidationError on passing an invalid datetime. + """ f = serializers.DateTimeField() try: - f.from_native('1984-42-31 04:31') + f.from_native('04:61:59') except validators.ValidationError as e: - self.assertEqual(e.messages, ['Datetime has wrong format. Use one of these formats instead: ' - 'YYYY-MM-DD HH:MM:SS; YYYY-MM-DD HH:MM:SS.uuuuuu; YYYY-MM-DD HH:MM; ' - 'YYYY-MM-DD; MM/DD/YYYY HH:MM:SS; MM/DD/YYYY HH:MM:SS.uuuuuu; ' - 'MM/DD/YYYY HH:MM; MM/DD/YYYY; MM/DD/YY HH:MM:SS; ' - 'MM/DD/YY HH:MM:SS.uuuuuu; MM/DD/YY HH:MM; MM/DD/YY']) + self.assertEqual(e.messages, ["Datetime has wrong format. Use one of these formats instead: " + "YYYY-MM-DD; YYYY-MM-DD HH:MM; YYYY-MM-DD HH:MM:SS; " + "YYYY-MM-DD HH:MM:SS.uuuuuu"]) else: self.fail("ValidationError was not properly raised") - @unittest.skipUnless(django.VERSION < (1, 4), "django >= 1.4 have microseconds in default settings") - def test_from_native_invalid_datetime_for_django_lt_1_4(self): + def test_from_native_invalid_format(self): + """ + Make sure from_native() raises a ValidationError on passing an invalid format. + """ f = serializers.DateTimeField() try: - f.from_native('1984-42-31 04:31') + f.from_native('04 -- 31') except validators.ValidationError as e: - self.assertEqual(e.messages, ['Datetime has wrong format. Use one of these formats instead: ' - 'YYYY-MM-DD HH:MM:SS; YYYY-MM-DD HH:MM; YYYY-MM-DD; ' - 'MM/DD/YYYY HH:MM:SS; MM/DD/YYYY HH:MM; MM/DD/YYYY; ' - 'MM/DD/YY HH:MM:SS; MM/DD/YY HH:MM; MM/DD/YY']) + self.assertEqual(e.messages, ["Datetime has wrong format. Use one of these formats instead: " + "YYYY-MM-DD; YYYY-MM-DD HH:MM; YYYY-MM-DD HH:MM:SS; " + "YYYY-MM-DD HH:MM:SS.uuuuuu"]) else: self.fail("ValidationError was not properly raised") + def test_to_native(self): + """ + Make sure to_native() returns isoformat as default. + """ + f = serializers.DateTimeField() -class TimeFieldTest(TestCase): - def test_valid_default_time_input_formats(self): - serializer = TimeObjectSerializer(data={'time': '04:31'}) - self.assertTrue(serializer.is_valid()) + result_1 = f.to_native(datetime.datetime(1984, 7, 31)) + result_2 = f.to_native(datetime.datetime(1984, 7, 31, 4, 31)) + result_3 = f.to_native(datetime.datetime(1984, 7, 31, 4, 31, 59)) + result_4 = f.to_native(datetime.datetime(1984, 7, 31, 4, 31, 59, 200)) + + self.assertEqual('1984-07-31T00:00:00', result_1) + self.assertEqual('1984-07-31T04:31:00', result_2) + self.assertEqual('1984-07-31T04:31:59', result_3) + self.assertEqual('1984-07-31T04:31:59.000200', result_4) - serializer = TimeObjectSerializer(data={'time': '04:31:59'}) - self.assertTrue(serializer.is_valid()) + def test_to_native_custom_format(self): + """ + Make sure to_native() returns correct custom format. + """ + f = serializers.DateTimeField(output_format="%Y - %H:%M") - def test_valid_custom_time_input_formats(self): - serializer = TimeObjectCustomFormatSerializer(data={'time': '04 -- 31'}) - self.assertTrue(serializer.is_valid()) + result_1 = f.to_native(datetime.datetime(1984, 7, 31)) + result_2 = f.to_native(datetime.datetime(1984, 7, 31, 4, 31)) + result_3 = f.to_native(datetime.datetime(1984, 7, 31, 4, 31, 59)) + result_4 = f.to_native(datetime.datetime(1984, 7, 31, 4, 31, 59, 200)) - serializer = TimeObjectCustomFormatSerializer(data={'time': '043159'}) - self.assertTrue(serializer.is_valid()) + self.assertEqual('1984 - 00:00', result_1) + self.assertEqual('1984 - 04:31', result_2) + self.assertEqual('1984 - 04:31', result_3) + self.assertEqual('1984 - 04:31', result_4) - def test_wrong_default_time_input_format(self): - serializer = TimeObjectSerializer(data={'time': 'something wrong'}) - self.assertFalse(serializer.is_valid()) - self.assertEquals(serializer.errors, {'time': ['Time has wrong format. Use one of these formats instead: HH:MM:SS; HH:MM']}) - def test_wrong_custom_time_input_format(self): - serializer = TimeObjectCustomFormatSerializer(data={'time': '04:31'}) - self.assertFalse(serializer.is_valid()) - self.assertEquals(serializer.errors, {'time': ['Time has wrong format. Use one of these formats instead: HH -- MM; HHMMSS']}) +class TimeFieldTest(TestCase): + """ + Tests for the TimeField from_native() and to_native() behavior + """ - def test_from_native(self): + def test_from_native_string(self): + """ + Make sure from_native() accepts default iso input formats. + """ f = serializers.TimeField() - result = f.from_native('12:34:56') + result_1 = f.from_native('04:31') + result_2 = f.from_native('04:31:59') + result_3 = f.from_native('04:31:59.000200') - self.assertEqual(datetime.time(12, 34, 56), result) + self.assertEqual(datetime.time(4, 31), result_1) + self.assertEqual(datetime.time(4, 31, 59), result_2) + self.assertEqual(datetime.time(4, 31, 59, 200), result_3) def test_from_native_datetime_time(self): """ Make sure from_native() accepts a datetime.time instance. """ f = serializers.TimeField() - result = f.from_native(datetime.time(12, 34, 56)) + result_1 = f.from_native(datetime.time(4, 31)) + result_2 = f.from_native(datetime.time(4, 31, 59)) + result_3 = f.from_native(datetime.time(4, 31, 59, 200)) + + self.assertEqual(result_1, datetime.time(4, 31)) + self.assertEqual(result_2, datetime.time(4, 31, 59)) + self.assertEqual(result_3, datetime.time(4, 31, 59, 200)) + + def test_from_native_custom_format(self): + """ + Make sure from_native() accepts custom input formats. + """ + f = serializers.TimeField(input_formats=['%H -- %M']) + result = f.from_native('04 -- 31') + + self.assertEqual(datetime.time(4, 31), result) + + def test_from_native_invalid_default_on_custom_format(self): + """ + Make sure from_native() don't accept default formats if custom format is preset + """ + f = serializers.TimeField(input_formats=['%H -- %M']) - self.assertEqual(result, datetime.time(12, 34, 56)) + try: + f.from_native('04:31:59') + except validators.ValidationError as e: + self.assertEqual(e.messages, ["Time has wrong format. Use one of these formats instead: HH -- MM"]) + else: + self.fail("ValidationError was not properly raised") def test_from_native_empty(self): + """ + Make sure from_native() returns None on empty param. + """ f = serializers.TimeField() result = f.from_native('') self.assertEqual(result, None) def test_from_native_invalid_time(self): + """ + Make sure from_native() raises a ValidationError on passing an invalid time. + """ f = serializers.TimeField() try: - f.from_native('12:69:12') + f.from_native('04:61:59') except validators.ValidationError as e: - self.assertEqual(e.messages, ["Time has wrong format. Use one of these formats instead: HH:MM:SS; HH:MM"]) + self.assertEqual(e.messages, ["Time has wrong format. Use one of these formats instead: " + "HH:MM; HH:MM:SS; HH:MM:SS.uuuuuu"]) else: - self.fail("ValidationError was not properly raised") \ No newline at end of file + self.fail("ValidationError was not properly raised") + + def test_from_native_invalid_format(self): + """ + Make sure from_native() raises a ValidationError on passing an invalid format. + """ + f = serializers.TimeField() + + try: + f.from_native('04 -- 31') + except validators.ValidationError as e: + self.assertEqual(e.messages, ["Time has wrong format. Use one of these formats instead: " + "HH:MM; HH:MM:SS; HH:MM:SS.uuuuuu"]) + else: + self.fail("ValidationError was not properly raised") + + def test_to_native(self): + """ + Make sure to_native() returns isoformat as default. + """ + f = serializers.TimeField() + result_1 = f.to_native(datetime.time(4, 31)) + result_2 = f.to_native(datetime.time(4, 31, 59)) + result_3 = f.to_native(datetime.time(4, 31, 59, 200)) + + self.assertEqual('04:31:00', result_1) + self.assertEqual('04:31:59', result_2) + self.assertEqual('04:31:59.000200', result_3) + + def test_to_native_custom_format(self): + """ + Make sure to_native() returns correct custom format. + """ + f = serializers.TimeField(output_format="%H - %S [%f]") + result_1 = f.to_native(datetime.time(4, 31)) + result_2 = f.to_native(datetime.time(4, 31, 59)) + result_3 = f.to_native(datetime.time(4, 31, 59, 200)) + + self.assertEqual('04 - 00 [000000]', result_1) + self.assertEqual('04 - 59 [000000]', result_2) + self.assertEqual('04 - 59 [000200]', result_3) \ No newline at end of file diff --git a/rest_framework/tests/filterset.py b/rest_framework/tests/filterset.py index daea6e53..16f9a48e 100644 --- a/rest_framework/tests/filterset.py +++ b/rest_framework/tests/filterset.py @@ -65,8 +65,8 @@ class IntegrationTestFiltering(TestCase): self.objects = FilterableItem.objects self.data = [ - {'id': obj.id, 'text': obj.text, 'decimal': obj.decimal, 'date': obj.date} - for obj in self.objects.all() + {'id': obj.id, 'text': obj.text, 'decimal': obj.decimal, 'date': obj.date.isoformat()} + for obj in self.objects.all() ] @unittest.skipUnless(django_filters, 'django-filters not installed') @@ -95,7 +95,7 @@ class IntegrationTestFiltering(TestCase): request = factory.get('/?date=%s' % search_date) # search_date str: '2012-09-22' response = view(request).render() self.assertEquals(response.status_code, status.HTTP_200_OK) - expected_data = [f for f in self.data if f['date'] == search_date] + expected_data = [f for f in self.data if datetime.datetime.strptime(f['date'], '%Y-%m-%d').date() == search_date] self.assertEquals(response.data, expected_data) @unittest.skipUnless(django_filters, 'django-filters not installed') @@ -125,7 +125,7 @@ class IntegrationTestFiltering(TestCase): request = factory.get('/?date=%s' % search_date) # search_date str: '2012-10-02' response = view(request).render() self.assertEquals(response.status_code, status.HTTP_200_OK) - expected_data = [f for f in self.data if f['date'] > search_date] + expected_data = [f for f in self.data if datetime.datetime.strptime(f['date'], '%Y-%m-%d').date() > search_date] self.assertEquals(response.data, expected_data) # Tests that the text filter set with 'icontains' in the filter class works. @@ -142,8 +142,9 @@ class IntegrationTestFiltering(TestCase): request = factory.get('/?decimal=%s&date=%s' % (search_decimal, search_date)) response = view(request).render() self.assertEquals(response.status_code, status.HTTP_200_OK) - expected_data = [f for f in self.data if f['date'] > search_date and - f['decimal'] < search_decimal] + expected_data = [f for f in self.data if + datetime.datetime.strptime(f['date'], '%Y-%m-%d').date() > search_date and + f['decimal'] < search_decimal] self.assertEquals(response.data, expected_data) @unittest.skipUnless(django_filters, 'django-filters not installed') diff --git a/rest_framework/tests/pagination.py b/rest_framework/tests/pagination.py index b85ce144..c1efd4f5 100644 --- a/rest_framework/tests/pagination.py +++ b/rest_framework/tests/pagination.py @@ -112,8 +112,8 @@ class IntegrationTestPaginationAndFiltering(TestCase): self.objects = FilterableItem.objects self.data = [ - {'id': obj.id, 'text': obj.text, 'decimal': obj.decimal, 'date': obj.date} - for obj in self.objects.all() + {'id': obj.id, 'text': obj.text, 'decimal': obj.decimal, 'date': obj.date.isoformat()} + for obj in self.objects.all() ] self.view = FilterFieldsRootView.as_view() diff --git a/rest_framework/tests/serializer.py b/rest_framework/tests/serializer.py index 671494b5..1c99375f 100644 --- a/rest_framework/tests/serializer.py +++ b/rest_framework/tests/serializer.py @@ -107,7 +107,7 @@ class BasicTests(TestCase): self.expected = { 'email': 'tom@example.com', 'content': 'Happy new year!', - 'created': datetime.datetime(2012, 1, 1), + 'created': '2012-01-01T00:00:00', 'sub_comment': 'And Merry Christmas!' } self.person_data = {'name': 'dwight', 'age': 35} -- cgit v1.2.3 From 7e702439eb4a82fe2ef45911fbb176e95d5f0bce 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 --- docs/api-guide/fields.md | 24 ++++++++++++++++++------ docs/api-guide/settings.md | 35 +++++++++++++++++++++++++++++++++++ docs/topics/release-notes.md | 4 +--- rest_framework/fields.py | 18 +++++++++--------- 4 files changed, 63 insertions(+), 18 deletions(-) diff --git a/docs/api-guide/fields.md b/docs/api-guide/fields.md index a3dc4fe2..898de12d 100644 --- a/docs/api-guide/fields.md +++ b/docs/api-guide/fields.md @@ -185,18 +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` +**Signature:** `DateField(input_formats=None, output_format=False)` + + - `input_formats` designates which input formats are supported. This will override the `DATE_INPUT_FORMATS` + + - `output_format` designates which output format will be used. This will override the `DATE_OUTPUT_FORMAT` + ## 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` @@ -211,16 +213,26 @@ If you want to override this behavior, you'll need to declare the `DateTimeField class Meta: model = Comment +**Signature:** `DateTimeField(input_formats=None, output_format=False)` + + - `input_formats` designates which input formats are supported. This will override the `DATETIME_INPUT_FORMATS` + + - `output_format` designates which output format will be used. This will override the `DATETIME_OUTPUT_FORMAT` + ## TimeField 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` +**Signature:** `TimeField(input_formats=None, output_format=False)` + + - `input_formats` designates which input formats are supported. This will override the `TIME_INPUT_FORMATS` + + - `output_format` designates which output format will be used. This will override the `TIME_OUTPUT_FORMAT` + ## IntegerField An integer representation. diff --git a/docs/api-guide/settings.md b/docs/api-guide/settings.md index e103fbab..9080cacb 100644 --- a/docs/api-guide/settings.md +++ b/docs/api-guide/settings.md @@ -174,4 +174,39 @@ The name of a parameter in the URL conf that may be used to provide a format suf Default: `'format'` +## DATE_INPUT_FORMATS + +Default: + + ( + '%Y-%m-%d', # '1984-07-31' + ) + +## DATE_OUTPUT_FORMAT + +## DATETIME_INPUT_FORMATS + +Default: + + ( + '%Y-%m-%d', # '1984-07-31' + '%Y-%m-%d %H:%M', # '1984-07-31 04:31' + '%Y-%m-%d %H:%M:%S', # '1984-07-31 04:31:59' + '%Y-%m-%d %H:%M:%S.%f', # '1984-07-31 04:31:59.000200' + ) + +## DATETIME_OUTPUT_FORMAT + +## TIME_INPUT_FORMATS + +Default: + + ( + '%H:%M', # '04:31' + '%H:%M:%S', # '04:31:59' + '%H:%M:%S.%f', # '04:31:59.000200' + ) + +## TIME_OUTPUT_FORMAT + [cite]: http://www.python.org/dev/peps/pep-0020/ diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md index 31ff68dd..f5f4dc55 100644 --- a/docs/topics/release-notes.md +++ b/docs/topics/release-notes.md @@ -44,9 +44,7 @@ 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 +* Support for custom input and output formats for `DateField`, `DateTimeField` and `TimeField` ### 2.2.1 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 29a54b5a773c0bc854a57393364437efd2c7a172 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/__init__.py | 3 ++ rest_framework/fields.py | 83 ++++++++++++++++++++++++++++-------------- rest_framework/settings.py | 20 +++++----- rest_framework/tests/fields.py | 38 ++++++++----------- 4 files changed, 84 insertions(+), 60 deletions(-) diff --git a/rest_framework/__init__.py b/rest_framework/__init__.py index 29f3d7bc..d26bb6bf 100644 --- a/rest_framework/__init__.py +++ b/rest_framework/__init__.py @@ -4,3 +4,6 @@ VERSION = __version__ # synonym # Header encoding (see RFC5987) HTTP_HEADER_ENCODING = 'iso-8859-1' + +# Default input and output format +ISO8601 = 'iso-8601' \ No newline at end of file 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): diff --git a/rest_framework/settings.py b/rest_framework/settings.py index 717496ea..02d751e1 100644 --- a/rest_framework/settings.py +++ b/rest_framework/settings.py @@ -18,8 +18,11 @@ REST framework settings, checking for user settings first, then falling back to the defaults. """ from __future__ import unicode_literals + from django.conf import settings from django.utils import importlib + +from rest_framework import ISO8601 from rest_framework.compat import six @@ -79,24 +82,19 @@ DEFAULTS = { # Input and output formats 'DATE_INPUT_FORMATS': ( - '%Y-%m-%d', # '1984-07-31' + ISO8601, ), - 'DATE_OUTPUT_FORMAT': None, + 'DATE_OUTPUT_FORMAT': ISO8601, 'DATETIME_INPUT_FORMATS': ( - '%Y-%m-%d', # '1984-07-31' - '%Y-%m-%d %H:%M', # '1984-07-31 04:31' - '%Y-%m-%d %H:%M:%S', # '1984-07-31 04:31:59' - '%Y-%m-%d %H:%M:%S.%f', # '1984-07-31 04:31:59.000200' + ISO8601, ), - 'DATETIME_OUTPUT_FORMAT': None, + 'DATETIME_OUTPUT_FORMAT': ISO8601, 'TIME_INPUT_FORMATS': ( - '%H:%M', # '04:31' - '%H:%M:%S', # '04:31:59' - '%H:%M:%S.%f', # '04:31:59.000200' + ISO8601, ), - 'TIME_OUTPUT_FORMAT': None, + 'TIME_OUTPUT_FORMAT': ISO8601, } diff --git a/rest_framework/tests/fields.py b/rest_framework/tests/fields.py index ddedd6f9..3f8a6730 100644 --- a/rest_framework/tests/fields.py +++ b/rest_framework/tests/fields.py @@ -173,30 +173,26 @@ class DateTimeFieldTest(TestCase): Make sure from_native() accepts default iso input formats. """ f = serializers.DateTimeField() - result_1 = f.from_native('1984-07-31') - result_2 = f.from_native('1984-07-31 04:31') - result_3 = f.from_native('1984-07-31 04:31:59') - result_4 = f.from_native('1984-07-31 04:31:59.000200') + result_1 = f.from_native('1984-07-31 04:31') + result_2 = f.from_native('1984-07-31 04:31:59') + result_3 = f.from_native('1984-07-31 04:31:59.000200') - self.assertEqual(datetime.datetime(1984, 7, 31), result_1) - self.assertEqual(datetime.datetime(1984, 7, 31, 4, 31), result_2) - self.assertEqual(datetime.datetime(1984, 7, 31, 4, 31, 59), result_3) - self.assertEqual(datetime.datetime(1984, 7, 31, 4, 31, 59, 200), result_4) + self.assertEqual(datetime.datetime(1984, 7, 31, 4, 31), result_1) + self.assertEqual(datetime.datetime(1984, 7, 31, 4, 31, 59), result_2) + self.assertEqual(datetime.datetime(1984, 7, 31, 4, 31, 59, 200), result_3) def test_from_native_datetime_datetime(self): """ Make sure from_native() accepts a datetime.datetime instance. """ f = serializers.DateTimeField() - result_1 = f.from_native(datetime.datetime(1984, 7, 31)) - result_2 = f.from_native(datetime.datetime(1984, 7, 31, 4, 31)) - result_3 = f.from_native(datetime.datetime(1984, 7, 31, 4, 31, 59)) - result_4 = f.from_native(datetime.datetime(1984, 7, 31, 4, 31, 59, 200)) + result_1 = f.from_native(datetime.datetime(1984, 7, 31, 4, 31)) + result_2 = f.from_native(datetime.datetime(1984, 7, 31, 4, 31, 59)) + result_3 = f.from_native(datetime.datetime(1984, 7, 31, 4, 31, 59, 200)) - self.assertEqual(result_1, datetime.datetime(1984, 7, 31)) - self.assertEqual(result_2, datetime.datetime(1984, 7, 31, 4, 31)) - self.assertEqual(result_3, datetime.datetime(1984, 7, 31, 4, 31, 59)) - self.assertEqual(result_4, datetime.datetime(1984, 7, 31, 4, 31, 59, 200)) + self.assertEqual(result_1, datetime.datetime(1984, 7, 31, 4, 31)) + self.assertEqual(result_2, datetime.datetime(1984, 7, 31, 4, 31, 59)) + self.assertEqual(result_3, datetime.datetime(1984, 7, 31, 4, 31, 59, 200)) def test_from_native_custom_format(self): """ @@ -239,8 +235,7 @@ class DateTimeFieldTest(TestCase): f.from_native('04:61:59') except validators.ValidationError as e: self.assertEqual(e.messages, ["Datetime has wrong format. Use one of these formats instead: " - "YYYY-MM-DD; YYYY-MM-DD HH:MM; YYYY-MM-DD HH:MM:SS; " - "YYYY-MM-DD HH:MM:SS.uuuuuu"]) + "YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ]"]) else: self.fail("ValidationError was not properly raised") @@ -254,8 +249,7 @@ class DateTimeFieldTest(TestCase): f.from_native('04 -- 31') except validators.ValidationError as e: self.assertEqual(e.messages, ["Datetime has wrong format. Use one of these formats instead: " - "YYYY-MM-DD; YYYY-MM-DD HH:MM; YYYY-MM-DD HH:MM:SS; " - "YYYY-MM-DD HH:MM:SS.uuuuuu"]) + "YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ]"]) else: self.fail("ValidationError was not properly raised") @@ -364,7 +358,7 @@ class TimeFieldTest(TestCase): f.from_native('04:61:59') except validators.ValidationError as e: self.assertEqual(e.messages, ["Time has wrong format. Use one of these formats instead: " - "HH:MM; HH:MM:SS; HH:MM:SS.uuuuuu"]) + "HH:MM[:ss[.uuuuuu]]"]) else: self.fail("ValidationError was not properly raised") @@ -378,7 +372,7 @@ class TimeFieldTest(TestCase): f.from_native('04 -- 31') except validators.ValidationError as e: self.assertEqual(e.messages, ["Time has wrong format. Use one of these formats instead: " - "HH:MM; HH:MM:SS; HH:MM:SS.uuuuuu"]) + "HH:MM[:ss[.uuuuuu]]"]) else: self.fail("ValidationError was not properly raised") -- cgit v1.2.3 From d20089c8d18e04dec03c3836b47b4436945d0a85 Mon Sep 17 00:00:00 2001 From: Stephan Groß Date: Fri, 1 Mar 2013 16:35:27 +0100 Subject: Update docs --- docs/api-guide/settings.md | 29 +++++++++-------------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/docs/api-guide/settings.md b/docs/api-guide/settings.md index 9080cacb..04569f44 100644 --- a/docs/api-guide/settings.md +++ b/docs/api-guide/settings.md @@ -176,37 +176,26 @@ Default: `'format'` ## DATE_INPUT_FORMATS -Default: - - ( - '%Y-%m-%d', # '1984-07-31' - ) +Default: `ISO8601` ## DATE_OUTPUT_FORMAT +Default: `ISO8601` + ## DATETIME_INPUT_FORMATS -Default: - - ( - '%Y-%m-%d', # '1984-07-31' - '%Y-%m-%d %H:%M', # '1984-07-31 04:31' - '%Y-%m-%d %H:%M:%S', # '1984-07-31 04:31:59' - '%Y-%m-%d %H:%M:%S.%f', # '1984-07-31 04:31:59.000200' - ) +Default: `ISO8601` ## DATETIME_OUTPUT_FORMAT +Default: `ISO8601` + ## TIME_INPUT_FORMATS -Default: - - ( - '%H:%M', # '04:31' - '%H:%M:%S', # '04:31:59' - '%H:%M:%S.%f', # '04:31:59.000200' - ) +Default: `ISO8601` ## TIME_OUTPUT_FORMAT +Default: `ISO8601` + [cite]: http://www.python.org/dev/peps/pep-0020/ -- cgit v1.2.3