Fixed #23524 -- Allowed DATABASES['TIME_ZONE'] option on PostgreSQL.
This commit is contained in:
parent
ad88524e4d
commit
c06492dd87
|
@ -124,11 +124,11 @@ class BaseDatabaseWrapper:
|
||||||
|
|
||||||
When the database backend supports time zones, it doesn't matter which
|
When the database backend supports time zones, it doesn't matter which
|
||||||
time zone Django uses, as long as aware datetimes are used everywhere.
|
time zone Django uses, as long as aware datetimes are used everywhere.
|
||||||
For simplicity, Django selects UTC.
|
Other users connecting to the database can choose their own time zone.
|
||||||
|
|
||||||
When the database backend doesn't support time zones, the time zone
|
When the database backend doesn't support time zones, the time zone
|
||||||
Django uses can be selected with the TIME_ZONE configuration option, so
|
Django uses may be constrained by the requirements of other users of
|
||||||
it can match what other users of the database expect.
|
the database.
|
||||||
"""
|
"""
|
||||||
if not settings.USE_TZ:
|
if not settings.USE_TZ:
|
||||||
return None
|
return None
|
||||||
|
@ -205,15 +205,11 @@ class BaseDatabaseWrapper:
|
||||||
self.run_on_commit = []
|
self.run_on_commit = []
|
||||||
|
|
||||||
def check_settings(self):
|
def check_settings(self):
|
||||||
if self.settings_dict['TIME_ZONE'] is not None:
|
if self.settings_dict['TIME_ZONE'] is not None and not settings.USE_TZ:
|
||||||
if not settings.USE_TZ:
|
|
||||||
raise ImproperlyConfigured(
|
raise ImproperlyConfigured(
|
||||||
"Connection '%s' cannot set TIME_ZONE because USE_TZ is "
|
"Connection '%s' cannot set TIME_ZONE because USE_TZ is False."
|
||||||
"False." % self.alias)
|
% self.alias
|
||||||
elif self.features.supports_timezones:
|
)
|
||||||
raise ImproperlyConfigured(
|
|
||||||
"Connection '%s' cannot set TIME_ZONE because its engine "
|
|
||||||
"handles time zones conversions natively." % self.alias)
|
|
||||||
|
|
||||||
@async_unsafe
|
@async_unsafe
|
||||||
def ensure_connection(self):
|
def ensure_connection(self):
|
||||||
|
|
|
@ -47,7 +47,6 @@ from .features import DatabaseFeatures # NOQA isort:skip
|
||||||
from .introspection import DatabaseIntrospection # NOQA isort:skip
|
from .introspection import DatabaseIntrospection # NOQA isort:skip
|
||||||
from .operations import DatabaseOperations # NOQA isort:skip
|
from .operations import DatabaseOperations # NOQA isort:skip
|
||||||
from .schema import DatabaseSchemaEditor # NOQA isort:skip
|
from .schema import DatabaseSchemaEditor # NOQA isort:skip
|
||||||
from .utils import utc_tzinfo_factory # NOQA isort:skip
|
|
||||||
|
|
||||||
psycopg2.extensions.register_adapter(SafeString, psycopg2.extensions.QuotedString)
|
psycopg2.extensions.register_adapter(SafeString, psycopg2.extensions.QuotedString)
|
||||||
psycopg2.extras.register_uuid()
|
psycopg2.extras.register_uuid()
|
||||||
|
@ -231,9 +230,12 @@ class DatabaseWrapper(BaseDatabaseWrapper):
|
||||||
cursor = self.connection.cursor(name, scrollable=False, withhold=self.connection.autocommit)
|
cursor = self.connection.cursor(name, scrollable=False, withhold=self.connection.autocommit)
|
||||||
else:
|
else:
|
||||||
cursor = self.connection.cursor()
|
cursor = self.connection.cursor()
|
||||||
cursor.tzinfo_factory = utc_tzinfo_factory if settings.USE_TZ else None
|
cursor.tzinfo_factory = self.tzinfo_factory if settings.USE_TZ else None
|
||||||
return cursor
|
return cursor
|
||||||
|
|
||||||
|
def tzinfo_factory(self, offset):
|
||||||
|
return self.timezone
|
||||||
|
|
||||||
@async_unsafe
|
@async_unsafe
|
||||||
def chunked_cursor(self):
|
def chunked_cursor(self):
|
||||||
self._named_cursor_idx += 1
|
self._named_cursor_idx += 1
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
from django.utils.timezone import utc
|
|
||||||
|
|
||||||
|
|
||||||
def utc_tzinfo_factory(offset):
|
|
||||||
if offset != 0:
|
|
||||||
raise AssertionError("database connection isn't set to UTC")
|
|
||||||
return utc
|
|
|
@ -121,8 +121,11 @@ Django needs the following parameters for its database connections:
|
||||||
- ``client_encoding``: ``'UTF8'``,
|
- ``client_encoding``: ``'UTF8'``,
|
||||||
- ``default_transaction_isolation``: ``'read committed'`` by default,
|
- ``default_transaction_isolation``: ``'read committed'`` by default,
|
||||||
or the value set in the connection options (see below),
|
or the value set in the connection options (see below),
|
||||||
- ``timezone``: ``'UTC'`` when :setting:`USE_TZ` is ``True``, value of
|
- ``timezone``:
|
||||||
:setting:`TIME_ZONE` otherwise.
|
- when :setting:`USE_TZ` is ``True``, ``'UTC'`` by default, or the
|
||||||
|
:setting:`TIME_ZONE <DATABASE-TIME_ZONE>` value set for the connection,
|
||||||
|
- when :setting:`USE_TZ` is ``False``, the value of the global
|
||||||
|
:setting:`TIME_ZONE` setting.
|
||||||
|
|
||||||
If these parameters already have the correct values, Django won't set them for
|
If these parameters already have the correct values, Django won't set them for
|
||||||
every new connection, which improves performance slightly. You can configure
|
every new connection, which improves performance slightly. You can configure
|
||||||
|
|
|
@ -632,24 +632,53 @@ default port. Not used with SQLite.
|
||||||
|
|
||||||
Default: ``None``
|
Default: ``None``
|
||||||
|
|
||||||
A string representing the time zone for datetimes stored in this database
|
A string representing the time zone for this database connection or ``None``.
|
||||||
(assuming that it doesn't support time zones) or ``None``. This inner option of
|
This inner option of the :setting:`DATABASES` setting accepts the same values
|
||||||
the :setting:`DATABASES` setting accepts the same values as the general
|
as the general :setting:`TIME_ZONE` setting.
|
||||||
:setting:`TIME_ZONE` setting.
|
|
||||||
|
|
||||||
This allows interacting with third-party databases that store datetimes in
|
|
||||||
local time rather than UTC. To avoid issues around DST changes, you shouldn't
|
|
||||||
set this option for databases managed by Django.
|
|
||||||
|
|
||||||
When :setting:`USE_TZ` is ``True`` and the database doesn't support time zones
|
|
||||||
(e.g. SQLite, MySQL, Oracle), Django reads and writes datetimes in local time
|
|
||||||
according to this option if it is set and in UTC if it isn't.
|
|
||||||
|
|
||||||
When :setting:`USE_TZ` is ``True`` and the database supports time zones (e.g.
|
|
||||||
PostgreSQL), it is an error to set this option.
|
|
||||||
|
|
||||||
|
When :setting:`USE_TZ` is ``True`` and this option is set, reading datetimes
|
||||||
|
from the database returns aware datetimes in this time zone instead of UTC.
|
||||||
When :setting:`USE_TZ` is ``False``, it is an error to set this option.
|
When :setting:`USE_TZ` is ``False``, it is an error to set this option.
|
||||||
|
|
||||||
|
* If the database backend doesn't support time zones (e.g. SQLite, MySQL,
|
||||||
|
Oracle), Django reads and writes datetimes in local time according to this
|
||||||
|
option if it is set and in UTC if it isn't.
|
||||||
|
|
||||||
|
Changing the connection time zone changes how datetimes are read from and
|
||||||
|
written to the database.
|
||||||
|
|
||||||
|
* If Django manages the database and you don't have a strong reason to do
|
||||||
|
otherwise, you should leave this option unset. It's best to store datetimes
|
||||||
|
in UTC because it avoids ambiguous or nonexistent datetimes during daylight
|
||||||
|
saving time changes. Also, receiving datetimes in UTC keeps datetime
|
||||||
|
arithmetic simple — there's no need for the ``normalize()`` method provided
|
||||||
|
by pytz.
|
||||||
|
|
||||||
|
* If you're connecting to a third-party database that stores datetimes in a
|
||||||
|
local time rather than UTC, then you must set this option to the
|
||||||
|
appropriate time zone. Likewise, if Django manages the database but
|
||||||
|
third-party systems connect to the same database and expect to find
|
||||||
|
datetimes in local time, then you must set this option.
|
||||||
|
|
||||||
|
* If the database backend supports time zones (e.g. PostgreSQL), the
|
||||||
|
``TIME_ZONE`` option is very rarely needed. It can be changed at any time;
|
||||||
|
the database takes care of converting datetimes to the desired time zone.
|
||||||
|
|
||||||
|
Setting the time zone of the database connection may be useful for running
|
||||||
|
raw SQL queries involving date/time functions provided by the database, such
|
||||||
|
as ``date_trunc``, because their results depend on the time zone.
|
||||||
|
|
||||||
|
However, this has a downside: receiving all datetimes in local time makes
|
||||||
|
datetime arithmetic more tricky — you must call the ``normalize()`` method
|
||||||
|
provided by pytz after each operation.
|
||||||
|
|
||||||
|
Consider converting to local time explicitly with ``AT TIME ZONE`` in raw SQL
|
||||||
|
queries instead of setting the ``TIME_ZONE`` option.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.1
|
||||||
|
|
||||||
|
Using this option when the database backend supports time zones was allowed.
|
||||||
|
|
||||||
.. setting:: DATABASE-DISABLE_SERVER_SIDE_CURSORS
|
.. setting:: DATABASE-DISABLE_SERVER_SIDE_CURSORS
|
||||||
|
|
||||||
``DISABLE_SERVER_SIDE_CURSORS``
|
``DISABLE_SERVER_SIDE_CURSORS``
|
||||||
|
|
|
@ -283,6 +283,9 @@ Miscellaneous
|
||||||
:class:`pathlib.Path` instead of :mod:`os.path` for building filesystem
|
:class:`pathlib.Path` instead of :mod:`os.path` for building filesystem
|
||||||
paths.
|
paths.
|
||||||
|
|
||||||
|
* The :setting:`TIME_ZONE <DATABASE-TIME_ZONE>` setting is now allowed on
|
||||||
|
databases that support time zones.
|
||||||
|
|
||||||
.. _backwards-incompatible-3.1:
|
.. _backwards-incompatible-3.1:
|
||||||
|
|
||||||
Backwards incompatible changes in 3.1
|
Backwards incompatible changes in 3.1
|
||||||
|
|
|
@ -9,8 +9,7 @@ import pytz
|
||||||
|
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.core import serializers
|
from django.core import serializers
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.db import connection
|
||||||
from django.db import connection, connections
|
|
||||||
from django.db.models import F, Max, Min
|
from django.db.models import F, Max, Min
|
||||||
from django.http import HttpRequest
|
from django.http import HttpRequest
|
||||||
from django.template import (
|
from django.template import (
|
||||||
|
@ -532,6 +531,14 @@ class NewDatabaseTests(TestCase):
|
||||||
cursor.execute('SELECT dt FROM timezones_event WHERE dt = %s', [utc_naive_dt])
|
cursor.execute('SELECT dt FROM timezones_event WHERE dt = %s', [utc_naive_dt])
|
||||||
self.assertEqual(cursor.fetchall()[0][0], utc_naive_dt)
|
self.assertEqual(cursor.fetchall()[0][0], utc_naive_dt)
|
||||||
|
|
||||||
|
@skipUnlessDBFeature('supports_timezones')
|
||||||
|
def test_cursor_explicit_time_zone(self):
|
||||||
|
with override_database_connection_timezone('Europe/Paris'):
|
||||||
|
with connection.cursor() as cursor:
|
||||||
|
cursor.execute('SELECT CURRENT_TIMESTAMP')
|
||||||
|
now = cursor.fetchone()[0]
|
||||||
|
self.assertEqual(now.tzinfo.zone, 'Europe/Paris')
|
||||||
|
|
||||||
@requires_tz_support
|
@requires_tz_support
|
||||||
def test_filter_date_field_with_aware_datetime(self):
|
def test_filter_date_field_with_aware_datetime(self):
|
||||||
# Regression test for #17742
|
# Regression test for #17742
|
||||||
|
@ -595,27 +602,6 @@ class ForcedTimeZoneDatabaseTests(TransactionTestCase):
|
||||||
self.assertEqual(event.dt, fake_dt)
|
self.assertEqual(event.dt, fake_dt)
|
||||||
|
|
||||||
|
|
||||||
@skipUnlessDBFeature('supports_timezones')
|
|
||||||
@override_settings(TIME_ZONE='Africa/Nairobi', USE_TZ=True)
|
|
||||||
class UnsupportedTimeZoneDatabaseTests(TestCase):
|
|
||||||
|
|
||||||
def test_time_zone_parameter_not_supported_if_database_supports_timezone(self):
|
|
||||||
connections.databases['tz'] = connections.databases['default'].copy()
|
|
||||||
connections.databases['tz']['TIME_ZONE'] = 'Asia/Bangkok'
|
|
||||||
tz_conn = connections['tz']
|
|
||||||
try:
|
|
||||||
msg = (
|
|
||||||
"Connection 'tz' cannot set TIME_ZONE because its engine "
|
|
||||||
"handles time zones conversions natively."
|
|
||||||
)
|
|
||||||
with self.assertRaisesMessage(ImproperlyConfigured, msg):
|
|
||||||
tz_conn.cursor()
|
|
||||||
finally:
|
|
||||||
connections['tz'].close() # in case the test fails
|
|
||||||
del connections['tz']
|
|
||||||
del connections.databases['tz']
|
|
||||||
|
|
||||||
|
|
||||||
@override_settings(TIME_ZONE='Africa/Nairobi')
|
@override_settings(TIME_ZONE='Africa/Nairobi')
|
||||||
class SerializationTests(SimpleTestCase):
|
class SerializationTests(SimpleTestCase):
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue