mirror of
				https://github.com/encode/django-rest-framework.git
				synced 2025-11-04 18:08:03 +03:00 
			
		
		
		
	Merge pull request #743 from tomchristie/fix-datetime-regression
Fix datetime regression
This commit is contained in:
		
						commit
						6770a5bbaf
					
				| 
						 | 
				
			
			@ -199,7 +199,7 @@ If you want to override this behavior, you'll need to declare the `DateTimeField
 | 
			
		|||
 | 
			
		||||
**Signature:** `DateTimeField(format=None, input_formats=None)`
 | 
			
		||||
 | 
			
		||||
* `format` - A string representing the output format.  If not specified, the `DATETIME_FORMAT` setting will be used, which defaults to `'iso-8601'`.
 | 
			
		||||
* `format` - A string representing the output format.  If not specified, this defaults to `None`, which indicates that python `datetime` objects should be returned by `to_native`.  In this case the datetime encoding will be determined by the renderer. 
 | 
			
		||||
* `input_formats` - A list of strings representing the input formats which may be used to parse the date. If not specified, the `DATETIME_INPUT_FORMATS` setting will be used, which defaults to `['iso-8601']`.
 | 
			
		||||
 | 
			
		||||
DateTime format strings may either be [python strftime formats][strftime] which explicitly specifiy the format, or the special string `'iso-8601'`, which indicates that [ISO 8601][iso8601] style datetimes should be used. (eg `'2013-01-29T12:34:56.000000'`)
 | 
			
		||||
| 
						 | 
				
			
			@ -212,7 +212,7 @@ Corresponds to `django.db.models.fields.DateField`
 | 
			
		|||
 | 
			
		||||
**Signature:** `DateField(format=None, input_formats=None)`
 | 
			
		||||
 | 
			
		||||
* `format` - A string representing the output format.  If not specified, the `DATE_FORMAT` setting will be used, which defaults to `'iso-8601'`.
 | 
			
		||||
* `format` - A string representing the output format.  If not specified, this defaults to `None`, which indicates that python `date` objects should be returned by `to_native`.  In this case the date encoding will be determined by the renderer.
 | 
			
		||||
* `input_formats` - A list of strings representing the input formats which may be used to parse the date. If not specified, the `DATE_INPUT_FORMATS` setting will be used, which defaults to `['iso-8601']`. 
 | 
			
		||||
 | 
			
		||||
Date format strings may either be [python strftime formats][strftime] which explicitly specifiy the format, or the special string `'iso-8601'`, which indicates that [ISO 8601][iso8601] style dates should be used. (eg `'2013-01-29'`)
 | 
			
		||||
| 
						 | 
				
			
			@ -227,7 +227,7 @@ Corresponds to `django.db.models.fields.TimeField`
 | 
			
		|||
 | 
			
		||||
**Signature:** `TimeField(format=None, input_formats=None)`
 | 
			
		||||
 | 
			
		||||
* `format` - A string representing the output format.  If not specified, the `TIME_FORMAT` setting will be used, which defaults to `'iso-8601'`.
 | 
			
		||||
* `format` - A string representing the output format.  If not specified, this defaults to `None`, which indicates that python `time` objects should be returned by `to_native`.  In this case the time encoding will be determined by the renderer.
 | 
			
		||||
* `input_formats` - A list of strings representing the input formats which may be used to parse the date. If not specified, the `TIME_INPUT_FORMATS` setting will be used, which defaults to `['iso-8601']`.
 | 
			
		||||
 | 
			
		||||
Time format strings may either be [python strftime formats][strftime] which explicitly specifiy the format, or the special string `'iso-8601'`, which indicates that [ISO 8601][iso8601] style times should be used. (eg `'12:34:56.000000'`)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -192,44 +192,56 @@ Default: `'format'`
 | 
			
		|||
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
## Date/Time formatting
 | 
			
		||||
## Date and time formatting
 | 
			
		||||
 | 
			
		||||
*The following settings are used to control how date and time representations may be parsed and rendered.*
 | 
			
		||||
 | 
			
		||||
#### DATETIME_FORMAT
 | 
			
		||||
 | 
			
		||||
A format string that should be used by default for rendering the output of `DateTimeField` serializer fields.
 | 
			
		||||
A format string that should be used by default for rendering the output of `DateTimeField` serializer fields.  If `None`, then `DateTimeField` serializer fields will return python `datetime` objects, and the datetime encoding will be determined by the renderer.
 | 
			
		||||
 | 
			
		||||
