parent
eda12ceef1
commit
ec186572e6
|
@ -74,6 +74,8 @@ class DatabaseCache(BaseDatabaseCache):
|
|||
# All core backends work without typecasting, so be careful about
|
||||
# changes here - test suite will NOT pick regressions here.
|
||||
expires = typecast_timestamp(str(expires))
|
||||
if settings.USE_TZ:
|
||||
expires = expires.replace(tzinfo=timezone.utc)
|
||||
if expires < now:
|
||||
db = router.db_for_write(self.cache_model_class)
|
||||
with connections[db].cursor() as cursor:
|
||||
|
@ -132,6 +134,8 @@ class DatabaseCache(BaseDatabaseCache):
|
|||
if (connections[db].features.needs_datetime_string_cast and not
|
||||
isinstance(current_expires, datetime)):
|
||||
current_expires = typecast_timestamp(str(current_expires))
|
||||
if settings.USE_TZ:
|
||||
current_expires = current_expires.replace(tzinfo=timezone.utc)
|
||||
exp = connections[db].ops.value_to_db_datetime(exp)
|
||||
if result and (mode == 'set' or (mode == 'add' and current_expires < now)):
|
||||
cursor.execute("UPDATE %s SET value = %%s, expires = %%s "
|
||||
|
|
|
@ -51,17 +51,6 @@ if (version < (1, 2, 1) or (version[:3] == (1, 2, 1) and
|
|||
DatabaseError = Database.DatabaseError
|
||||
IntegrityError = Database.IntegrityError
|
||||
|
||||
# It's impossible to import datetime_or_None directly from MySQLdb.times
|
||||
parse_datetime = conversions[FIELD_TYPE.DATETIME]
|
||||
|
||||
|
||||
def parse_datetime_with_timezone_support(value):
|
||||
dt = parse_datetime(value)
|
||||
# Confirm that dt is naive before overwriting its tzinfo.
|
||||
if dt is not None and settings.USE_TZ and timezone.is_naive(dt):
|
||||
dt = dt.replace(tzinfo=timezone.utc)
|
||||
return dt
|
||||
|
||||
|
||||
def adapt_datetime_with_timezone_support(value, conv):
|
||||
# Equivalent to DateTimeField.get_db_prep_value. Used only by raw SQL.
|
||||
|
@ -80,14 +69,11 @@ def adapt_datetime_with_timezone_support(value, conv):
|
|||
# and Django expects time, so we still need to override that. We also need to
|
||||
# add special handling for SafeText and SafeBytes as MySQLdb's type
|
||||
# checking is too tight to catch those (see Django ticket #6052).
|
||||
# Finally, MySQLdb always returns naive datetime objects. However, when
|
||||
# timezone support is active, Django expects timezone-aware datetime objects.
|
||||
django_conversions = conversions.copy()
|
||||
django_conversions.update({
|
||||
FIELD_TYPE.TIME: backend_utils.typecast_time,
|
||||
FIELD_TYPE.DECIMAL: backend_utils.typecast_decimal,
|
||||
FIELD_TYPE.NEWDECIMAL: backend_utils.typecast_decimal,
|
||||
FIELD_TYPE.DATETIME: parse_datetime_with_timezone_support,
|
||||
datetime.datetime: adapt_datetime_with_timezone_support,
|
||||
})
|
||||
|
||||
|
|
|
@ -184,6 +184,8 @@ class DatabaseOperations(BaseDatabaseOperations):
|
|||
internal_type = expression.output_field.get_internal_type()
|
||||
if internal_type in ['BooleanField', 'NullBooleanField']:
|
||||
converters.append(self.convert_booleanfield_value)
|
||||
if internal_type == 'DateTimeField':
|
||||
converters.append(self.convert_datetimefield_value)
|
||||
if internal_type == 'UUIDField':
|
||||
converters.append(self.convert_uuidfield_value)
|
||||
if internal_type == 'TextField':
|
||||
|
@ -195,6 +197,12 @@ class DatabaseOperations(BaseDatabaseOperations):
|
|||
value = bool(value)
|
||||
return value
|
||||
|
||||
def convert_datetimefield_value(self, value, expression, connection, context):
|
||||
if value is not None:
|
||||
if settings.USE_TZ:
|
||||
value = value.replace(tzinfo=timezone.utc)
|
||||
return value
|
||||
|
||||
def convert_uuidfield_value(self, value, expression, connection, context):
|
||||
if value is not None:
|
||||
value = uuid.UUID(value)
|
||||
|
|
|
@ -588,12 +588,6 @@ def _rowfactory(row, cursor):
|
|||
value = decimal.Decimal(value)
|
||||
else:
|
||||
value = int(value)
|
||||
# datetimes are returned as TIMESTAMP, except the results
|
||||
# of "dates" queries, which are returned as DATETIME.
|
||||
elif desc[1] in (Database.TIMESTAMP, Database.DATETIME):
|
||||
# Confirm that dt is naive before overwriting its tzinfo.
|
||||
if settings.USE_TZ and value is not None and timezone.is_naive(value):
|
||||
value = value.replace(tzinfo=timezone.utc)
|
||||
elif desc[1] in (Database.STRING, Database.FIXED_CHAR,
|
||||
Database.LONG_STRING):
|
||||
value = to_unicode(value)
|
||||
|
|
|
@ -163,6 +163,8 @@ WHEN (new.%(col_name)s IS NULL)
|
|||
converters.append(self.convert_binaryfield_value)
|
||||
elif internal_type in ['BooleanField', 'NullBooleanField']:
|
||||
converters.append(self.convert_booleanfield_value)
|
||||
elif internal_type == 'DateTimeField':
|
||||
converters.append(self.convert_datetimefield_value)
|
||||
elif internal_type == 'DateField':
|
||||
converters.append(self.convert_datefield_value)
|
||||
elif internal_type == 'TimeField':
|
||||
|
@ -202,6 +204,13 @@ WHEN (new.%(col_name)s IS NULL)
|
|||
# cx_Oracle always returns datetime.datetime objects for
|
||||
# DATE and TIMESTAMP columns, but Django wants to see a
|
||||
# python datetime.date, .time, or .datetime.
|
||||
|
||||
def convert_datetimefield_value(self, value, expression, connection, context):
|
||||
if value is not None:
|
||||
if settings.USE_TZ:
|
||||
value = value.replace(tzinfo=timezone.utc)
|
||||
return value
|
||||
|
||||
def convert_datefield_value(self, value, expression, connection, context):
|
||||
if isinstance(value, Database.Timestamp):
|
||||
return value.date()
|
||||
|
|
|
@ -17,7 +17,9 @@ from django.db.backends import utils as backend_utils
|
|||
from django.db.backends.base.base import BaseDatabaseWrapper
|
||||
from django.db.backends.base.validation import BaseDatabaseValidation
|
||||
from django.utils import six, timezone
|
||||
from django.utils.dateparse import parse_date, parse_duration, parse_time
|
||||
from django.utils.dateparse import (
|
||||
parse_date, parse_datetime, parse_duration, parse_time,
|
||||
)
|
||||
from django.utils.encoding import force_text
|
||||
from django.utils.safestring import SafeBytes
|
||||
|
||||
|
@ -42,7 +44,6 @@ from .features import DatabaseFeatures # isort:skip
|
|||
from .introspection import DatabaseIntrospection # isort:skip
|
||||
from .operations import DatabaseOperations # isort:skip
|
||||
from .schema import DatabaseSchemaEditor # isort:skip
|
||||
from .utils import parse_datetime_with_timezone_support # isort:skip
|
||||
|
||||
DatabaseError = Database.DatabaseError
|
||||
IntegrityError = Database.IntegrityError
|
||||
|
@ -71,9 +72,9 @@ def decoder(conv_func):
|
|||
Database.register_converter(str("bool"), decoder(lambda s: s == '1'))
|
||||
Database.register_converter(str("time"), decoder(parse_time))
|
||||
Database.register_converter(str("date"), decoder(parse_date))
|
||||
Database.register_converter(str("datetime"), decoder(parse_datetime_with_timezone_support))
|
||||
Database.register_converter(str("timestamp"), decoder(parse_datetime_with_timezone_support))
|
||||
Database.register_converter(str("TIMESTAMP"), decoder(parse_datetime_with_timezone_support))
|
||||
Database.register_converter(str("datetime"), decoder(parse_datetime))
|
||||
Database.register_converter(str("timestamp"), decoder(parse_datetime))
|
||||
Database.register_converter(str("TIMESTAMP"), decoder(parse_datetime))
|
||||
Database.register_converter(str("decimal"), decoder(backend_utils.typecast_decimal))
|
||||
|
||||
Database.register_adapter(datetime.datetime, adapt_datetime_with_timezone_support)
|
||||
|
|
|
@ -10,11 +10,9 @@ from django.db.backends import utils as backend_utils
|
|||
from django.db.backends.base.operations import BaseDatabaseOperations
|
||||
from django.db.models import aggregates, fields
|
||||
from django.utils import six, timezone
|
||||
from django.utils.dateparse import parse_date, parse_time
|
||||
from django.utils.dateparse import parse_date, parse_datetime, parse_time
|
||||
from django.utils.duration import duration_string
|
||||
|
||||
from .utils import parse_datetime_with_timezone_support
|
||||
|
||||
try:
|
||||
import pytz
|
||||
except ImportError:
|
||||
|
@ -157,18 +155,23 @@ class DatabaseOperations(BaseDatabaseOperations):
|
|||
return backend_utils.typecast_decimal(expression.output_field.format_number(value))
|
||||
|
||||
def convert_datefield_value(self, value, expression, connection, context):
|
||||
if value is not None and not isinstance(value, datetime.date):
|
||||
value = parse_date(value)
|
||||
if value is not None:
|
||||
if not isinstance(value, datetime.date):
|
||||
value = parse_date(value)
|
||||
return value
|
||||
|
||||
def convert_datetimefield_value(self, value, expression, connection, context):
|
||||
if value is not None and not isinstance(value, datetime.datetime):
|
||||
value = parse_datetime_with_timezone_support(value)
|
||||
if value is not None:
|
||||
if not isinstance(value, datetime.datetime):
|
||||
value = parse_datetime(value)
|
||||
if settings.USE_TZ:
|
||||
value = value.replace(tzinfo=timezone.utc)
|
||||
return value
|
||||
|
||||
def convert_timefield_value(self, value, expression, connection, context):
|
||||
if value is not None and not isinstance(value, datetime.time):
|
||||
value = parse_time(value)
|
||||
if value is not None:
|
||||
if not isinstance(value, datetime.time):
|
||||
value = parse_time(value)
|
||||
return value
|
||||
|
||||
def convert_uuidfield_value(self, value, expression, connection, context):
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
from django.conf import settings
|
||||
from django.utils import timezone
|
||||
from django.utils.dateparse import parse_datetime
|
||||
|
||||
|
||||
def parse_datetime_with_timezone_support(value):
|
||||
dt = parse_datetime(value)
|
||||
# Confirm that dt is naive before overwriting its tzinfo.
|
||||
if dt is not None and settings.USE_TZ and timezone.is_naive(dt):
|
||||
dt = dt.replace(tzinfo=timezone.utc)
|
||||
return dt
|
|
@ -313,6 +313,12 @@ Database backend API
|
|||
doesn't implement this. You may want to review the implementation on the
|
||||
backends that Django includes for reference (:ticket:`24245`).
|
||||
|
||||
* The recommended way to add time zone information to datetimes fetched from
|
||||
databases that don't support time zones is to register a converter for
|
||||
``DateTimeField``. Do this in ``DatabaseOperations.get_db_converters()``.
|
||||
Registering a global converter at the level of the DB-API module is
|
||||
discouraged because it can conflict with other libraries.
|
||||
|
||||
Default settings that were tuples are now lists
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
@ -412,6 +418,21 @@ console, for example.
|
|||
If you are overriding Django's default logging, you should check to see how
|
||||
your configuration merges with the new defaults.
|
||||
|
||||
Removal of time zone aware global converters for datetimes
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Django no longer registers global converters for returning time zone aware
|
||||
datetimes in database query results when :setting:`USE_TZ` is ``True``.
|
||||
Instead the ORM adds suitable time zone information.
|
||||
|
||||
As a consequence, SQL queries executed outside of the ORM, for instance with
|
||||
``cursor.execute(query, params)``, now return naive datetimes instead of aware
|
||||
datetimes on databases that do not support time zones: SQLite, MySQL, and
|
||||
Oracle. Since these datetimes are in UTC, you can make them aware as follows::
|
||||
|
||||
from django.utils import timezone
|
||||
value = value.replace(tzinfo=timezone.utc)
|
||||
|
||||
Miscellaneous
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ from xml.dom.minidom import parseString
|
|||
from django.contrib.auth.models import User
|
||||
from django.core import serializers
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.db import connection
|
||||
from django.db.models import Max, Min
|
||||
from django.http import HttpRequest
|
||||
from django.template import (
|
||||
|
@ -265,6 +266,13 @@ class LegacyDatabaseTests(TestCase):
|
|||
[event],
|
||||
transform=lambda d: d)
|
||||
|
||||
def test_cursor_execute_returns_naive_datetime(self):
|
||||
dt = datetime.datetime(2011, 9, 1, 13, 20, 30)
|
||||
Event.objects.create(dt=dt)
|
||||
with connection.cursor() as cursor:
|
||||
cursor.execute('SELECT dt FROM timezones_event WHERE dt = %s', [dt])
|
||||
self.assertEqual(cursor.fetchall()[0][0], dt)
|
||||
|
||||
def test_filter_date_field_with_aware_datetime(self):
|
||||
# Regression test for #17742
|
||||
day = datetime.date(2011, 9, 1)
|
||||
|
@ -556,6 +564,23 @@ class NewDatabaseTests(TestCase):
|
|||
[event],
|
||||
transform=lambda d: d)
|
||||
|
||||
@skipUnlessDBFeature('supports_timezones')
|
||||
def test_cursor_execute_returns_aware_datetime(self):
|
||||
dt = datetime.datetime(2011, 9, 1, 13, 20, 30, tzinfo=EAT)
|
||||
Event.objects.create(dt=dt)
|
||||
with connection.cursor() as cursor:
|
||||
cursor.execute('SELECT dt FROM timezones_event WHERE dt = %s', [dt])
|
||||
self.assertEqual(cursor.fetchall()[0][0], dt)
|
||||
|
||||
@skipIfDBFeature('supports_timezones')
|
||||
def test_cursor_execute_returns_naive_datetime(self):
|
||||
dt = datetime.datetime(2011, 9, 1, 13, 20, 30, tzinfo=EAT)
|
||||
utc_naive_dt = timezone.make_naive(dt, timezone.utc)
|
||||
Event.objects.create(dt=dt)
|
||||
with connection.cursor() as cursor:
|
||||
cursor.execute('SELECT dt FROM timezones_event WHERE dt = %s', [dt])
|
||||
self.assertEqual(cursor.fetchall()[0][0], utc_naive_dt)
|
||||
|
||||
@requires_tz_support
|
||||
def test_filter_date_field_with_aware_datetime(self):
|
||||
# Regression test for #17742
|
||||
|
|
Loading…
Reference in New Issue