From 299b3b1996a13ffb9318e061547173a46d484478 Mon Sep 17 00:00:00 2001 From: Dmitriy Tatarkin Date: Mon, 12 Aug 2019 19:00:07 +0300 Subject: [PATCH] Fixed "can't compare offset-naive and offset-aware datetimes" (#337) * Fixed "can't compare offset-naive and offset-aware datetimes" when USE_TZ = True * Fixed case when `USE_TZ = False` --- .gitignore | 1 + AUTHORS | 1 + constance/admin.py | 16 ++++++++++------ tests/settings.py | 2 ++ tests/test_admin.py | 23 +++++++++++++++++++++++ tests/test_cli.py | 7 ++++++- 6 files changed, 43 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index e4022af..d5f90e3 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ test.db .tox .coverage docs/_build +.idea diff --git a/AUTHORS b/AUTHORS index 4618079..9a8b030 100644 --- a/AUTHORS +++ b/AUTHORS @@ -39,3 +39,4 @@ saw2th trbs vl <1844144@gmail.com> vl +Dmitriy Tatarkin diff --git a/constance/admin.py b/constance/admin.py index d17b50f..a011b70 100644 --- a/constance/admin.py +++ b/constance/admin.py @@ -3,11 +3,9 @@ from datetime import datetime, date, time, timedelta from decimal import Decimal from operator import itemgetter import hashlib -import os -from django import forms, VERSION +from django import forms, VERSION, conf from django.apps import apps -from django.conf import settings as django_settings from django.conf.urls import url from django.contrib import admin, messages from django.contrib.admin import widgets @@ -17,7 +15,7 @@ from django.core.files.storage import default_storage from django.forms import fields from django.http import HttpResponseRedirect from django.template.response import TemplateResponse -from django.utils import six +from django.utils import six, timezone from django.utils.encoding import smart_bytes from django.utils.formats import localize from django.utils.module_loading import import_string @@ -142,8 +140,14 @@ class ConstanceForm(forms.Form): self.cleaned_data[file_field] = default_storage.save(file.name, file) for name in settings.CONFIG: - if getattr(config, name) != self.cleaned_data[name]: - setattr(config, name, self.cleaned_data[name]) + current = getattr(config, name) + new = self.cleaned_data[name] + + if conf.settings.USE_TZ and isinstance(current, datetime) and not timezone.is_aware(current): + current = timezone.make_aware(current) + + if current != new: + setattr(config, name, new) def clean_version(self): value = self.cleaned_data['version'] diff --git a/tests/settings.py b/tests/settings.py index c37daa8..404e111 100644 --- a/tests/settings.py +++ b/tests/settings.py @@ -63,6 +63,8 @@ CONSTANCE_ADDITIONAL_FIELDS = { 'email': ('django.forms.fields.EmailField',), } +USE_TZ = True + CONSTANCE_CONFIG = { 'INT_VALUE': (1, 'some int'), 'LONG_VALUE': (long_value, 'some looong int'), diff --git a/tests/test_admin.py b/tests/test_admin.py index a6d7706..8a10feb 100644 --- a/tests/test_admin.py +++ b/tests/test_admin.py @@ -1,3 +1,5 @@ +from datetime import datetime + import mock from django.contrib import admin from django.contrib.auth.models import User, Permission @@ -99,6 +101,27 @@ class TestAdmin(TestCase): response = self.options.changelist_view(request, {}) self.assertIsInstance(response, HttpResponseRedirect) + @mock.patch('constance.settings.CONFIG', { + 'DATETIME_VALUE': (datetime(2019, 8, 7, 18, 40, 0), 'some naive datetime'), + }) + @mock.patch('constance.settings.IGNORE_ADMIN_VERSION_CHECK', True) + @mock.patch('tests.redis_mockup.Connection.set', mock.MagicMock()) + def test_submit_aware_datetime(self): + """ + Test that submitting the admin page results in an http redirect when + everything is in order. + """ + request = self.rf.post('/admin/constance/config/', data={ + "DATETIME_VALUE_0": "2019-08-07", + "DATETIME_VALUE_1": "19:17:01", + "version": "123", + }) + request.user = self.superuser + request._dont_enforce_csrf_checks = True + with mock.patch("django.contrib.messages.add_message"): + response = self.options.changelist_view(request, {}) + self.assertIsInstance(response, HttpResponseRedirect) + @mock.patch('constance.settings.CONFIG_FIELDSETS', { 'Numbers': ('LONG_VALUE', 'INT_VALUE',), 'Text': ('STRING_VALUE', 'UNICODE_VALUE'), diff --git a/tests/test_cli.py b/tests/test_cli.py index 74df804..b0b9988 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -3,8 +3,10 @@ from datetime import datetime from textwrap import dedent +from django.conf import settings from django.core.management import call_command, CommandError from django.test import TransactionTestCase +from django.utils import timezone from django.utils.encoding import smart_str from django.utils.six import StringIO @@ -53,7 +55,10 @@ u""" BOOL_VALUE True call_command('constance', *('set', 'DATETIME_VALUE', '2011-09-24', '12:30:25'), stdout=self.out) - self.assertEqual(config.DATETIME_VALUE, datetime(2011, 9, 24, 12, 30, 25)) + expected = datetime(2011, 9, 24, 12, 30, 25) + if settings.USE_TZ: + expected = timezone.make_aware(expected) + self.assertEqual(config.DATETIME_VALUE, expected) def test_get_invalid_name(self): self.assertRaisesMessage(CommandError, "NOT_A_REAL_CONFIG is not defined in settings.CONSTANCE_CONFIG",