mirror of
https://github.com/encode/django-rest-framework.git
synced 2025-07-27 00:19:53 +03:00
Merge a405f90403
into 8021bb5d50
This commit is contained in:
commit
3c7dff7208
|
@ -187,12 +187,20 @@ A date representation.
|
|||
|
||||
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.
|
||||
|
||||
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:
|
||||
|
|
|
@ -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: Fix issue with deserializing empty to-many relations.
|
||||
* Bugfix: Ensure model field validation is still applied for ModelSerializer subclasses with an custom `.restore_object()` method.
|
||||
|
|
|
@ -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):
|
||||
|
@ -425,13 +425,14 @@ 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
|
||||
|
||||
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
|
||||
|
@ -446,15 +447,16 @@ class DateField(WritableField):
|
|||
if isinstance(value, datetime.date):
|
||||
return value
|
||||
|
||||
for format in self.format:
|
||||
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
|
||||
date_input_formats = '; '.join(self.format)
|
||||
msg = self.error_messages['invalid'] % get_readable_date_format(date_input_formats)
|
||||
raise ValidationError(msg)
|
||||
|
||||
|
||||
|
@ -464,16 +466,14 @@ 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
|
||||
|
||||
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
|
||||
|
@ -494,23 +494,16 @@ class DateTimeField(WritableField):
|
|||
value = timezone.make_aware(value, default_timezone)
|
||||
return value
|
||||
|
||||
for format in self.format:
|
||||
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
|
||||
datetime_input_formats = '; '.join(self.format)
|
||||
msg = self.error_messages['invalid'] % get_readable_date_format(datetime_input_formats)
|
||||
raise ValidationError(msg)
|
||||
|
||||
|
||||
|
|
|
@ -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']})
|
||||
|
|
14
rest_framework/utils/dates.py
Normal file
14
rest_framework/utils/dates.py
Normal 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
|
Loading…
Reference in New Issue
Block a user