Default: `'iso-8601'`
 | 
			
		||||
May be any of `None`, `'iso-8601'` or a python [strftime format][strftime] string.
 | 
			
		||||
 | 
			
		||||
Default: `None'`
 | 
			
		||||
 | 
			
		||||
#### DATETIME_INPUT_FORMATS
 | 
			
		||||
 | 
			
		||||
A list of format strings that should be used by default for parsing inputs to `DateTimeField` serializer fields.
 | 
			
		||||
 | 
			
		||||
May be a list including the string `'iso-8601'` or python [strftime format][strftime] strings.
 | 
			
		||||
 | 
			
		||||
Default: `['iso-8601']`
 | 
			
		||||
 | 
			
		||||
#### DATE_FORMAT
 | 
			
		||||
 | 
			
		||||
A format string that should be used by default for rendering the output of `DateField` serializer fields.
 | 
			
		||||
A format string that should be used by default for rendering the output of `DateField` serializer fields.  If `None`, then `DateField` serializer fields will return python `date` objects, and the date encoding will be determined by the renderer.
 | 
			
		||||
 | 
			
		||||
Default: `'iso-8601'`
 | 
			
		||||
May be any of `None`, `'iso-8601'` or a python [strftime format][strftime] string.
 | 
			
		||||
 | 
			
		||||
Default: `None`
 | 
			
		||||
 | 
			
		||||
#### DATE_INPUT_FORMATS
 | 
			
		||||
 | 
			
		||||
A list of format strings that should be used by default for parsing inputs to `DateField` serializer fields.
 | 
			
		||||
 | 
			
		||||
May be a list including the string `'iso-8601'` or python [strftime format][strftime] strings.
 | 
			
		||||
 | 
			
		||||
Default: `['iso-8601']`
 | 
			
		||||
 | 
			
		||||
#### TIME_FORMAT
 | 
			
		||||
 | 
			
		||||
A format string that should be used by default for rendering the output of `TimeField` serializer fields.
 | 
			
		||||
A format string that should be used by default for rendering the output of `TimeField` serializer fields.  If `None`, then `TimeField` serializer fields will return python `time` objects, and the time encoding will be determined by the renderer.
 | 
			
		||||
 | 
			
		||||
Default: `'iso-8601'`
 | 
			
		||||
May be any of `None`, `'iso-8601'` or a python [strftime format][strftime] string.
 | 
			
		||||
 | 
			
		||||
Default: `None`
 | 
			
		||||
 | 
			
		||||
#### TIME_INPUT_FORMATS
 | 
			
		||||
 | 
			
		||||
A list of format strings that should be used by default for parsing inputs to `TimeField` serializer fields.
 | 
			
		||||
 | 
			
		||||
May be a list including the string `'iso-8601'` or python [strftime format][strftime] strings.
 | 
			
		||||
 | 
			
		||||
Default: `['iso-8601']`
 | 
			
		||||
 | 
			
		||||
---
 | 
			
		||||
| 
						 | 
				
			
			@ -243,3 +255,4 @@ The name of a parameter in the URL conf that may be used to provide a format suf
 | 
			
		|||
Default: `'format'`
 | 
			
		||||
 | 
			
		||||
[cite]: http://www.python.org/dev/peps/pep-0020/
 | 
			
		||||
[strftime]: http://docs.python.org/2/library/time.html#time.strftime
 | 
			
		||||
| 
						 | 
				
			
			@ -494,7 +494,7 @@ class DateField(WritableField):
 | 
			
		|||
    }
 | 
			
		||||
    empty = None
 | 
			
		||||
    input_formats = api_settings.DATE_INPUT_FORMATS
 | 
			
		||||
    format = api_settings.DATE_FORMAT
 | 
			
		||||
    format = None
 | 
			
		||||
 | 
			
		||||
    def __init__(self, input_formats=None, format=None, *args, **kwargs):
 | 
			
		||||
        self.input_formats = input_formats if input_formats is not None else self.input_formats
 | 
			
		||||
| 
						 | 
				
			
			@ -536,8 +536,8 @@ class DateField(WritableField):
 | 
			
		|||
        raise ValidationError(msg)
 | 
			
		||||
 | 
			
		||||
    def to_native(self, value):
 | 
			
		||||
        if value is None:
 | 
			
		||||
            return None
 | 
			
		||||
        if value is None or self.format is None:
 | 
			
		||||
            return value
 | 
			
		||||
 | 
			
		||||
        if isinstance(value, datetime.datetime):
 | 
			
		||||
            value = value.date()
 | 
			
		||||
