Add better date / datetime validation

This commit is contained in:
Stephan Groß 2013-01-29 12:05:03 +01:00
parent 54096a19fc
commit 5e354845c5
4 changed files with 157 additions and 34 deletions

View File

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

View File

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

View File

@ -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
for format in settings.DATE_INPUT_FORMATS:
try:
parsed = parse_date(value)
if parsed is not None:
return parsed
parsed = datetime.datetime.strptime(value, format)
except ValueError:
msg = self.error_messages['invalid_date'] % value
raise ValidationError(msg)
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
for format in settings.DATETIME_INPUT_FORMATS:
try:
parsed = parse_datetime(value)
if parsed is not None:
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)

View File

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