diff --git a/django/contrib/gis/db/backends/mysql/operations.py b/django/contrib/gis/db/backends/mysql/operations.py index 81aa1e2fb6..989b5a1292 100644 --- a/django/contrib/gis/db/backends/mysql/operations.py +++ b/django/contrib/gis/db/backends/mysql/operations.py @@ -49,9 +49,7 @@ class MySQLOperations(DatabaseOperations, BaseSpatialOperations): return placeholder def spatial_lookup_sql(self, lvalue, lookup_type, value, field, qn): - alias, col, db_type = lvalue - - geo_col = '%s.%s' % (qn(alias), qn(col)) + geo_col, db_type = lvalue lookup_info = self.geometry_functions.get(lookup_type, False) if lookup_info: diff --git a/django/contrib/gis/db/backends/oracle/operations.py b/django/contrib/gis/db/backends/oracle/operations.py index 8212938f01..b3aa191ccc 100644 --- a/django/contrib/gis/db/backends/oracle/operations.py +++ b/django/contrib/gis/db/backends/oracle/operations.py @@ -231,10 +231,7 @@ class OracleOperations(DatabaseOperations, BaseSpatialOperations): def spatial_lookup_sql(self, lvalue, lookup_type, value, field, qn): "Returns the SQL WHERE clause for use in Oracle spatial SQL construction." - alias, col, db_type = lvalue - - # Getting the quoted table name as `geo_col`. - geo_col = '%s.%s' % (qn(alias), qn(col)) + geo_col, db_type = lvalue # See if a Oracle Geometry function matches the lookup type next lookup_info = self.geometry_functions.get(lookup_type, False) diff --git a/django/contrib/gis/db/backends/postgis/operations.py b/django/contrib/gis/db/backends/postgis/operations.py index 86608afa35..fabf2b7871 100644 --- a/django/contrib/gis/db/backends/postgis/operations.py +++ b/django/contrib/gis/db/backends/postgis/operations.py @@ -478,10 +478,7 @@ class PostGISOperations(DatabaseOperations, BaseSpatialOperations): (alias, col, db_type), the lookup type string, lookup value, and the geometry field. """ - alias, col, db_type = lvalue - - # Getting the quoted geometry column. - geo_col = '%s.%s' % (qn(alias), qn(col)) + geo_col, db_type = lvalue if lookup_type in self.geometry_operators: if field.geography and not lookup_type in self.geography_operators: diff --git a/django/contrib/gis/db/backends/spatialite/operations.py b/django/contrib/gis/db/backends/spatialite/operations.py index 5149b541a2..ad44b470b3 100644 --- a/django/contrib/gis/db/backends/spatialite/operations.py +++ b/django/contrib/gis/db/backends/spatialite/operations.py @@ -324,10 +324,7 @@ class SpatiaLiteOperations(DatabaseOperations, BaseSpatialOperations): [a tuple of (alias, column, db_type)], lookup type, lookup value, the model field, and the quoting function. """ - alias, col, db_type = lvalue - - # Getting the quoted field as `geo_col`. - geo_col = '%s.%s' % (qn(alias), qn(col)) + geo_col, db_type = lvalue if lookup_type in self.geometry_functions: # See if a SpatiaLite geometry function matches the lookup type. diff --git a/django/contrib/gis/db/models/constants.py b/django/contrib/gis/db/models/constants.py new file mode 100644 index 0000000000..52794231b0 --- /dev/null +++ b/django/contrib/gis/db/models/constants.py @@ -0,0 +1,16 @@ +from django.db.models.sql.constants import QUERY_TERMS + +ALL_TERMS = set([ + 'bbcontains', 'bboverlaps', 'contained', 'contains', + 'contains_properly', 'coveredby', 'covers', 'crosses', 'disjoint', + 'distance_gt', 'distance_gte', 'distance_lt', 'distance_lte', + 'dwithin', 'equals', 'exact', + 'intersects', 'overlaps', 'relate', 'same_as', 'touches', 'within', + 'left', 'right', 'overlaps_left', 'overlaps_right', + 'overlaps_above', 'overlaps_below', + 'strictly_above', 'strictly_below' +]) +GIS_LOOKUPS = ALL_TERMS.copy() +ALL_TERMS.update(QUERY_TERMS) + +__all__ = ['ALL_TERMS', 'GIS_LOOKUPS'] diff --git a/django/contrib/gis/db/models/fields.py b/django/contrib/gis/db/models/fields.py index b5cdc72c29..ee82d4f263 100644 --- a/django/contrib/gis/db/models/fields.py +++ b/django/contrib/gis/db/models/fields.py @@ -2,6 +2,8 @@ from django.db.models.fields import Field from django.db.models.sql.expressions import SQLEvaluator from django.utils.translation import ugettext_lazy as _ from django.contrib.gis import forms +from django.contrib.gis.db.models.constants import GIS_LOOKUPS +from django.contrib.gis.db.models.lookups import GISLookup from django.contrib.gis.db.models.proxy import GeometryProxy from django.contrib.gis.geometry.backend import Geometry, GeometryException from django.utils import six @@ -284,6 +286,10 @@ class GeometryField(Field): """ return connection.ops.get_geom_placeholder(self, value) +for lookup_name in GIS_LOOKUPS: + lookup = type(lookup_name, (GISLookup,), {'lookup_name': lookup_name}) + GeometryField.register_lookup(lookup) + # The OpenGIS Geometry Type Fields class PointField(GeometryField): diff --git a/django/contrib/gis/db/models/lookups.py b/django/contrib/gis/db/models/lookups.py new file mode 100644 index 0000000000..cad6c69000 --- /dev/null +++ b/django/contrib/gis/db/models/lookups.py @@ -0,0 +1,28 @@ +from django.db.models.lookups import Lookup +from django.db.models.sql.expressions import SQLEvaluator + + +class GISLookup(Lookup): + def as_sql(self, qn, connection): + from django.contrib.gis.db.models.sql import GeoWhereNode + # We use the same approach as was used by GeoWhereNode. It would + # be a good idea to upgrade GIS to use similar code that is used + # for other lookups. + if isinstance(self.rhs, SQLEvaluator): + # Make sure the F Expression destination field exists, and + # set an `srid` attribute with the same as that of the + # destination. + geo_fld = GeoWhereNode._check_geo_field(self.rhs.opts, self.rhs.expression.name) + if not geo_fld: + raise ValueError('No geographic field found in expression.') + self.rhs.srid = geo_fld.srid + db_type = self.lhs.output_type.db_type(connection=connection) + params = self.lhs.output_type.get_db_prep_lookup( + self.lookup_name, self.rhs, connection=connection) + lhs_sql, lhs_params = self.process_lhs(qn, connection) + # lhs_params not currently supported. + assert not lhs_params + data = (lhs_sql, db_type) + spatial_sql, spatial_params = connection.ops.spatial_lookup_sql( + data, self.lookup_name, self.rhs, self.lhs.output_type, qn) + return spatial_sql, spatial_params + params diff --git a/django/contrib/gis/db/models/sql/query.py b/django/contrib/gis/db/models/sql/query.py index 3923bf460e..f3fa1f6322 100644 --- a/django/contrib/gis/db/models/sql/query.py +++ b/django/contrib/gis/db/models/sql/query.py @@ -1,6 +1,7 @@ from django.db import connections from django.db.models.query import sql +from django.contrib.gis.db.models.constants import ALL_TERMS from django.contrib.gis.db.models.fields import GeometryField from django.contrib.gis.db.models.sql import aggregates as gis_aggregates from django.contrib.gis.db.models.sql.conversion import AreaField, DistanceField, GeomField @@ -9,19 +10,6 @@ from django.contrib.gis.geometry.backend import Geometry from django.contrib.gis.measure import Area, Distance -ALL_TERMS = set([ - 'bbcontains', 'bboverlaps', 'contained', 'contains', - 'contains_properly', 'coveredby', 'covers', 'crosses', 'disjoint', - 'distance_gt', 'distance_gte', 'distance_lt', 'distance_lte', - 'dwithin', 'equals', 'exact', - 'intersects', 'overlaps', 'relate', 'same_as', 'touches', 'within', - 'left', 'right', 'overlaps_left', 'overlaps_right', - 'overlaps_above', 'overlaps_below', - 'strictly_above', 'strictly_below' -]) -ALL_TERMS.update(sql.constants.QUERY_TERMS) - - class GeoQuery(sql.Query): """ A single spatial SQL query.