mirror of
				https://github.com/django/django.git
				synced 2025-10-31 09:41:08 +00:00 
			
		
		
		
	Fixed #23524 -- Allowed DATABASES['TIME_ZONE'] option on PostgreSQL.
This commit is contained in:
		
				
					committed by
					
						 Mariusz Felisiak
						Mariusz Felisiak
					
				
			
			
				
	
			
			
			
						parent
						
							ad88524e4d
						
					
				
				
					commit
					c06492dd87
				
			| @@ -124,11 +124,11 @@ class BaseDatabaseWrapper: | ||||
|  | ||||
|         When the database backend supports time zones, it doesn't matter which | ||||
|         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 | ||||
|         Django uses can be selected with the TIME_ZONE configuration option, so | ||||
|         it can match what other users of the database expect. | ||||
|         Django uses may be constrained by the requirements of other users of | ||||
|         the database. | ||||
|         """ | ||||
|         if not settings.USE_TZ: | ||||
|             return None | ||||
| @@ -205,15 +205,11 @@ class BaseDatabaseWrapper: | ||||
|         self.run_on_commit = [] | ||||
|  | ||||
|     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) | ||||
|         if self.settings_dict['TIME_ZONE'] is not None and not settings.USE_TZ: | ||||
|             raise ImproperlyConfigured( | ||||
|                 "Connection '%s' cannot set TIME_ZONE because USE_TZ is False." | ||||
|                 % self.alias | ||||
|             ) | ||||
|  | ||||
|     @async_unsafe | ||||
|     def ensure_connection(self): | ||||
|   | ||||
| @@ -47,7 +47,6 @@ from .features import DatabaseFeatures                      # NOQA isort:skip | ||||
| from .introspection import DatabaseIntrospection            # NOQA isort:skip | ||||
| from .operations import DatabaseOperations                  # 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.extras.register_uuid() | ||||
| @@ -231,9 +230,12 @@ class DatabaseWrapper(BaseDatabaseWrapper): | ||||
|             cursor = self.connection.cursor(name, scrollable=False, withhold=self.connection.autocommit) | ||||
|         else: | ||||
|             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 | ||||
|  | ||||
|     def tzinfo_factory(self, offset): | ||||
|         return self.timezone | ||||
|  | ||||
|     @async_unsafe | ||||
|     def chunked_cursor(self): | ||||
|         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'``, | ||||
| - ``default_transaction_isolation``: ``'read committed'`` by default, | ||||
|   or the value set in the connection options (see below), | ||||
| - ``timezone``: ``'UTC'`` when :setting:`USE_TZ` is ``True``, value of | ||||
|   :setting:`TIME_ZONE` otherwise. | ||||
| - ``timezone``: | ||||
|     - 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 | ||||
| every new connection, which improves performance slightly. You can configure | ||||
|   | ||||
| @@ -632,24 +632,53 @@ default port. Not used with SQLite. | ||||
|  | ||||
| Default: ``None`` | ||||
|  | ||||
| A string representing the time zone for datetimes stored in this database | ||||
| (assuming that it doesn't support time zones) or ``None``. This inner option of | ||||
| the :setting:`DATABASES` setting accepts the same values as 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. | ||||
|  | ||||
| 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. | ||||
| A string representing the time zone for this database connection or ``None``. | ||||
| This inner option of the :setting:`DATABASES` setting accepts the same values | ||||
| as the general :setting:`TIME_ZONE` setting. | ||||
|  | ||||
| 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. | ||||
|  | ||||
| * 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 | ||||
|  | ||||
| ``DISABLE_SERVER_SIDE_CURSORS`` | ||||
|   | ||||
| @@ -283,6 +283,9 @@ Miscellaneous | ||||
|   :class:`pathlib.Path` instead of :mod:`os.path` for building filesystem | ||||
|   paths. | ||||
|  | ||||
| * The :setting:`TIME_ZONE <DATABASE-TIME_ZONE>` setting is now allowed on | ||||
|   databases that support time zones. | ||||
|  | ||||
| .. _backwards-incompatible-3.1: | ||||
|  | ||||
| Backwards incompatible changes in 3.1 | ||||
|   | ||||
| @@ -9,8 +9,7 @@ import pytz | ||||
|  | ||||
| from django.contrib.auth.models import User | ||||
| from django.core import serializers | ||||
| from django.core.exceptions import ImproperlyConfigured | ||||
| from django.db import connection, connections | ||||
| from django.db import connection | ||||
| from django.db.models import F, Max, Min | ||||
| from django.http import HttpRequest | ||||
| from django.template import ( | ||||
| @@ -532,6 +531,14 @@ class NewDatabaseTests(TestCase): | ||||
|             cursor.execute('SELECT dt FROM timezones_event WHERE dt = %s', [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 | ||||
|     def test_filter_date_field_with_aware_datetime(self): | ||||
|         # Regression test for #17742 | ||||
| @@ -595,27 +602,6 @@ class ForcedTimeZoneDatabaseTests(TransactionTestCase): | ||||
|         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') | ||||
| class SerializationTests(SimpleTestCase): | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user