From 9c2cf518c24f46218749c8ccb7070e626a527b08 Mon Sep 17 00:00:00 2001 From: Tim Saylor Date: Wed, 8 Jul 2015 23:06:15 -0500 Subject: [PATCH 1/4] When making datetimes aware, use the active timezone if it exists. Fixes #2850. --- rest_framework/fields.py | 8 +++++++- tests/test_fields.py | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 3ca7d682e..b8e2e1b2b 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -13,6 +13,7 @@ from django.core.exceptions import ValidationError as DjangoValidationError from django.core.exceptions import ObjectDoesNotExist from django.core.validators import RegexValidator, ip_address_validators from django.forms import ImageField as DjangoImageField +from django.forms.utils import from_current_timezone from django.utils import six, timezone from django.utils.dateparse import parse_date, parse_datetime, parse_time from django.utils.encoding import is_protected_type, smart_text @@ -903,7 +904,12 @@ class DateTimeField(Field): When `self.default_timezone` is not `None`, always return aware datetimes. """ if (self.default_timezone is not None) and not timezone.is_aware(value): - return timezone.make_aware(value, self.default_timezone) + # If a timezone is active, we want to use it, but if not we want to use the timezone + # specified for this field over the system's default timezone. + if hasattr(timezone._active, 'value'): + return from_current_timezone(value) + else: + return timezone.make_aware(value, self.default_timezone) elif (self.default_timezone is None) and timezone.is_aware(value): return timezone.make_naive(value, timezone.UTC()) return value diff --git a/tests/test_fields.py b/tests/test_fields.py index 76e6d9d60..b3dad35e2 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -4,6 +4,7 @@ from decimal import Decimal import django import pytest +from django.test.utils import override_settings from django.utils import timezone import rest_framework @@ -848,6 +849,40 @@ class TestNoOutputFormatDateField(FieldValues): field = serializers.DateField(format=None) +class FakeTimezone(datetime.tzinfo): + + def __repr__(self): + return "" + + def utcoffset(self, dt): + return datetime.timedelta(1) + + def tzname(self, dt): + return "FakeTimezone" + + def dst(self, dt): + return datetime.timedelta(1) + + +class TestAwareDateTimeField: + + @override_settings(USE_TZ=True) + def test_with_timezone_active(self): + naive_now = timezone.make_naive(timezone.now()) + timezone.activate(FakeTimezone()) + field = serializers.DateTimeField(default_timezone=timezone.UTC()) + aware_now = field.enforce_timezone(naive_now) + assert aware_now.tzname() == 'FakeTimezone' + timezone.deactivate() + + @override_settings(USE_TZ=True) + def test_without_timezone_active(self): + naive_now = timezone.make_naive(timezone.now()) + field = serializers.DateTimeField(default_timezone=timezone.UTC()) + aware_now = field.enforce_timezone(naive_now) + assert aware_now.tzname() == 'UTC' + + class TestDateTimeField(FieldValues): """ Valid and invalid values for `DateTimeField`. From b182ee596626fdc431a7ef553a8c9ffd2146b262 Mon Sep 17 00:00:00 2001 From: Tim Saylor Date: Wed, 8 Jul 2015 23:57:06 -0500 Subject: [PATCH 2/4] fixed old version compatibility for make_naive and django.forms.utils --- rest_framework/fields.py | 7 ++++++- tests/test_fields.py | 4 ++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index b8e2e1b2b..8c76e7f3e 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -13,7 +13,6 @@ from django.core.exceptions import ValidationError as DjangoValidationError from django.core.exceptions import ObjectDoesNotExist from django.core.validators import RegexValidator, ip_address_validators from django.forms import ImageField as DjangoImageField -from django.forms.utils import from_current_timezone from django.utils import six, timezone from django.utils.dateparse import parse_date, parse_datetime, parse_time from django.utils.encoding import is_protected_type, smart_text @@ -30,6 +29,12 @@ from rest_framework.exceptions import ValidationError from rest_framework.settings import api_settings from rest_framework.utils import html, humanize_datetime, representation +# django.form.util was renamed in 1.7 +try: + from django.forms.utils import from_current_timezone +except ImportError: + from django.forms.util import from_current_timezone + class empty: """ diff --git a/tests/test_fields.py b/tests/test_fields.py index b3dad35e2..80375789c 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -868,7 +868,7 @@ class TestAwareDateTimeField: @override_settings(USE_TZ=True) def test_with_timezone_active(self): - naive_now = timezone.make_naive(timezone.now()) + naive_now = timezone.make_naive(timezone.now(), timezone.UTC()) timezone.activate(FakeTimezone()) field = serializers.DateTimeField(default_timezone=timezone.UTC()) aware_now = field.enforce_timezone(naive_now) @@ -877,7 +877,7 @@ class TestAwareDateTimeField: @override_settings(USE_TZ=True) def test_without_timezone_active(self): - naive_now = timezone.make_naive(timezone.now()) + naive_now = timezone.make_naive(timezone.now(), timezone.UTC()) field = serializers.DateTimeField(default_timezone=timezone.UTC()) aware_now = field.enforce_timezone(naive_now) assert aware_now.tzname() == 'UTC' From a8250984403671d4b5d9a20cf658b536d11b2490 Mon Sep 17 00:00:00 2001 From: Tim Saylor Date: Thu, 9 Jul 2015 11:01:07 -0500 Subject: [PATCH 3/4] from_current_timezone is unnecessary, using timezone.make_aware instead --- rest_framework/fields.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index 8c76e7f3e..aaa61ff5a 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -29,12 +29,6 @@ from rest_framework.exceptions import ValidationError from rest_framework.settings import api_settings from rest_framework.utils import html, humanize_datetime, representation -# django.form.util was renamed in 1.7 -try: - from django.forms.utils import from_current_timezone -except ImportError: - from django.forms.util import from_current_timezone - class empty: """ @@ -909,10 +903,10 @@ class DateTimeField(Field): When `self.default_timezone` is not `None`, always return aware datetimes. """ if (self.default_timezone is not None) and not timezone.is_aware(value): - # If a timezone is active, we want to use it, but if not we want to use the timezone + # If a timezone is active we want to use it, but if not we want to use the timezone # specified for this field over the system's default timezone. if hasattr(timezone._active, 'value'): - return from_current_timezone(value) + return timezone.make_aware(value, timezone._active.value) else: return timezone.make_aware(value, self.default_timezone) elif (self.default_timezone is None) and timezone.is_aware(value): From 48e75379c19bf36bb6dee6344d624fdbe945267b Mon Sep 17 00:00:00 2001 From: Tim Saylor Date: Thu, 9 Jul 2015 13:01:19 -0500 Subject: [PATCH 4/4] convert datetimes to the appropriate timezone on their way out --- rest_framework/fields.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/rest_framework/fields.py b/rest_framework/fields.py index aaa61ff5a..45fadc9f9 100644 --- a/rest_framework/fields.py +++ b/rest_framework/fields.py @@ -941,6 +941,11 @@ class DateTimeField(Field): self.fail('invalid', format=humanized_format) def to_representation(self, value): + if timezone.is_aware(value): + # convert the datetime to the timezone the user is expecting + tz = getattr(timezone._active, "value", self.default_timezone) + value = timezone.localtime(value, tz) + if self.format is None: return value