Update date / datetime validation due to github comments

This commit is contained in:
Stephan Groß 2013-01-30 10:59:38 +01:00
parent 57fcbda730
commit a405f90403
5 changed files with 215 additions and 154 deletions

View File

@ -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:

View File

@ -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)

View File

@ -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']})

View File

@ -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()

View File

@ -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