From 5e354845c572572501ddac531fa1fc4aeb2f285f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20Gro=C3=9F?= Date: Tue, 29 Jan 2013 12:05:03 +0100 Subject: [PATCH 1/4] Add better date / datetime validation --- docs/api-guide/fields.md | 4 + docs/topics/release-notes.md | 2 + rest_framework/fields.py | 61 +++++++------- rest_framework/tests/serializer.py | 124 +++++++++++++++++++++++++++++ 4 files changed, 157 insertions(+), 34 deletions(-) diff --git a/docs/api-guide/fields.md b/docs/api-guide/fields.md index e43282ce3..7be311b82 100644 --- a/docs/api-guide/fields.md +++ b/docs/api-guide/fields.md @@ -187,12 +187,16 @@ A date representation. Corresponds to `django.db.models.fields.DateField` +Uses `DATE_INPUT_FORMATS` to validate date. + ## DateTimeField A date and time representation. Corresponds to `django.db.models.fields.DateTimeField` +Uses `DATETIME_INPUT_FORMATS` to validate date_time. + 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. If you want to override this behavior, you'll need to declare the `DateTimeField` explicitly on the serializer. For example: diff --git a/docs/topics/release-notes.md b/docs/topics/release-notes.md index a6de11889..d7f52489c 100644 --- a/docs/topics/release-notes.md +++ b/docs/topics/release-notes.md @@ -28,6 +28,8 @@ You can determine your currently installed version using `pip freeze`: ### Master +* Support `DATE_INPUT_FORMATS` for `DateField` validation +* Support `DATETIME_INPUT_FORMATS` for `DateTimeField` validation * Bugfix: Fix styling on browsable API login. * Bugfix: Ensure model field validation is still applied for ModelSerializer subclasses with an custom `.restore_object()` method. diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 998911e12..6d930338a 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -425,10 +425,7 @@ class DateField(WritableField): form_field_class = forms.DateField default_error_messages = { - 'invalid': _(u"'%s' value has an invalid date format. It must be " - u"in YYYY-MM-DD format."), - 'invalid_date': _(u"'%s' value has the correct format (YYYY-MM-DD) " - u"but it is an invalid date."), + 'invalid': _(u"Date has wrong format. Use one of these formats instead: %s"), } empty = None @@ -446,15 +443,20 @@ class DateField(WritableField): if isinstance(value, datetime.date): return value - try: - parsed = parse_date(value) - if parsed is not None: - return parsed - except ValueError: - msg = self.error_messages['invalid_date'] % value - raise ValidationError(msg) + for format in settings.DATE_INPUT_FORMATS: + try: + parsed = datetime.datetime.strptime(value, format) + except ValueError: + pass + else: + return parsed.date() - msg = self.error_messages['invalid'] % value + formats = '; '.join(settings.DATE_INPUT_FORMATS) + mapping = [("%Y", "YYYY"), ("%y", "YY"), ("%m", "MM"), ("%b", "[Jan through Dec]"), + ("%B", "[January through December]"), ("%d", "DD"), ("%H", "HH"), ("%M", "MM"), ("%S", "SS")] + for k, v in mapping: + formats = formats.replace(k, v) + msg = self.error_messages['invalid'] % formats raise ValidationError(msg) @@ -464,13 +466,7 @@ class DateTimeField(WritableField): form_field_class = forms.DateTimeField default_error_messages = { - 'invalid': _(u"'%s' value has an invalid format. It must be in " - u"YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ] format."), - 'invalid_date': _(u"'%s' value has the correct format " - u"(YYYY-MM-DD) but it is an invalid date."), - 'invalid_datetime': _(u"'%s' value has the correct format " - u"(YYYY-MM-DD HH:MM[:ss[.uuuuuu]][TZ]) " - u"but it is an invalid date/time."), + 'invalid': _(u"Datetime has wrong format. Use one of these formats instead: %s"), } empty = None @@ -494,23 +490,20 @@ 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 settings.DATETIME_INPUT_FORMATS: + try: + parsed = datetime.datetime.strptime(value, format) + except ValueError: + pass + else: return parsed - except ValueError: - 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: - msg = self.error_messages['invalid_date'] % value - raise ValidationError(msg) - - msg = self.error_messages['invalid'] % value + formats = '; '.join(settings.DATETIME_INPUT_FORMATS) + mapping = [("%Y", "YYYY"), ("%y", "YY"), ("%m", "MM"), ("%d", "DD"), + ("%H", "HH"), ("%M", "MM"), ("%S", "SS"), ("%f", "uuuuuu")] + for k, v in mapping: + formats = formats.replace(k, v) + msg = self.error_messages['invalid'] % formats raise ValidationError(msg) diff --git a/rest_framework/tests/serializer.py b/rest_framework/tests/serializer.py index 48b4f1ab9..7afd3f698 100644 --- a/rest_framework/tests/serializer.py +++ b/rest_framework/tests/serializer.py @@ -41,6 +41,36 @@ class CommentSerializer(serializers.Serializer): return instance +class DateObject(object): + def __init__(self, date): + self.date = date + + +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 DateTimeObject(object): + def __init__(self, date_time): + self.date_time = date_time + + +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 BookSerializer(serializers.ModelSerializer): isbn = serializers.RegexField(regex=r'^[0-9]{13}$', error_messages={'invalid': 'isbn has to be exact 13 numbers'}) @@ -436,6 +466,100 @@ class RegexValidationTest(TestCase): self.assertTrue(serializer.is_valid()) +class DateValidationTest(TestCase): + def test_valid_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_wrong_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']}) + + +class DateTimeValidationTest(TestCase): + def test_valid_date_time_input_formats(self): + serializer = DateTimeObjectSerializer(data={'date_time': '1984-07-31 04:31:59.123456'}) + self.assertTrue(serializer.is_valid()) + + 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.123456'}) + 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.123456'}) + 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()) + + def test_wrong_date_time_input_format(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']}) + + class MetadataTests(TestCase): def test_empty(self): serializer = CommentSerializer() From e8ccc58254aa7fcc68a7ff0dc321de1fdd96cb3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20Gro=C3=9F?= Date: Tue, 29 Jan 2013 13:30:37 +0100 Subject: [PATCH 2/4] fix testcase for django < 1.4 --- rest_framework/tests/serializer.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/rest_framework/tests/serializer.py b/rest_framework/tests/serializer.py index 7afd3f698..fb34443e0 100644 --- a/rest_framework/tests/serializer.py +++ b/rest_framework/tests/serializer.py @@ -1,6 +1,8 @@ import datetime import pickle +import django from django.test import TestCase +from django.utils import unittest from rest_framework import serializers from rest_framework.tests.models import (HasPositiveIntegerAsChoice, Album, ActionItem, Anchor, BasicModel, BlankFieldModel, BlogPost, Book, CallableDefaultValueModel, DefaultValueModel, @@ -514,8 +516,6 @@ class DateValidationTest(TestCase): class DateTimeValidationTest(TestCase): def test_valid_date_time_input_formats(self): - serializer = DateTimeObjectSerializer(data={'date_time': '1984-07-31 04:31:59.123456'}) - self.assertTrue(serializer.is_valid()) serializer = DateTimeObjectSerializer(data={'date_time': '1984-07-31 04:31:59'}) self.assertTrue(serializer.is_valid()) @@ -526,9 +526,6 @@ class DateTimeValidationTest(TestCase): serializer = DateTimeObjectSerializer(data={'date_time': '1984-07-31'}) 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/1984 04:31:59'}) self.assertTrue(serializer.is_valid()) @@ -538,9 +535,6 @@ class DateTimeValidationTest(TestCase): serializer = DateTimeObjectSerializer(data={'date_time': '07/31/1984'}) self.assertTrue(serializer.is_valid()) - serializer = DateTimeObjectSerializer(data={'date_time': '07/31/84 04:31:59.123456'}) - self.assertTrue(serializer.is_valid()) - serializer = DateTimeObjectSerializer(data={'date_time': '07/31/84 04:31:59'}) self.assertTrue(serializer.is_valid()) @@ -550,6 +544,18 @@ class DateTimeValidationTest(TestCase): 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_date_time_input_formats_2(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_wrong_date_time_input_format(self): serializer = DateTimeObjectSerializer(data={'date_time': 'something wrong'}) self.assertFalse(serializer.is_valid()) From 57fcbda7302ff91f509ac0ba6cfe84d34965a7f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20Gro=C3=9F?= Date: Tue, 29 Jan 2013 13:34:28 +0100 Subject: [PATCH 3/4] fix error message for django < 1.4 --- rest_framework/tests/serializer.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/rest_framework/tests/serializer.py b/rest_framework/tests/serializer.py index fb34443e0..6d05bc381 100644 --- a/rest_framework/tests/serializer.py +++ b/rest_framework/tests/serializer.py @@ -555,7 +555,7 @@ class DateTimeValidationTest(TestCase): serializer = DateTimeObjectSerializer(data={'date_time': '07/31/84 04:31:59.123456'}) self.assertTrue(serializer.is_valid()) - + @unittest.skipUnless(django.VERSION >= (1, 4), "django < 1.4 don't have microseconds in default settings") def test_wrong_date_time_input_format(self): serializer = DateTimeObjectSerializer(data={'date_time': 'something wrong'}) self.assertFalse(serializer.is_valid()) @@ -565,6 +565,15 @@ class DateTimeValidationTest(TestCase): 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_date_time_input_format_2(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']}) + class MetadataTests(TestCase): def test_empty(self): From a405f90403a845deeb2641aa6a9c8c6486553ea4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20Gro=C3=9F?= Date: Wed, 30 Jan 2013 10:59:38 +0100 Subject: [PATCH 4/4] Update date / datetime validation due to github comments --- docs/api-guide/fields.md | 4 + rest_framework/fields.py | 30 ++--- rest_framework/tests/fields.py | 182 +++++++++++++++++++++++++++++ rest_framework/tests/serializer.py | 139 ---------------------- rest_framework/utils/dates.py | 14 +++ 5 files changed, 215 insertions(+), 154 deletions(-) create mode 100644 rest_framework/utils/dates.py diff --git a/docs/api-guide/fields.md b/docs/api-guide/fields.md index 7be311b82..852277b07 100644 --- a/docs/api-guide/fields.md +++ b/docs/api-guide/fields.md @@ -189,6 +189,8 @@ Corresponds to `django.db.models.fields.DateField` Uses `DATE_INPUT_FORMATS` to validate date. +Optionally takes `format` as parameter to replace the matching pattern. + ## DateTimeField A date and time representation. @@ -197,6 +199,8 @@ Corresponds to `django.db.models.fields.DateTimeField` Uses `DATETIME_INPUT_FORMATS` to validate date_time. +Optionally takes `format` as parameter to replace the matching pattern. + 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. If you want to override this behavior, you'll need to declare the `DateTimeField` explicitly on the serializer. For example: diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 6d930338a..8b4367773 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -13,8 +13,8 @@ from django import forms from django.forms import widgets from django.utils.encoding import is_protected_type, smart_unicode 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.utils.dates import get_readable_date_format def is_simple_callable(obj): @@ -429,6 +429,10 @@ class DateField(WritableField): } 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 @@ -443,7 +447,7 @@ class DateField(WritableField): if isinstance(value, datetime.date): return value - for format in settings.DATE_INPUT_FORMATS: + for format in self.format: try: parsed = datetime.datetime.strptime(value, format) except ValueError: @@ -451,12 +455,8 @@ class DateField(WritableField): else: return parsed.date() - formats = '; '.join(settings.DATE_INPUT_FORMATS) - mapping = [("%Y", "YYYY"), ("%y", "YY"), ("%m", "MM"), ("%b", "[Jan through Dec]"), - ("%B", "[January through December]"), ("%d", "DD"), ("%H", "HH"), ("%M", "MM"), ("%S", "SS")] - for k, v in mapping: - formats = formats.replace(k, v) - msg = self.error_messages['invalid'] % formats + date_input_formats = '; '.join(self.format) + msg = self.error_messages['invalid'] % get_readable_date_format(date_input_formats) raise ValidationError(msg) @@ -470,6 +470,10 @@ class DateTimeField(WritableField): } 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 @@ -490,7 +494,7 @@ class DateTimeField(WritableField): value = timezone.make_aware(value, default_timezone) return value - for format in settings.DATETIME_INPUT_FORMATS: + for format in self.format: try: parsed = datetime.datetime.strptime(value, format) except ValueError: @@ -498,12 +502,8 @@ class DateTimeField(WritableField): else: return parsed - formats = '; '.join(settings.DATETIME_INPUT_FORMATS) - mapping = [("%Y", "YYYY"), ("%y", "YY"), ("%m", "MM"), ("%d", "DD"), - ("%H", "HH"), ("%M", "MM"), ("%S", "SS"), ("%f", "uuuuuu")] - for k, v in mapping: - formats = formats.replace(k, v) - msg = self.error_messages['invalid'] % formats + datetime_input_formats = '; '.join(self.format) + msg = self.error_messages['invalid'] % get_readable_date_format(datetime_input_formats) raise ValidationError(msg) diff --git a/rest_framework/tests/fields.py b/rest_framework/tests/fields.py index 8068272d4..b2017c39f 100644 --- a/rest_framework/tests/fields.py +++ b/rest_framework/tests/fields.py @@ -2,8 +2,10 @@ General serializer field tests. """ +import django from django.db import models from django.test import TestCase +from django.utils import unittest from rest_framework import serializers @@ -16,6 +18,16 @@ 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 TimestampedModelSerializer(serializers.ModelSerializer): class Meta: model = TimestampedModel @@ -26,6 +38,46 @@ 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 ReadOnlyFieldTests(TestCase): def test_auto_now_fields_read_only(self): """ @@ -47,3 +99,133 @@ class ReadOnlyFieldTests(TestCase): """ serializer = CharPrimaryKeyModelSerializer() self.assertEquals(serializer.fields['id'].read_only, False) + + +class DateValidationTest(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']}) + + +class DateTimeValidationTest(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']}) diff --git a/rest_framework/tests/serializer.py b/rest_framework/tests/serializer.py index 6d05bc381..48b4f1ab9 100644 --- a/rest_framework/tests/serializer.py +++ b/rest_framework/tests/serializer.py @@ -1,8 +1,6 @@ import datetime import pickle -import django from django.test import TestCase -from django.utils import unittest from rest_framework import serializers from rest_framework.tests.models import (HasPositiveIntegerAsChoice, Album, ActionItem, Anchor, BasicModel, BlankFieldModel, BlogPost, Book, CallableDefaultValueModel, DefaultValueModel, @@ -43,36 +41,6 @@ class CommentSerializer(serializers.Serializer): return instance -class DateObject(object): - def __init__(self, date): - self.date = date - - -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 DateTimeObject(object): - def __init__(self, date_time): - self.date_time = date_time - - -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 BookSerializer(serializers.ModelSerializer): isbn = serializers.RegexField(regex=r'^[0-9]{13}$', error_messages={'invalid': 'isbn has to be exact 13 numbers'}) @@ -468,113 +436,6 @@ class RegexValidationTest(TestCase): self.assertTrue(serializer.is_valid()) -class DateValidationTest(TestCase): - def test_valid_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_wrong_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']}) - - -class DateTimeValidationTest(TestCase): - def test_valid_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_date_time_input_formats_2(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()) - - @unittest.skipUnless(django.VERSION >= (1, 4), "django < 1.4 don't have microseconds in default settings") - def test_wrong_date_time_input_format(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_date_time_input_format_2(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']}) - - class MetadataTests(TestCase): def test_empty(self): serializer = CommentSerializer() diff --git a/rest_framework/utils/dates.py b/rest_framework/utils/dates.py new file mode 100644 index 000000000..f094f72db --- /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