| 
						 | 
				
			
			@ -557,7 +557,7 @@ class DateTimeField(WritableField):
 | 
			
		|||
    }
 | 
			
		||||
    empty = None
 | 
			
		||||
    input_formats = api_settings.DATETIME_INPUT_FORMATS
 | 
			
		||||
    format = api_settings.DATETIME_FORMAT
 | 
			
		||||
    format = None
 | 
			
		||||
 | 
			
		||||
    def __init__(self, input_formats=None, format=None, *args, **kwargs):
 | 
			
		||||
        self.input_formats = input_formats if input_formats is not None else self.input_formats
 | 
			
		||||
| 
						 | 
				
			
			@ -605,11 +605,14 @@ class DateTimeField(WritableField):
 | 
			
		|||
        raise ValidationError(msg)
 | 
			
		||||
 | 
			
		||||
    def to_native(self, value):
 | 
			
		||||
        if value is None:
 | 
			
		||||
            return None
 | 
			
		||||
        if value is None or self.format is None:
 | 
			
		||||
            return value
 | 
			
		||||
 | 
			
		||||
        if self.format.lower() == ISO_8601:
 | 
			
		||||
            return value.isoformat()
 | 
			
		||||
            ret = value.isoformat()
 | 
			
		||||
            if ret.endswith('+00:00'):
 | 
			
		||||
                ret = ret[:-6] + 'Z'
 | 
			
		||||
            return ret
 | 
			
		||||
        return value.strftime(self.format)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -623,7 +626,7 @@ class TimeField(WritableField):
 | 
			
		|||
    }
 | 
			
		||||
    empty = None
 | 
			
		||||
    input_formats = api_settings.TIME_INPUT_FORMATS
 | 
			
		||||
    format = api_settings.TIME_FORMAT
 | 
			
		||||
    format = None
 | 
			
		||||
 | 
			
		||||
    def __init__(self, input_formats=None, format=None, *args, **kwargs):
 | 
			
		||||
        self.input_formats = input_formats if input_formats is not None else self.input_formats
 | 
			
		||||
| 
						 | 
				
			
			@ -658,8 +661,8 @@ class TimeField(WritableField):
 | 
			
		|||
        raise ValidationError(msg)
 | 
			
		||||
 | 
			
		||||
    def to_native(self, value):
 | 
			
		||||
        if value is None:
 | 
			
		||||
            return None
 | 
			
		||||
        if value is None or self.format is None:
 | 
			
		||||
            return value
 | 
			
		||||
 | 
			
		||||
        if isinstance(value, datetime.datetime):
 | 
			
		||||
            value = value.time()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -153,12 +153,22 @@ class DateFieldTest(TestCase):
 | 
			
		|||
 | 
			
		||||
    def test_to_native(self):
 | 
			
		||||
        """
 | 
			
		||||
        Make sure to_native() returns isoformat as default.
 | 
			
		||||
        Make sure to_native() returns datetime as default.
 | 
			
		||||
        """
 | 
			
		||||
        f = serializers.DateField()
 | 
			
		||||
 | 
			
		||||
        result_1 = f.to_native(datetime.date(1984, 7, 31))
 | 
			
		||||
 | 
			
		||||
        self.assertEqual(datetime.date(1984, 7, 31), result_1)
 | 
			
		||||
 | 
			
		||||
    def test_to_native_iso(self):
 | 
			
		||||
        """
 | 
			
		||||
        Make sure to_native() with 'iso-8601' returns iso formated date.
 | 
			
		||||
        """
 | 
			
		||||
        f = serializers.DateField(format='iso-8601')
 | 
			
		||||
 | 
			
		||||
        result_1 = f.to_native(datetime.date(1984, 7, 31))
 | 
			
		||||
 | 
			
		||||
        self.assertEqual('1984-07-31', result_1)
 | 
			
		||||
 | 
			
		||||
    def test_to_native_custom_format(self):
 | 
			
		||||
