diff --git a/django/contrib/gis/db/models/lookups.py b/django/contrib/gis/db/models/lookups.py index cad6c69000..8f803a73c1 100644 --- a/django/contrib/gis/db/models/lookups.py +++ b/django/contrib/gis/db/models/lookups.py @@ -1,10 +1,51 @@ +from django.db.models.constants import LOOKUP_SEP +from django.db.models.fields import FieldDoesNotExist from django.db.models.lookups import Lookup from django.db.models.sql.expressions import SQLEvaluator class GISLookup(Lookup): + @classmethod + def _check_geo_field(cls, opts, lookup): + """ + Utility for checking the given lookup with the given model options. + The lookup is a string either specifying the geographic field, e.g. + 'point, 'the_geom', or a related lookup on a geographic field like + 'address__point'. + + If a GeometryField exists according to the given lookup on the model + options, it will be returned. Otherwise returns None. + """ + from django.contrib.gis.db.models.fields import GeometryField + # This takes into account the situation where the lookup is a + # lookup to a related geographic field, e.g., 'address__point'. + field_list = lookup.split(LOOKUP_SEP) + + # Reversing so list operates like a queue of related lookups, + # and popping the top lookup. + field_list.reverse() + fld_name = field_list.pop() + + try: + geo_fld = opts.get_field(fld_name) + # If the field list is still around, then it means that the + # lookup was for a geometry field across a relationship -- + # thus we keep on getting the related model options and the + # model field associated with the next field in the list + # until there's no more left. + while len(field_list): + opts = geo_fld.rel.to._meta + geo_fld = opts.get_field(field_list.pop()) + except (FieldDoesNotExist, AttributeError): + return False + + # Finally, make sure we got a Geographic field and return. + if isinstance(geo_fld, GeometryField): + return geo_fld + else: + return False + 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. @@ -12,7 +53,7 @@ class GISLookup(Lookup): # 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) + geo_fld = self._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 diff --git a/django/contrib/gis/db/models/sql/__init__.py b/django/contrib/gis/db/models/sql/__init__.py index a7d15dab63..cde4c2d5e6 100644 --- a/django/contrib/gis/db/models/sql/__init__.py +++ b/django/contrib/gis/db/models/sql/__init__.py @@ -1,7 +1,6 @@ from django.contrib.gis.db.models.sql.conversion import AreaField, DistanceField, GeomField from django.contrib.gis.db.models.sql.query import GeoQuery -from django.contrib.gis.db.models.sql.where import GeoWhereNode __all__ = [ - 'AreaField', 'DistanceField', 'GeomField', 'GeoQuery', 'GeoWhereNode', + 'AreaField', 'DistanceField', 'GeomField', 'GeoQuery', ] diff --git a/django/contrib/gis/db/models/sql/query.py b/django/contrib/gis/db/models/sql/query.py index f3fa1f6322..9aec3a65bc 100644 --- a/django/contrib/gis/db/models/sql/query.py +++ b/django/contrib/gis/db/models/sql/query.py @@ -3,9 +3,9 @@ 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.lookups import GISLookup from django.contrib.gis.db.models.sql import aggregates as gis_aggregates from django.contrib.gis.db.models.sql.conversion import AreaField, DistanceField, GeomField -from django.contrib.gis.db.models.sql.where import GeoWhereNode from django.contrib.gis.geometry.backend import Geometry from django.contrib.gis.measure import Area, Distance @@ -21,11 +21,10 @@ class GeoQuery(sql.Query): compiler = 'GeoSQLCompiler' #### Methods overridden from the base Query class #### - def __init__(self, model, where=GeoWhereNode): - super(GeoQuery, self).__init__(model, where) + def __init__(self, model): + super(GeoQuery, self).__init__(model) #, where) # The following attributes are customized for the GeoQuerySet. - # The GeoWhereNode and SpatialBackend classes contain backend-specific - # routines and functions. + # The SpatialBackend classes contain backend-specific routines and functions. self.custom_select = {} self.transformed_srid = None self.extra_select_fields = {} @@ -108,4 +107,4 @@ class GeoQuery(sql.Query): else: # Otherwise, check by the given field name -- which may be # a lookup to a _related_ geographic field. - return GeoWhereNode._check_geo_field(self.model._meta, field_name) + return GISLookup._check_geo_field(self.model._meta, field_name) diff --git a/django/contrib/gis/db/models/sql/where.py b/django/contrib/gis/db/models/sql/where.py deleted file mode 100644 index 1e750ebd89..0000000000 --- a/django/contrib/gis/db/models/sql/where.py +++ /dev/null @@ -1,93 +0,0 @@ -from django.db.models.constants import LOOKUP_SEP -from django.db.models.fields import FieldDoesNotExist -from django.db.models.sql.expressions import SQLEvaluator -from django.db.models.sql.where import Constraint, WhereNode -from django.contrib.gis.db.models.fields import GeometryField - - -class GeoConstraint(Constraint): - """ - This subclass overrides `process` to better handle geographic SQL - construction. - """ - def __init__(self, init_constraint): - self.alias = init_constraint.alias - self.col = init_constraint.col - self.field = init_constraint.field - - def process(self, lookup_type, value, connection): - if isinstance(value, 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(value.opts, value.expression.name) - if not geo_fld: - raise ValueError('No geographic field found in expression.') - value.srid = geo_fld.srid - db_type = self.field.db_type(connection=connection) - params = self.field.get_db_prep_lookup(lookup_type, value, connection=connection) - return (self.alias, self.col, db_type), params - - -class GeoWhereNode(WhereNode): - """ - Used to represent the SQL where-clause for spatial databases -- - these are tied to the GeoQuery class that created it. - """ - - def _prepare_data(self, data): - if isinstance(data, (list, tuple)): - obj, lookup_type, value = data - if (isinstance(obj, Constraint) and - isinstance(obj.field, GeometryField)): - data = (GeoConstraint(obj), lookup_type, value) - return super(GeoWhereNode, self)._prepare_data(data) - - def make_atom(self, child, qn, connection): - lvalue, lookup_type, value_annot, params_or_value = child - if isinstance(lvalue, GeoConstraint): - data, params = lvalue.process(lookup_type, params_or_value, connection) - spatial_sql, spatial_params = connection.ops.spatial_lookup_sql( - data, lookup_type, params_or_value, lvalue.field, qn) - return spatial_sql, spatial_params + params - else: - return super(GeoWhereNode, self).make_atom(child, qn, connection) - - @classmethod - def _check_geo_field(cls, opts, lookup): - """ - Utility for checking the given lookup with the given model options. - The lookup is a string either specifying the geographic field, e.g. - 'point, 'the_geom', or a related lookup on a geographic field like - 'address__point'. - - If a GeometryField exists according to the given lookup on the model - options, it will be returned. Otherwise returns None. - """ - # This takes into account the situation where the lookup is a - # lookup to a related geographic field, e.g., 'address__point'. - field_list = lookup.split(LOOKUP_SEP) - - # Reversing so list operates like a queue of related lookups, - # and popping the top lookup. - field_list.reverse() - fld_name = field_list.pop() - - try: - geo_fld = opts.get_field(fld_name) - # If the field list is still around, then it means that the - # lookup was for a geometry field across a relationship -- - # thus we keep on getting the related model options and the - # model field associated with the next field in the list - # until there's no more left. - while len(field_list): - opts = geo_fld.rel.to._meta - geo_fld = opts.get_field(field_list.pop()) - except (FieldDoesNotExist, AttributeError): - return False - - # Finally, make sure we got a Geographic field and return. - if isinstance(geo_fld, GeometryField): - return geo_fld - else: - return False