parent
eda12ceef1
commit
ec186572e6
|
@ -74,6 +74,8 @@ class DatabaseCache(BaseDatabaseCache):
|
||||||
# All core backends work without typecasting, so be careful about
|
# All core backends work without typecasting, so be careful about
|
||||||
# changes here - test suite will NOT pick regressions here.
|
# changes here - test suite will NOT pick regressions here.
|
||||||
expires = typecast_timestamp(str(expires))
|
expires = typecast_timestamp(str(expires))
|
||||||
|
if settings.USE_TZ:
|
||||||
|
expires = expires.replace(tzinfo=timezone.utc)
|
||||||
if expires < now:
|
if expires < now:
|
||||||
db = router.db_for_write(self.cache_model_class)
|
db = router.db_for_write(self.cache_model_class)
|
||||||
with connections[db].cursor() as cursor:
|
with connections[db].cursor() as cursor:
|
||||||
|
@ -132,6 +134,8 @@ class DatabaseCache(BaseDatabaseCache):
|
||||||
if (connections[db].features.needs_datetime_string_cast and not
|
if (connections[db].features.needs_datetime_string_cast and not
|
||||||
isinstance(current_expires, datetime)):
|
isinstance(current_expires, datetime)):
|
||||||
current_expires = typecast_timestamp(str(current_expires))
|
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)
|
exp = connections[db].ops.value_to_db_datetime(exp)
|
||||||
if result and (mode == 'set' or (mode == 'add' and current_expires < now)):
|
if result and (mode == 'set' or (mode == 'add' and current_expires < now)):
|
||||||
cursor.execute("UPDATE %s SET value = %%s, expires = %%s "
|
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
|
DatabaseError = Database.DatabaseError
|
||||||
IntegrityError = Database.IntegrityError
|
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):
|
def adapt_datetime_with_timezone_support(value, conv):
|
||||||
# Equivalent to DateTimeField.get_db_prep_value. Used only by raw SQL.
|
# 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
|
# 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
|
# add special handling for SafeText and SafeBytes as MySQLdb's type
|
||||||
# checking is too tight to catch those (see Django ticket #6052).
|
# 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 = conversions.copy()
|
||||||
django_conversions.update({
|
django_conversions.update({
|
||||||
FIELD_TYPE.TIME: backend_utils.typecast_time,
|
FIELD_TYPE.TIME: backend_utils.typecast_time,
|
||||||
FIELD_TYPE.DECIMAL: backend_utils.typecast_decimal,
|
FIELD_TYPE.DECIMAL: backend_utils.typecast_decimal,
|
||||||
FIELD_TYPE.NEWDECIMAL: 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,
|
datetime.datetime: adapt_datetime_with_timezone_support,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -184,6 +184,8 @@ class DatabaseOperations(BaseDatabaseOperations):
|
||||||
internal_type = expression.output_field.get_internal_type()
|
internal_type = expression.output_field.get_internal_type()
|
||||||
if internal_type in ['BooleanField', 'NullBooleanField']:
|
if internal_type in ['BooleanField', 'NullBooleanField']:
|
||||||
converters.append(self.convert_booleanfield_value)
|
converters.append(self.convert_booleanfield_value)
|
||||||
|
if internal_type == 'DateTimeField':
|
||||||
|
converters.append(self.convert_datetimefield_value)
|
||||||
if internal_type == 'UUIDField':
|
if internal_type == 'UUIDField':
|
||||||
converters.append(self.convert_uuidfield_value)
|
converters.append(self.convert_uuidfield_value)
|
||||||
if internal_type == 'TextField':
|
if internal_type == 'TextField':
|
||||||
|
@ -195,6 +197,12 @@ class DatabaseOperations(BaseDatabaseOperations):
|
||||||
value = bool(value)
|
value = bool(value)
|
||||||
return 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):
|
def convert_uuidfield_value(self, value, expression, connection, context):
|
||||||
if value is not None:
|
if value is not None:
|
||||||
value = uuid.UUID(value)
|
value = uuid.UUID(value)
|
||||||
|
|
|
@ -588,12 +588,6 @@ def _rowfactory(row, cursor):
|
||||||
value = decimal.Decimal(value)
|
value = decimal.Decimal(value)
|
||||||
else:
|
else:
|
||||||
value = int(value)
|
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,
|
elif desc[1] in (Database.STRING, Database.FIXED_CHAR,
|
||||||
Database.LONG_STRING):
|
Database.LONG_STRING):
|
||||||
value = to_unicode(value)
|
value = to_unicode(value)
|
||||||
|
|
|
@ -163,6 +163,8 @@ WHEN (new.%(col_name)s IS NULL)
|
||||||
converters.append(self.convert_binaryfield_value)
|
converters.append(self.convert_binaryfield_value)
|
||||||
elif internal_type in ['BooleanField', 'NullBooleanField']:
|
elif internal_type in ['BooleanField', 'NullBooleanField']:
|
||||||
converters.append(self.convert_booleanfield_value)
|
converters.append(self.convert_booleanfield_value)
|
||||||
|
elif internal_type == 'DateTimeField':
|
||||||
|
converters.append(self.convert_datetimefield_value)
|
||||||
elif internal_type == 'DateField':
|
elif internal_type == 'DateField':
|
||||||
converters.append(self.convert_datefield_value)
|
converters.append(self.convert_datefield_value)
|
||||||
elif internal_type == 'TimeField':
|
elif internal_type == 'TimeField':
|
||||||
|
@ -202,6 +204,13 @@ WHEN (new.%(col_name)s IS NULL)
|
||||||
# cx_Oracle always returns datetime.datetime objects for
|
# cx_Oracle always returns datetime.datetime objects for
|
||||||
# DATE and TIMESTAMP columns, but Django wants to see a
|
# DATE and TIMESTAMP columns, but Django wants to see a
|
||||||
# python datetime.date, .time, or .datetime.
|
# 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):
|
def convert_datefield_value(self, value, expression, connection, context):
|
||||||
if isinstance(value, Database.Timestamp):
|
if isinstance(value, Database.Timestamp):
|
||||||
return value.date()
|
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.base import BaseDatabaseWrapper
|
||||||
from django.db.backends.base.validation import BaseDatabaseValidation
|
from django.db.backends.base.validation import BaseDatabaseValidation
|
||||||
from django.utils import six, timezone
|
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.encoding import force_text
|
||||||
from django.utils.safestring import SafeBytes
|
from django.utils.safestring import SafeBytes
|
||||||
|
|
||||||
|
@ -42,7 +44,6 @@ from .features import DatabaseFeatures # isort:skip
|
||||||
from .introspection import DatabaseIntrospection # isort:skip
|
from .introspection import DatabaseIntrospection # isort:skip
|
||||||
from .operations import DatabaseOperations # isort:skip
|
from .operations import DatabaseOperations # isort:skip
|
||||||
from .schema import DatabaseSchemaEditor # isort:skip
|
from .schema import DatabaseSchemaEditor # isort:skip
|
||||||
from .utils import parse_datetime_with_timezone_support # isort:skip
|
|
||||||
|
|
||||||
DatabaseError = Database.DatabaseError
|
DatabaseError = Database.DatabaseError
|
||||||
IntegrityError = Database.IntegrityError
|
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("bool"), decoder(lambda s: s == '1'))
|
||||||
Database.register_converter(str("time"), decoder(parse_time))
|
Database.register_converter(str("time"), decoder(parse_time))
|
||||||
Database.register_converter(str("date"), decoder(parse_date))
|
Database.register_converter(str("date"), decoder(parse_date))
|
||||||
Database.register_converter(str("datetime"), decoder(parse_datetime_with_timezone_support))
|
Database.register_converter(str("datetime"), decoder(parse_datetime))
|
||||||
Database.register_converter(str("timestamp"), decoder(parse_datetime_with_timezone_support))
|
Database.register_converter(str("timestamp"), decoder(parse_datetime))
|
||||||
Database.register_converter(str("TIMESTAMP"), decoder(parse_datetime_with_timezone_support))
|
Database.register_converter(str("TIMESTAMP"), decoder(parse_datetime))
|
||||||
Database.register_converter(str("decimal"), decoder(backend_utils.typecast_decimal))
|
Database.register_converter(str("decimal"), decoder(backend_utils.typecast_decimal))
|
||||||
|
|
||||||
Database.register_adapter(datetime.datetime, adapt_datetime_with_timezone_support)
|
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.backends.base.operations import BaseDatabaseOperations
|
||||||
from django.db.models import aggregates, fields
|
from django.db.models import aggregates, fields
|
||||||
from django.utils import six, timezone
|
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 django.utils.duration import duration_string
|
||||||
|
|
||||||
from .utils import parse_datetime_with_timezone_support
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import pytz
|
import pytz
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
@ -157,18 +155,23 @@ class DatabaseOperations(BaseDatabaseOperations):
|
||||||
return backend_utils.typecast_decimal(expression.output_field.format_number(value))
|
return backend_utils.typecast_decimal(expression.output_field.format_number(value))
|
||||||
|
|
||||||
def convert_datefield_value(self, value, expression, connection, context):
|
def convert_datefield_value(self, value, expression, connection, context):
|
||||||
if value is not None and not isinstance(value, datetime.date):
|
if value is not None:
|
||||||
value = parse_date(value)
|
if not isinstance(value, datetime.date):
|
||||||
|
value = parse_date(value)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def convert_datetimefield_value(self, value, expression, connection, context):
|
def convert_datetimefield_value(self, value, expression, connection, context):
|
||||||
if value is not None and not isinstance(value, datetime.datetime):
|
if value is not None:
|
||||||
value = parse_datetime_with_timezone_support(value)
|
if not isinstance(value, datetime.datetime):
|
||||||
|
value = parse_datetime(value)
|
||||||
|
if settings.USE_TZ:
|
||||||
|
value = value.replace(tzinfo=timezone.utc)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def convert_timefield_value(self, value, expression, connection, context):
|
def convert_timefield_value(self, value, expression, connection, context):
|
||||||
if value is not None and not isinstance(value, datetime.time):
|
if value is not None:
|
||||||
value = parse_time(value)
|
if not isinstance(value, datetime.time):
|
||||||
|
value = parse_time(value)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def convert_uuidfield_value(self, value, expression, connection, context):
|
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
|
doesn't implement this. You may want to review the implementation on the
|
||||||
backends that Django includes for reference (:ticket:`24245`).
|
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
|
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
|
If you are overriding Django's default logging, you should check to see how
|
||||||
your configuration merges with the new defaults.
|
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
|
Miscellaneous
|
||||||
~~~~~~~~~~~~~
|
~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ 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.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
|
from django.db import connection
|
||||||
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 (
|
||||||
|
@ -265,6 +266,13 @@ class LegacyDatabaseTests(TestCase):
|
||||||
[event],
|
[event],
|
||||||
transform=lambda d: d)
|
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):
|
def test_filter_date_field_with_aware_datetime(self):
|
||||||
# Regression test for #17742
|
# Regression test for #17742
|
||||||
day = datetime.date(2011, 9, 1)
|
day = datetime.date(2011, 9, 1)
|
||||||
|
@ -556,6 +564,23 @@ class NewDatabaseTests(TestCase):
|
||||||
[event],
|
[event],
|
||||||
transform=lambda d: d)
|
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
|
@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
|
||||||
|
|
Loading…
Reference in New Issue