Fixed #23820 -- Supported per-database time zone.
The primary use case is to interact with a third-party database (not primarily managed by Django) that doesn't support time zones and where datetimes are stored in local time when USE_TZ is True. Configuring a PostgreSQL database with the TIME_ZONE option while USE_TZ is False used to result in silent data corruption. Now this is an error.
This commit is contained in:
parent
54026f1e8d
commit
ed83881e64
|
@ -20,10 +20,9 @@ router = ConnectionRouter()
|
||||||
# `connection`, `DatabaseError` and `IntegrityError` are convenient aliases
|
# `connection`, `DatabaseError` and `IntegrityError` are convenient aliases
|
||||||
# for backend bits.
|
# for backend bits.
|
||||||
|
|
||||||
# DatabaseWrapper.__init__() takes a dictionary, not a settings module, so
|
# DatabaseWrapper.__init__() takes a dictionary, not a settings module, so we
|
||||||
# we manually create the dictionary from the settings, passing only the
|
# manually create the dictionary from the settings, passing only the settings
|
||||||
# settings that the database backends care about. Note that TIME_ZONE is used
|
# that the database backends care about.
|
||||||
# by the PostgreSQL backends.
|
|
||||||
# We load all these up for backwards compatibility, you should use
|
# We load all these up for backwards compatibility, you should use
|
||||||
# connections['default'] instead.
|
# connections['default'] instead.
|
||||||
class DefaultConnectionProxy(object):
|
class DefaultConnectionProxy(object):
|
||||||
|
|
|
@ -4,14 +4,21 @@ from collections import deque
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
from django.db import DEFAULT_DB_ALIAS
|
from django.db import DEFAULT_DB_ALIAS
|
||||||
from django.db.backends import utils
|
from django.db.backends import utils
|
||||||
from django.db.backends.signals import connection_created
|
from django.db.backends.signals import connection_created
|
||||||
from django.db.transaction import TransactionManagementError
|
from django.db.transaction import TransactionManagementError
|
||||||
from django.db.utils import DatabaseError, DatabaseErrorWrapper
|
from django.db.utils import DatabaseError, DatabaseErrorWrapper
|
||||||
|
from django.utils import timezone
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
from django.utils.six.moves import _thread as thread
|
from django.utils.six.moves import _thread as thread
|
||||||
|
|
||||||
|
try:
|
||||||
|
import pytz
|
||||||
|
except ImportError:
|
||||||
|
pytz = None
|
||||||
|
|
||||||
NO_DB_ALIAS = '__no_db__'
|
NO_DB_ALIAS = '__no_db__'
|
||||||
|
|
||||||
|
|
||||||
|
@ -71,6 +78,39 @@ class BaseDatabaseWrapper(object):
|
||||||
self.allow_thread_sharing = allow_thread_sharing
|
self.allow_thread_sharing = allow_thread_sharing
|
||||||
self._thread_ident = thread.get_ident()
|
self._thread_ident = thread.get_ident()
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def timezone(self):
|
||||||
|
"""
|
||||||
|
Time zone for datetimes stored as naive values in the database.
|
||||||
|
|
||||||
|
Returns a tzinfo object or None.
|
||||||
|
|
||||||
|
This is only needed when time zone support is enabled and the database
|
||||||
|
doesn't support time zones. (When the database supports time zones,
|
||||||
|
the adapter handles aware datetimes so Django doesn't need to.)
|
||||||
|
"""
|
||||||
|
if not settings.USE_TZ:
|
||||||
|
return None
|
||||||
|
elif self.features.supports_timezones:
|
||||||
|
return None
|
||||||
|
elif self.settings_dict['TIME_ZONE'] is None:
|
||||||
|
return timezone.utc
|
||||||
|
else:
|
||||||
|
# Only this branch requires pytz.
|
||||||
|
return pytz.timezone(self.settings_dict['TIME_ZONE'])
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def timezone_name(self):
|
||||||
|
"""
|
||||||
|
Name of the time zone of the database connection.
|
||||||
|
"""
|
||||||
|
if not settings.USE_TZ:
|
||||||
|
return settings.TIME_ZONE
|
||||||
|
elif self.settings_dict['TIME_ZONE'] is None:
|
||||||
|
return 'UTC'
|
||||||
|
else:
|
||||||
|
return self.settings_dict['TIME_ZONE']
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def queries_logged(self):
|
def queries_logged(self):
|
||||||
return self.force_debug_cursor or settings.DEBUG
|
return self.force_debug_cursor or settings.DEBUG
|
||||||
|
@ -105,6 +145,8 @@ class BaseDatabaseWrapper(object):
|
||||||
|
|
||||||
def connect(self):
|
def connect(self):
|
||||||
"""Connects to the database. Assumes that the connection is closed."""
|
"""Connects to the database. Assumes that the connection is closed."""
|
||||||
|
# Check for invalid configurations.
|
||||||
|
self.check_settings()
|
||||||
# In case the previous connection was closed while in an atomic block
|
# In case the previous connection was closed while in an atomic block
|
||||||
self.in_atomic_block = False
|
self.in_atomic_block = False
|
||||||
self.savepoint_ids = []
|
self.savepoint_ids = []
|
||||||
|
@ -121,6 +163,21 @@ class BaseDatabaseWrapper(object):
|
||||||
self.init_connection_state()
|
self.init_connection_state()
|
||||||
connection_created.send(sender=self.__class__, connection=self)
|
connection_created.send(sender=self.__class__, connection=self)
|
||||||
|
|
||||||
|
def check_settings(self):
|
||||||
|
if self.settings_dict['TIME_ZONE'] is not None:
|
||||||
|
if not settings.USE_TZ:
|
||||||
|
raise ImproperlyConfigured(
|
||||||
|
"Connection '%s' cannot set TIME_ZONE because USE_TZ is "
|
||||||
|
"False." % 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)
|
||||||
|
elif pytz is None:
|
||||||
|
raise ImproperlyConfigured(
|
||||||
|
"Connection '%s' cannot set TIME_ZONE because pytz isn't "
|
||||||
|
"installed." % self.alias)
|
||||||
|
|
||||||
def ensure_connection(self):
|
def ensure_connection(self):
|
||||||
"""
|
"""
|
||||||
Guarantees that a connection to the database is established.
|
Guarantees that a connection to the database is established.
|
||||||
|
|
|
@ -61,6 +61,8 @@ def adapt_datetime_warn_on_aware_datetime(value, conv):
|
||||||
"probably from cursor.execute(). Update your code to pass a "
|
"probably from cursor.execute(). Update your code to pass a "
|
||||||
"naive datetime in the database connection's time zone (UTC by "
|
"naive datetime in the database connection's time zone (UTC by "
|
||||||
"default).", RemovedInDjango21Warning)
|
"default).", RemovedInDjango21Warning)
|
||||||
|
# This doesn't account for the database connection's timezone,
|
||||||
|
# which isn't known. (That's why this adapter is deprecated.)
|
||||||
value = value.astimezone(timezone.utc).replace(tzinfo=None)
|
value = value.astimezone(timezone.utc).replace(tzinfo=None)
|
||||||
return Thing2Literal(value.strftime("%Y-%m-%d %H:%M:%S.%f"), conv)
|
return Thing2Literal(value.strftime("%Y-%m-%d %H:%M:%S.%f"), conv)
|
||||||
|
|
||||||
|
|
|
@ -145,7 +145,7 @@ class DatabaseOperations(BaseDatabaseOperations):
|
||||||
# MySQL doesn't support tz-aware datetimes
|
# MySQL doesn't support tz-aware datetimes
|
||||||
if timezone.is_aware(value):
|
if timezone.is_aware(value):
|
||||||
if settings.USE_TZ:
|
if settings.USE_TZ:
|
||||||
value = value.astimezone(timezone.utc).replace(tzinfo=None)
|
value = timezone.make_naive(value, self.connection.timezone)
|
||||||
else:
|
else:
|
||||||
raise ValueError("MySQL backend does not support timezone-aware datetimes when USE_TZ is False.")
|
raise ValueError("MySQL backend does not support timezone-aware datetimes when USE_TZ is False.")
|
||||||
|
|
||||||
|
@ -205,7 +205,7 @@ class DatabaseOperations(BaseDatabaseOperations):
|
||||||
def convert_datetimefield_value(self, value, expression, connection, context):
|
def convert_datetimefield_value(self, value, expression, connection, context):
|
||||||
if value is not None:
|
if value is not None:
|
||||||
if settings.USE_TZ:
|
if settings.USE_TZ:
|
||||||
value = value.replace(tzinfo=timezone.utc)
|
value = timezone.make_aware(value, self.connection.timezone)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def convert_uuidfield_value(self, value, expression, connection, context):
|
def convert_uuidfield_value(self, value, expression, connection, context):
|
||||||
|
|
|
@ -196,7 +196,7 @@ WHEN (new.%(col_name)s IS NULL)
|
||||||
def convert_datetimefield_value(self, value, expression, connection, context):
|
def convert_datetimefield_value(self, value, expression, connection, context):
|
||||||
if value is not None:
|
if value is not None:
|
||||||
if settings.USE_TZ:
|
if settings.USE_TZ:
|
||||||
value = value.replace(tzinfo=timezone.utc)
|
value = timezone.make_aware(value, self.connection.timezone)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def convert_datefield_value(self, value, expression, connection, context):
|
def convert_datefield_value(self, value, expression, connection, context):
|
||||||
|
@ -399,7 +399,7 @@ WHEN (new.%(col_name)s IS NULL)
|
||||||
# cx_Oracle doesn't support tz-aware datetimes
|
# cx_Oracle doesn't support tz-aware datetimes
|
||||||
if timezone.is_aware(value):
|
if timezone.is_aware(value):
|
||||||
if settings.USE_TZ:
|
if settings.USE_TZ:
|
||||||
value = value.astimezone(timezone.utc).replace(tzinfo=None)
|
value = timezone.make_naive(value, self.connection.timezone)
|
||||||
else:
|
else:
|
||||||
raise ValueError("Oracle backend does not support timezone-aware datetimes when USE_TZ is False.")
|
raise ValueError("Oracle backend does not support timezone-aware datetimes when USE_TZ is False.")
|
||||||
|
|
||||||
|
|
|
@ -153,7 +153,6 @@ class DatabaseWrapper(BaseDatabaseWrapper):
|
||||||
settings_dict = self.settings_dict
|
settings_dict = self.settings_dict
|
||||||
# None may be used to connect to the default 'postgres' db
|
# None may be used to connect to the default 'postgres' db
|
||||||
if settings_dict['NAME'] == '':
|
if settings_dict['NAME'] == '':
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
|
||||||
raise ImproperlyConfigured(
|
raise ImproperlyConfigured(
|
||||||
"settings.DATABASES is improperly configured. "
|
"settings.DATABASES is improperly configured. "
|
||||||
"Please supply the NAME value.")
|
"Please supply the NAME value.")
|
||||||
|
@ -195,13 +194,12 @@ class DatabaseWrapper(BaseDatabaseWrapper):
|
||||||
def init_connection_state(self):
|
def init_connection_state(self):
|
||||||
self.connection.set_client_encoding('UTF8')
|
self.connection.set_client_encoding('UTF8')
|
||||||
|
|
||||||
tz = self.settings_dict['TIME_ZONE']
|
conn_timezone_name = self.connection.get_parameter_status('TimeZone')
|
||||||
conn_tz = self.connection.get_parameter_status('TimeZone')
|
|
||||||
|
|
||||||
if conn_tz != tz:
|
if conn_timezone_name != self.timezone_name:
|
||||||
cursor = self.connection.cursor()
|
cursor = self.connection.cursor()
|
||||||
try:
|
try:
|
||||||
cursor.execute(self.ops.set_time_zone_sql(), [tz])
|
cursor.execute(self.ops.set_time_zone_sql(), [self.timezone_name])
|
||||||
finally:
|
finally:
|
||||||
cursor.close()
|
cursor.close()
|
||||||
# Commit after setting the time zone (see #17062)
|
# Commit after setting the time zone (see #17062)
|
||||||
|
|
|
@ -58,6 +58,8 @@ def adapt_datetime_warn_on_aware_datetime(value):
|
||||||
"probably from cursor.execute(). Update your code to pass a "
|
"probably from cursor.execute(). Update your code to pass a "
|
||||||
"naive datetime in the database connection's time zone (UTC by "
|
"naive datetime in the database connection's time zone (UTC by "
|
||||||
"default).", RemovedInDjango21Warning)
|
"default).", RemovedInDjango21Warning)
|
||||||
|
# This doesn't account for the database connection's timezone,
|
||||||
|
# which isn't known. (That's why this adapter is deprecated.)
|
||||||
value = value.astimezone(timezone.utc).replace(tzinfo=None)
|
value = value.astimezone(timezone.utc).replace(tzinfo=None)
|
||||||
return value.isoformat(str(" "))
|
return value.isoformat(str(" "))
|
||||||
|
|
||||||
|
|
|
@ -120,7 +120,7 @@ class DatabaseOperations(BaseDatabaseOperations):
|
||||||
# SQLite doesn't support tz-aware datetimes
|
# SQLite doesn't support tz-aware datetimes
|
||||||
if timezone.is_aware(value):
|
if timezone.is_aware(value):
|
||||||
if settings.USE_TZ:
|
if settings.USE_TZ:
|
||||||
value = value.astimezone(timezone.utc).replace(tzinfo=None)
|
value = timezone.make_naive(value, self.connection.timezone)
|
||||||
else:
|
else:
|
||||||
raise ValueError("SQLite backend does not support timezone-aware datetimes when USE_TZ is False.")
|
raise ValueError("SQLite backend does not support timezone-aware datetimes when USE_TZ is False.")
|
||||||
|
|
||||||
|
@ -156,7 +156,7 @@ class DatabaseOperations(BaseDatabaseOperations):
|
||||||
if not isinstance(value, datetime.datetime):
|
if not isinstance(value, datetime.datetime):
|
||||||
value = parse_datetime(value)
|
value = parse_datetime(value)
|
||||||
if settings.USE_TZ:
|
if settings.USE_TZ:
|
||||||
value = value.replace(tzinfo=timezone.utc)
|
value = timezone.make_aware(value, self.connection.timezone)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def convert_datefield_value(self, value, expression, connection, context):
|
def convert_datefield_value(self, value, expression, connection, context):
|
||||||
|
|
|
@ -177,7 +177,7 @@ class ConnectionHandler(object):
|
||||||
conn['ENGINE'] = 'django.db.backends.dummy'
|
conn['ENGINE'] = 'django.db.backends.dummy'
|
||||||
conn.setdefault('CONN_MAX_AGE', 0)
|
conn.setdefault('CONN_MAX_AGE', 0)
|
||||||
conn.setdefault('OPTIONS', {})
|
conn.setdefault('OPTIONS', {})
|
||||||
conn.setdefault('TIME_ZONE', 'UTC' if settings.USE_TZ else settings.TIME_ZONE)
|
conn.setdefault('TIME_ZONE', None)
|
||||||
for setting in ['NAME', 'USER', 'PASSWORD', 'HOST', 'PORT']:
|
for setting in ['NAME', 'USER', 'PASSWORD', 'HOST', 'PORT']:
|
||||||
conn.setdefault(setting, '')
|
conn.setdefault(setting, '')
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,6 @@ import threading
|
||||||
import time
|
import time
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from django.core.signals import setting_changed
|
from django.core.signals import setting_changed
|
||||||
from django.db import connections, router
|
from django.db import connections, router
|
||||||
from django.db.utils import ConnectionRouter
|
from django.db.utils import ConnectionRouter
|
||||||
|
@ -62,19 +61,20 @@ def update_connections_time_zone(**kwargs):
|
||||||
timezone.get_default_timezone.cache_clear()
|
timezone.get_default_timezone.cache_clear()
|
||||||
|
|
||||||
# Reset the database connections' time zone
|
# Reset the database connections' time zone
|
||||||
if kwargs['setting'] == 'USE_TZ' and settings.TIME_ZONE != 'UTC':
|
if kwargs['setting'] in {'TIME_ZONE', 'USE_TZ'}:
|
||||||
USE_TZ, TIME_ZONE = kwargs['value'], settings.TIME_ZONE
|
|
||||||
elif kwargs['setting'] == 'TIME_ZONE' and not settings.USE_TZ:
|
|
||||||
USE_TZ, TIME_ZONE = settings.USE_TZ, kwargs['value']
|
|
||||||
else:
|
|
||||||
# no need to change the database connnections' time zones
|
|
||||||
return
|
|
||||||
tz = 'UTC' if USE_TZ else TIME_ZONE
|
|
||||||
for conn in connections.all():
|
for conn in connections.all():
|
||||||
conn.settings_dict['TIME_ZONE'] = tz
|
try:
|
||||||
|
del conn.timezone
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
del conn.timezone_name
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
tz_sql = conn.ops.set_time_zone_sql()
|
tz_sql = conn.ops.set_time_zone_sql()
|
||||||
if tz_sql:
|
if tz_sql:
|
||||||
conn.cursor().execute(tz_sql, [tz])
|
with conn.cursor() as cursor:
|
||||||
|
cursor.execute(tz_sql, [conn.timezone_name])
|
||||||
|
|
||||||
|
|
||||||
@receiver(setting_changed)
|
@receiver(setting_changed)
|
||||||
|
|
|
@ -589,6 +589,41 @@ Default: ``''`` (Empty string)
|
||||||
The port to use when connecting to the database. An empty string means the
|
The port to use when connecting to the database. An empty string means the
|
||||||
default port. Not used with SQLite.
|
default port. Not used with SQLite.
|
||||||
|
|
||||||
|
.. setting:: DATABASE-TIME_ZONE
|
||||||
|
|
||||||
|
TIME_ZONE
|
||||||
|
~~~~~~~~~
|
||||||
|
|
||||||
|
.. versionadded:: 1.9
|
||||||
|
|
||||||
|
Default: ``None``
|
||||||
|
|
||||||
|
A string representing the time zone for datetimes stored in this database
|
||||||
|
(assuming that it doesn't support time zones) or ``None``. The same values are
|
||||||
|
accepted as in the general :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.
|
||||||
|
|
||||||
|
Setting this option requires installing pytz_.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
.. versionchanged:: 1.9
|
||||||
|
|
||||||
|
Before Django 1.9, the PostgreSQL database backend accepted an
|
||||||
|
undocumented ``TIME_ZONE`` option, which caused data corruption.
|
||||||
|
|
||||||
|
When :setting:`USE_TZ` is ``False``, it is an error to set this option.
|
||||||
|
|
||||||
|
.. _pytz: http://pytz.sourceforge.net/
|
||||||
|
|
||||||
.. setting:: USER
|
.. setting:: USER
|
||||||
|
|
||||||
USER
|
USER
|
||||||
|
@ -2472,8 +2507,6 @@ to ensure your processes are running in the correct environment.
|
||||||
|
|
||||||
.. _list of time zones: http://en.wikipedia.org/wiki/List_of_tz_database_time_zones
|
.. _list of time zones: http://en.wikipedia.org/wiki/List_of_tz_database_time_zones
|
||||||
|
|
||||||
.. _pytz: http://pytz.sourceforge.net/
|
|
||||||
|
|
||||||
.. setting:: USE_ETAGS
|
.. setting:: USE_ETAGS
|
||||||
|
|
||||||
USE_ETAGS
|
USE_ETAGS
|
||||||
|
|
|
@ -201,6 +201,10 @@ Management Commands
|
||||||
Models
|
Models
|
||||||
^^^^^^
|
^^^^^^
|
||||||
|
|
||||||
|
* Database configuration gained a :setting:`TIME_ZONE <DATABASE-TIME_ZONE>`
|
||||||
|
option for interacting with databases that store datetimes in local time and
|
||||||
|
don't support time zones when :setting:`USE_TZ` is ``True``.
|
||||||
|
|
||||||
* Added the :meth:`RelatedManager.set()
|
* Added the :meth:`RelatedManager.set()
|
||||||
<django.db.models.fields.related.RelatedManager.set()>` method to the related
|
<django.db.models.fields.related.RelatedManager.set()>` method to the related
|
||||||
managers created by ``ForeignKey``, ``GenericForeignKey``, and
|
managers created by ``ForeignKey``, ``GenericForeignKey``, and
|
||||||
|
|
|
@ -9,16 +9,15 @@ Time zones
|
||||||
Overview
|
Overview
|
||||||
========
|
========
|
||||||
|
|
||||||
When support for time zones is enabled, Django stores datetime
|
When support for time zones is enabled, Django stores datetime information in
|
||||||
information in UTC in the database, uses time-zone-aware datetime objects
|
UTC in the database, uses time-zone-aware datetime objects internally, and
|
||||||
internally, and translates them to the end user's time zone in templates and
|
translates them to the end user's time zone in templates and forms.
|
||||||
forms.
|
|
||||||
|
|
||||||
This is handy if your users live in more than one time zone and you want to
|
This is handy if your users live in more than one time zone and you want to
|
||||||
display datetime information according to each user's wall clock.
|
display datetime information according to each user's wall clock.
|
||||||
|
|
||||||
Even if your Web site is available in only one time zone, it's still good
|
Even if your Web site is available in only one time zone, it's still good
|
||||||
practice to store data in UTC in your database. One main reason is Daylight
|
practice to store data in UTC in your database. The main reason is Daylight
|
||||||
Saving Time (DST). Many countries have a system of DST, where clocks are moved
|
Saving Time (DST). Many countries have a system of DST, where clocks are moved
|
||||||
forward in spring and backward in autumn. If you're working in local time,
|
forward in spring and backward in autumn. If you're working in local time,
|
||||||
you're likely to encounter errors twice a year, when the transitions happen.
|
you're likely to encounter errors twice a year, when the transitions happen.
|
||||||
|
@ -537,6 +536,14 @@ Setup
|
||||||
Furthermore, if you want to support users in more than one time zone, pytz
|
Furthermore, if you want to support users in more than one time zone, pytz
|
||||||
is the reference for time zone definitions.
|
is the reference for time zone definitions.
|
||||||
|
|
||||||
|
4. **How do I interact with a database that stores datetimes in local time?**
|
||||||
|
|
||||||
|
Set the :setting:`TIME_ZONE <DATABASE-TIME_ZONE>` option to the appropriate
|
||||||
|
time zone for this database in the :setting:`DATABASES` setting.
|
||||||
|
|
||||||
|
This is useful for connecting to a database that doesn't support time zones
|
||||||
|
and that isn't managed by Django when :setting:`USE_TZ` is ``True``.
|
||||||
|
|
||||||
Troubleshooting
|
Troubleshooting
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
|
|
|
@ -222,6 +222,7 @@ class PostgreSQLTests(TestCase):
|
||||||
databases = copy.deepcopy(settings.DATABASES)
|
databases = copy.deepcopy(settings.DATABASES)
|
||||||
new_connections = ConnectionHandler(databases)
|
new_connections = ConnectionHandler(databases)
|
||||||
new_connection = new_connections[DEFAULT_DB_ALIAS]
|
new_connection = new_connections[DEFAULT_DB_ALIAS]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Ensure the database default time zone is different than
|
# Ensure the database default time zone is different than
|
||||||
# the time zone in new_connection.settings_dict. We can
|
# the time zone in new_connection.settings_dict. We can
|
||||||
|
@ -233,9 +234,13 @@ class PostgreSQLTests(TestCase):
|
||||||
new_tz = 'Europe/Paris' if db_default_tz == 'UTC' else 'UTC'
|
new_tz = 'Europe/Paris' if db_default_tz == 'UTC' else 'UTC'
|
||||||
new_connection.close()
|
new_connection.close()
|
||||||
|
|
||||||
|
# Invalidate timezone name cache, because the setting_changed
|
||||||
|
# handler cannot know about new_connection.
|
||||||
|
del new_connection.timezone_name
|
||||||
|
|
||||||
# Fetch a new connection with the new_tz as default
|
# Fetch a new connection with the new_tz as default
|
||||||
# time zone, run a query and rollback.
|
# time zone, run a query and rollback.
|
||||||
new_connection.settings_dict['TIME_ZONE'] = new_tz
|
with self.settings(TIME_ZONE=new_tz):
|
||||||
new_connection.set_autocommit(False)
|
new_connection.set_autocommit(False)
|
||||||
cursor = new_connection.cursor()
|
cursor = new_connection.cursor()
|
||||||
new_connection.rollback()
|
new_connection.rollback()
|
||||||
|
@ -244,6 +249,7 @@ class PostgreSQLTests(TestCase):
|
||||||
cursor.execute("SHOW TIMEZONE")
|
cursor.execute("SHOW TIMEZONE")
|
||||||
tz = cursor.fetchone()[0]
|
tz = cursor.fetchone()[0]
|
||||||
self.assertEqual(new_tz, tz)
|
self.assertEqual(new_tz, tz)
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
new_connection.close()
|
new_connection.close()
|
||||||
|
|
||||||
|
|
|
@ -9,15 +9,17 @@ from xml.dom.minidom import parseString
|
||||||
|
|
||||||
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.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.db import connection
|
from django.db import connection, connections
|
||||||
from django.db.models import Max, Min
|
from django.db.models import Max, Min
|
||||||
from django.http import HttpRequest
|
from django.http import HttpRequest
|
||||||
from django.template import (
|
from django.template import (
|
||||||
Context, RequestContext, Template, TemplateSyntaxError, context_processors,
|
Context, RequestContext, Template, TemplateSyntaxError, context_processors,
|
||||||
)
|
)
|
||||||
from django.test import (
|
from django.test import (
|
||||||
TestCase, override_settings, skipIfDBFeature, skipUnlessDBFeature,
|
TestCase, TransactionTestCase, override_settings, skipIfDBFeature,
|
||||||
|
skipUnlessDBFeature,
|
||||||
)
|
)
|
||||||
from django.test.utils import requires_tz_support
|
from django.test.utils import requires_tz_support
|
||||||
from django.utils import six, timezone
|
from django.utils import six, timezone
|
||||||
|
@ -620,6 +622,67 @@ class NewDatabaseTests(TestCase):
|
||||||
self.assertEqual(e.dt, None)
|
self.assertEqual(e.dt, None)
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: chaining @skipIfDBFeature and @skipUnlessDBFeature doesn't work!
|
||||||
|
@skipIfDBFeature('supports_timezones')
|
||||||
|
@skipUnlessDBFeature('test_db_allows_multiple_connections')
|
||||||
|
@override_settings(TIME_ZONE='Africa/Nairobi', USE_TZ=True)
|
||||||
|
class ForcedTimeZoneDatabaseTests(TransactionTestCase):
|
||||||
|
"""
|
||||||
|
Test the TIME_ZONE database configuration parameter.
|
||||||
|
|
||||||
|
Since this involves reading and writing to the same database through two
|
||||||
|
connections, this is a TransactionTestCase.
|
||||||
|
"""
|
||||||
|
|
||||||
|
available_apps = ['timezones']
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
super(ForcedTimeZoneDatabaseTests, cls).setUpClass()
|
||||||
|
connections.databases['tz'] = connections.databases['default'].copy()
|
||||||
|
connections.databases['tz']['TIME_ZONE'] = 'Asia/Bangkok'
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tearDownClass(cls):
|
||||||
|
connections['tz'].close()
|
||||||
|
del connections['tz']
|
||||||
|
del connections.databases['tz']
|
||||||
|
super(ForcedTimeZoneDatabaseTests, cls).tearDownClass()
|
||||||
|
|
||||||
|
def test_read_datetime(self):
|
||||||
|
fake_dt = datetime.datetime(2011, 9, 1, 17, 20, 30, tzinfo=UTC)
|
||||||
|
Event.objects.create(dt=fake_dt)
|
||||||
|
|
||||||
|
event = Event.objects.using('tz').get()
|
||||||
|
dt = datetime.datetime(2011, 9, 1, 10, 20, 30, tzinfo=UTC)
|
||||||
|
self.assertEqual(event.dt, dt)
|
||||||
|
|
||||||
|
def test_write_datetime(self):
|
||||||
|
dt = datetime.datetime(2011, 9, 1, 10, 20, 30, tzinfo=UTC)
|
||||||
|
Event.objects.using('tz').create(dt=dt)
|
||||||
|
|
||||||
|
event = Event.objects.get()
|
||||||
|
fake_dt = datetime.datetime(2011, 9, 1, 17, 20, 30, tzinfo=UTC)
|
||||||
|
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:
|
||||||
|
with self.assertRaises(ImproperlyConfigured):
|
||||||
|
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(TestCase):
|
class SerializationTests(TestCase):
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue