diff --git a/django/contrib/gis/db/backends/spatialite/compiler.py b/django/contrib/gis/db/backends/spatialite/compiler.py deleted file mode 100644 index 3f81ae68a1..0000000000 --- a/django/contrib/gis/db/backends/spatialite/compiler.py +++ /dev/null @@ -1,32 +0,0 @@ -from django.db.backends.util import typecast_timestamp -from django.db.models.sql import compiler -from django.db.models.sql.constants import MULTI -from django.contrib.gis.db.models.sql.compiler import GeoSQLCompiler as BaseGeoSQLCompiler - -SQLCompiler = compiler.SQLCompiler - -class GeoSQLCompiler(BaseGeoSQLCompiler, SQLCompiler): - pass - -class SQLInsertCompiler(compiler.SQLInsertCompiler, GeoSQLCompiler): - pass - -class SQLDeleteCompiler(compiler.SQLDeleteCompiler, GeoSQLCompiler): - pass - -class SQLUpdateCompiler(compiler.SQLUpdateCompiler, GeoSQLCompiler): - pass - -class SQLAggregateCompiler(compiler.SQLAggregateCompiler, GeoSQLCompiler): - pass - -class SQLDateCompiler(compiler.SQLDateCompiler, GeoSQLCompiler): - """ - This is overridden for GeoDjango to properly cast date columns, see #16757. - """ - def results_iter(self): - offset = len(self.query.extra_select) - for rows in self.execute_sql(MULTI): - for row in rows: - date = typecast_timestamp(str(row[offset])) - yield date diff --git a/django/contrib/gis/db/backends/spatialite/operations.py b/django/contrib/gis/db/backends/spatialite/operations.py index 2ba54e5228..449c527187 100644 --- a/django/contrib/gis/db/backends/spatialite/operations.py +++ b/django/contrib/gis/db/backends/spatialite/operations.py @@ -48,7 +48,7 @@ def get_dist_ops(operator): return (SpatiaLiteDistance(operator),) class SpatiaLiteOperations(DatabaseOperations, BaseSpatialOperations): - compiler_module = 'django.contrib.gis.db.backends.spatialite.compiler' + compiler_module = 'django.contrib.gis.db.models.sql.compiler' name = 'spatialite' spatialite = True version_regex = re.compile(r'^(?P\d)\.(?P\d)\.(?P\d+)') diff --git a/django/contrib/gis/db/models/sql/compiler.py b/django/contrib/gis/db/models/sql/compiler.py index 782ce78368..405a000f42 100644 --- a/django/contrib/gis/db/models/sql/compiler.py +++ b/django/contrib/gis/db/models/sql/compiler.py @@ -1,7 +1,7 @@ from itertools import izip -from django.db.backends.util import truncate_name +from django.db.backends.util import truncate_name, typecast_timestamp from django.db.models.sql import compiler -from django.db.models.sql.constants import TABLE_NAME +from django.db.models.sql.constants import TABLE_NAME, MULTI from django.db.models.sql.query import get_proxied_model SQLCompiler = compiler.SQLCompiler @@ -194,7 +194,7 @@ class GeoSQLCompiler(compiler.SQLCompiler): # We resolve the rest of the columns if we're on Oracle or if # the `geo_values` attribute is defined. for value, field in map(None, row[index_start:], fields): - values.append(self.query.convert_values(value, field, connection=self.connection)) + values.append(self.query.convert_values(value, field, self.connection)) else: values.extend(row[index_start:]) return tuple(values) @@ -275,4 +275,24 @@ class SQLAggregateCompiler(compiler.SQLAggregateCompiler, GeoSQLCompiler): pass class SQLDateCompiler(compiler.SQLDateCompiler, GeoSQLCompiler): - pass + """ + This is overridden for GeoDjango to properly cast date columns, since + `GeoQuery.resolve_columns` is used for spatial values. + See #14648, #16757. + """ + def results_iter(self): + if self.connection.ops.oracle: + from django.db.models.fields import DateTimeField + fields = [DateTimeField()] + else: + needs_string_cast = self.connection.features.needs_datetime_string_cast + + offset = len(self.query.extra_select) + for rows in self.execute_sql(MULTI): + for row in rows: + date = row[offset] + if self.connection.ops.oracle: + date = self.resolve_columns(row, fields)[offset] + elif needs_string_cast: + date = typecast_timestamp(str(date)) + yield date diff --git a/django/contrib/gis/tests/relatedapp/fixtures/initial_data.json.gz b/django/contrib/gis/tests/relatedapp/fixtures/initial_data.json.gz index 68bf54c1b0..893763724b 100644 Binary files a/django/contrib/gis/tests/relatedapp/fixtures/initial_data.json.gz and b/django/contrib/gis/tests/relatedapp/fixtures/initial_data.json.gz differ diff --git a/django/contrib/gis/tests/relatedapp/models.py b/django/contrib/gis/tests/relatedapp/models.py index 2e9a62b61f..aec4e15749 100644 --- a/django/contrib/gis/tests/relatedapp/models.py +++ b/django/contrib/gis/tests/relatedapp/models.py @@ -36,6 +36,7 @@ class Parcel(models.Model): # These use the GeoManager but do not have any geographic fields. class Author(models.Model): name = models.CharField(max_length=100) + dob = models.DateField() objects = models.GeoManager() class Article(models.Model): diff --git a/django/contrib/gis/tests/relatedapp/tests.py b/django/contrib/gis/tests/relatedapp/tests.py index f51f34398f..685700e9ae 100644 --- a/django/contrib/gis/tests/relatedapp/tests.py +++ b/django/contrib/gis/tests/relatedapp/tests.py @@ -1,3 +1,4 @@ +from datetime import date from django.test import TestCase from django.contrib.gis.geos import GEOSGeometry, Point, MultiPoint @@ -281,4 +282,11 @@ class RelatedGeoModelTest(TestCase): # evaluated as list generation swallows TypeError in CPython. sql = str(qs.query) + def test16_annotated_date_queryset(self): + "Ensure annotated date querysets work if spatial backend is used. See #14648." + birth_years = [dt.year for dt in + list(Author.objects.annotate(num_books=Count('books')).dates('dob', 'year'))] + birth_years.sort() + self.assertEqual([1950, 1974], birth_years) + # TODO: Related tests for KML, GML, and distance lookups.