| 
						 | 
				
			
			@ -289,6 +299,22 @@ class DateTimeFieldTest(TestCase):
 | 
			
		|||
        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(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)
 | 
			
		||||
 | 
			
		||||
    def test_to_native_iso(self):
 | 
			
		||||
        """
 | 
			
		||||
        Make sure to_native() with format=iso-8601 returns iso formatted datetime.
 | 
			
		||||
        """
 | 
			
		||||
        f = serializers.DateTimeField(format='iso-8601')
 | 
			
		||||
 | 
			
		||||
        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)
 | 
			
		||||
| 
						 | 
				
			
			@ -419,13 +445,26 @@ class TimeFieldTest(TestCase):
 | 
			
		|||
 | 
			
		||||
    def test_to_native(self):
 | 
			
		||||
        """
 | 
			
		||||
        Make sure to_native() returns isoformat as default.
 | 
			
		||||
        Make sure to_native() returns time object 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(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_to_native_iso(self):
 | 
			
		||||
        """
 | 
			
		||||
        Make sure to_native() with format='iso-8601' returns iso formatted time.
 | 
			
		||||
        """
 | 
			
		||||
        f = serializers.TimeField(format='iso-8601')
 | 
			
		||||
        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)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -65,7 +65,7 @@ class IntegrationTestFiltering(TestCase):
 | 
			
		|||
 | 
			
		||||
        self.objects = FilterableItem.objects
 | 
			
		||||
        self.data = [
 | 
			
		||||
            {'id': obj.id, 'text': obj.text, 'decimal': obj.decimal, 'date': obj.date.isoformat()}
 | 
			
		||||
            {'id': obj.id, 'text': obj.text, 'decimal': obj.decimal, 'date': obj.date}
 | 
			
		||||
            for obj in self.objects.all()
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -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.assertEqual(response.status_code, status.HTTP_200_OK)
 | 
			
		||||
        expected_data = [f for f in self.data if datetime.datetime.strptime(f['date'], '%Y-%m-%d').date() == search_date]
 | 
			
		||||
        expected_data = [f for f in self.data if f['date'] == search_date]
 | 
			
		||||
        self.assertEqual(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.assertEqual(response.status_code, status.HTTP_200_OK)
 | 
			
		||||
        expected_data = [f for f in self.data if datetime.datetime.strptime(f['date'], '%Y-%m-%d').date() > search_date]
 | 
			
		||||
        expected_data = [f for f in self.data if f['date'] > search_date]
 | 
			
		||||
        self.assertEqual(response.data, expected_data)
 | 
			
		||||
 | 
			
		||||
        # Tests that the text filter set with 'icontains' in the filter class works.
 | 
			
		||||
| 
						 | 
				
			
			@ -142,8 +142,7 @@ class IntegrationTestFiltering(TestCase):
 | 
			
		|||
        request = factory.get('/?decimal=%s&date=%s' % (search_decimal, search_date))
 | 
			
		||||
        response = view(request).render()
 | 
			
		||||
        self.assertEqual(response.status_code, status.HTTP_200_OK)
 | 
			
		||||
        expected_data = [f for f in self.data if
 | 
			
		||||
                         datetime.datetime.strptime(f['date'], '%Y-%m-%d').date() > search_date and
 | 
			
		||||
        expected_data = [f for f in self.data if f['date'] > search_date and
 | 
			
		||||
                         f['decimal'] < search_decimal]
 | 
			
		||||
        self.assertEqual(response.data, expected_data)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -102,7 +102,7 @@ class IntegrationTestPaginationAndFiltering(TestCase):
 | 
			
		|||
 | 
			
		||||
        self.objects = FilterableItem.objects
 | 
			
		||||
        self.data = [
 | 
			
		||||
            {'id': obj.id, 'text': obj.text, 'decimal': obj.decimal, 'date': obj.date.isoformat()}
 | 
			
		||||
            {'id': obj.id, 'text': obj.text, 'decimal': obj.decimal, 'date': obj.date}
 | 
			
		||||
            for obj in self.objects.all()
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -112,7 +112,7 @@ class BasicTests(TestCase):
 | 
			
		|||
        self.expected = {
 | 
			
		||||
            'email': 'tom@example.com',
 | 
			
		||||
            'content': 'Happy new year!',
 | 
			
		||||
            'created': '2012-01-01T00:00:00',
 | 
			
		||||
            'created': datetime.datetime(2012, 1, 1),
 | 
			
		||||
            'sub_comment': 'And Merry Christmas!'
 | 
			
		||||
        }
 | 
			
		||||
        self.person_data = {'name': 'dwight', 'age': 35}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue
	
	Block a user