summaryrefslogtreecommitdiff
path: root/lib/python2.7/site-packages/django/contrib/gis/db/models/sql
diff options
context:
space:
mode:
authorttt2017-05-13 00:29:47 +0530
committerttt2017-05-13 00:29:47 +0530
commit4336f5f06f61de30ae3fa54650fce63a9d5ef5be (patch)
tree23b4ee9b8e8f24bf732acf2f7ad22ed50cdd5670 /lib/python2.7/site-packages/django/contrib/gis/db/models/sql
downloadSBHS-2018-Rpi-4336f5f06f61de30ae3fa54650fce63a9d5ef5be.tar.gz
SBHS-2018-Rpi-4336f5f06f61de30ae3fa54650fce63a9d5ef5be.tar.bz2
SBHS-2018-Rpi-4336f5f06f61de30ae3fa54650fce63a9d5ef5be.zip
added all server files
Diffstat (limited to 'lib/python2.7/site-packages/django/contrib/gis/db/models/sql')
-rw-r--r--lib/python2.7/site-packages/django/contrib/gis/db/models/sql/__init__.py3
-rw-r--r--lib/python2.7/site-packages/django/contrib/gis/db/models/sql/aggregates.py62
-rw-r--r--lib/python2.7/site-packages/django/contrib/gis/db/models/sql/compiler.py313
-rw-r--r--lib/python2.7/site-packages/django/contrib/gis/db/models/sql/conversion.py27
-rw-r--r--lib/python2.7/site-packages/django/contrib/gis/db/models/sql/query.py121
-rw-r--r--lib/python2.7/site-packages/django/contrib/gis/db/models/sql/where.py91
6 files changed, 617 insertions, 0 deletions
diff --git a/lib/python2.7/site-packages/django/contrib/gis/db/models/sql/__init__.py b/lib/python2.7/site-packages/django/contrib/gis/db/models/sql/__init__.py
new file mode 100644
index 0000000..38d9507
--- /dev/null
+++ b/lib/python2.7/site-packages/django/contrib/gis/db/models/sql/__init__.py
@@ -0,0 +1,3 @@
+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
diff --git a/lib/python2.7/site-packages/django/contrib/gis/db/models/sql/aggregates.py b/lib/python2.7/site-packages/django/contrib/gis/db/models/sql/aggregates.py
new file mode 100644
index 0000000..ae848c0
--- /dev/null
+++ b/lib/python2.7/site-packages/django/contrib/gis/db/models/sql/aggregates.py
@@ -0,0 +1,62 @@
+from django.db.models.sql.aggregates import *
+from django.contrib.gis.db.models.fields import GeometryField
+
+class GeoAggregate(Aggregate):
+ # Default SQL template for spatial aggregates.
+ sql_template = '%(function)s(%(field)s)'
+
+ # Conversion class, if necessary.
+ conversion_class = None
+
+ # Flags for indicating the type of the aggregate.
+ is_extent = False
+
+ def __init__(self, col, source=None, is_summary=False, tolerance=0.05, **extra):
+ super(GeoAggregate, self).__init__(col, source, is_summary, **extra)
+
+ # Required by some Oracle aggregates.
+ self.tolerance = tolerance
+
+ # Can't use geographic aggregates on non-geometry fields.
+ if not isinstance(self.source, GeometryField):
+ raise ValueError('Geospatial aggregates only allowed on geometry fields.')
+
+ def as_sql(self, qn, connection):
+ "Return the aggregate, rendered as SQL with parameters."
+
+ if connection.ops.oracle:
+ self.extra['tolerance'] = self.tolerance
+
+ params = []
+
+ if hasattr(self.col, 'as_sql'):
+ field_name, params = self.col.as_sql(qn, connection)
+ elif isinstance(self.col, (list, tuple)):
+ field_name = '.'.join([qn(c) for c in self.col])
+ else:
+ field_name = self.col
+
+ sql_template, sql_function = connection.ops.spatial_aggregate_sql(self)
+
+ substitutions = {
+ 'function': sql_function,
+ 'field': field_name
+ }
+ substitutions.update(self.extra)
+
+ return sql_template % substitutions, params
+
+class Collect(GeoAggregate):
+ pass
+
+class Extent(GeoAggregate):
+ is_extent = '2D'
+
+class Extent3D(GeoAggregate):
+ is_extent = '3D'
+
+class MakeLine(GeoAggregate):
+ pass
+
+class Union(GeoAggregate):
+ pass
diff --git a/lib/python2.7/site-packages/django/contrib/gis/db/models/sql/compiler.py b/lib/python2.7/site-packages/django/contrib/gis/db/models/sql/compiler.py
new file mode 100644
index 0000000..3fc9c17
--- /dev/null
+++ b/lib/python2.7/site-packages/django/contrib/gis/db/models/sql/compiler.py
@@ -0,0 +1,313 @@
+import datetime
+
+from django.conf import settings
+from django.db.backends.util import truncate_name, typecast_date, typecast_timestamp
+from django.db.models.sql import compiler
+from django.db.models.sql.constants import MULTI
+from django.utils import six
+from django.utils.six.moves import zip, zip_longest
+from django.utils import timezone
+
+SQLCompiler = compiler.SQLCompiler
+
+class GeoSQLCompiler(compiler.SQLCompiler):
+
+ def get_columns(self, with_aliases=False):
+ """
+ Return the list of columns to use in the select statement. If no
+ columns have been specified, returns all columns relating to fields in
+ the model.
+
+ If 'with_aliases' is true, any column names that are duplicated
+ (without the table names) are given unique aliases. This is needed in
+ some cases to avoid ambiguitity with nested queries.
+
+ This routine is overridden from Query to handle customized selection of
+ geometry columns.
+ """
+ qn = self.quote_name_unless_alias
+ qn2 = self.connection.ops.quote_name
+ result = ['(%s) AS %s' % (self.get_extra_select_format(alias) % col[0], qn2(alias))
+ for alias, col in six.iteritems(self.query.extra_select)]
+ params = []
+ aliases = set(self.query.extra_select.keys())
+ if with_aliases:
+ col_aliases = aliases.copy()
+ else:
+ col_aliases = set()
+ if self.query.select:
+ only_load = self.deferred_to_columns()
+ # This loop customized for GeoQuery.
+ for col, field in self.query.select:
+ if isinstance(col, (list, tuple)):
+ alias, column = col
+ table = self.query.alias_map[alias].table_name
+ if table in only_load and column not in only_load[table]:
+ continue
+ r = self.get_field_select(field, alias, column)
+ if with_aliases:
+ if col[1] in col_aliases:
+ c_alias = 'Col%d' % len(col_aliases)
+ result.append('%s AS %s' % (r, c_alias))
+ aliases.add(c_alias)
+ col_aliases.add(c_alias)
+ else:
+ result.append('%s AS %s' % (r, qn2(col[1])))
+ aliases.add(r)
+ col_aliases.add(col[1])
+ else:
+ result.append(r)
+ aliases.add(r)
+ col_aliases.add(col[1])
+ else:
+ col_sql, col_params = col.as_sql(qn, self.connection)
+ result.append(col_sql)
+ params.extend(col_params)
+
+ if hasattr(col, 'alias'):
+ aliases.add(col.alias)
+ col_aliases.add(col.alias)
+
+ elif self.query.default_cols:
+ cols, new_aliases = self.get_default_columns(with_aliases,
+ col_aliases)
+ result.extend(cols)
+ aliases.update(new_aliases)
+
+ max_name_length = self.connection.ops.max_name_length()
+ for alias, aggregate in self.query.aggregate_select.items():
+ agg_sql, agg_params = aggregate.as_sql(qn, self.connection)
+ if alias is None:
+ result.append(agg_sql)
+ else:
+ result.append('%s AS %s' % (agg_sql, qn(truncate_name(alias, max_name_length))))
+ params.extend(agg_params)
+
+ # This loop customized for GeoQuery.
+ for (table, col), field in self.query.related_select_cols:
+ r = self.get_field_select(field, table, col)
+ if with_aliases and col in col_aliases:
+ c_alias = 'Col%d' % len(col_aliases)
+ result.append('%s AS %s' % (r, c_alias))
+ aliases.add(c_alias)
+ col_aliases.add(c_alias)
+ else:
+ result.append(r)
+ aliases.add(r)
+ col_aliases.add(col)
+
+ self._select_aliases = aliases
+ return result, params
+
+ def get_default_columns(self, with_aliases=False, col_aliases=None,
+ start_alias=None, opts=None, as_pairs=False, from_parent=None):
+ """
+ Computes the default columns for selecting every field in the base
+ model. Will sometimes be called to pull in related models (e.g. via
+ select_related), in which case "opts" and "start_alias" will be given
+ to provide a starting point for the traversal.
+
+ Returns a list of strings, quoted appropriately for use in SQL
+ directly, as well as a set of aliases used in the select statement (if
+ 'as_pairs' is True, returns a list of (alias, col_name) pairs instead
+ of strings as the first component and None as the second component).
+
+ This routine is overridden from Query to handle customized selection of
+ geometry columns.
+ """
+ result = []
+ if opts is None:
+ opts = self.query.get_meta()
+ aliases = set()
+ only_load = self.deferred_to_columns()
+ seen = self.query.included_inherited_models.copy()
+ if start_alias:
+ seen[None] = start_alias
+ for field, model in opts.get_concrete_fields_with_model():
+ if from_parent and model is not None and issubclass(from_parent, model):
+ # Avoid loading data for already loaded parents.
+ continue
+ alias = self.query.join_parent_model(opts, model, start_alias, seen)
+ table = self.query.alias_map[alias].table_name
+ if table in only_load and field.column not in only_load[table]:
+ continue
+ if as_pairs:
+ result.append((alias, field))
+ aliases.add(alias)
+ continue
+ # This part of the function is customized for GeoQuery. We
+ # see if there was any custom selection specified in the
+ # dictionary, and set up the selection format appropriately.
+ field_sel = self.get_field_select(field, alias)
+ if with_aliases and field.column in col_aliases:
+ c_alias = 'Col%d' % len(col_aliases)
+ result.append('%s AS %s' % (field_sel, c_alias))
+ col_aliases.add(c_alias)
+ aliases.add(c_alias)
+ else:
+ r = field_sel
+ result.append(r)
+ aliases.add(r)
+ if with_aliases:
+ col_aliases.add(field.column)
+ return result, aliases
+
+ def resolve_columns(self, row, fields=()):
+ """
+ This routine is necessary so that distances and geometries returned
+ from extra selection SQL get resolved appropriately into Python
+ objects.
+ """
+ values = []
+ aliases = list(self.query.extra_select)
+
+ # Have to set a starting row number offset that is used for
+ # determining the correct starting row index -- needed for
+ # doing pagination with Oracle.
+ rn_offset = 0
+ if self.connection.ops.oracle:
+ if self.query.high_mark is not None or self.query.low_mark: rn_offset = 1
+ index_start = rn_offset + len(aliases)
+
+ # Converting any extra selection values (e.g., geometries and
+ # distance objects added by GeoQuerySet methods).
+ values = [self.query.convert_values(v,
+ self.query.extra_select_fields.get(a, None),
+ self.connection)
+ for v, a in zip(row[rn_offset:index_start], aliases)]
+ if self.connection.ops.oracle or getattr(self.query, 'geo_values', False):
+ # We resolve the rest of the columns if we're on Oracle or if
+ # the `geo_values` attribute is defined.
+ for value, field in zip_longest(row[index_start:], fields):
+ values.append(self.query.convert_values(value, field, self.connection))
+ else:
+ values.extend(row[index_start:])
+ return tuple(values)
+
+ #### Routines unique to GeoQuery ####
+ def get_extra_select_format(self, alias):
+ sel_fmt = '%s'
+ if hasattr(self.query, 'custom_select') and alias in self.query.custom_select:
+ sel_fmt = sel_fmt % self.query.custom_select[alias]
+ return sel_fmt
+
+ def get_field_select(self, field, alias=None, column=None):
+ """
+ Returns the SELECT SQL string for the given field. Figures out
+ if any custom selection SQL is needed for the column The `alias`
+ keyword may be used to manually specify the database table where
+ the column exists, if not in the model associated with this
+ `GeoQuery`. Similarly, `column` may be used to specify the exact
+ column name, rather than using the `column` attribute on `field`.
+ """
+ sel_fmt = self.get_select_format(field)
+ if field in self.query.custom_select:
+ field_sel = sel_fmt % self.query.custom_select[field]
+ else:
+ field_sel = sel_fmt % self._field_column(field, alias, column)
+ return field_sel
+
+ def get_select_format(self, fld):
+ """
+ Returns the selection format string, depending on the requirements
+ of the spatial backend. For example, Oracle and MySQL require custom
+ selection formats in order to retrieve geometries in OGC WKT. For all
+ other fields a simple '%s' format string is returned.
+ """
+ if self.connection.ops.select and hasattr(fld, 'geom_type'):
+ # This allows operations to be done on fields in the SELECT,
+ # overriding their values -- used by the Oracle and MySQL
+ # spatial backends to get database values as WKT, and by the
+ # `transform` method.
+ sel_fmt = self.connection.ops.select
+
+ # Because WKT doesn't contain spatial reference information,
+ # the SRID is prefixed to the returned WKT to ensure that the
+ # transformed geometries have an SRID different than that of the
+ # field -- this is only used by `transform` for Oracle and
+ # SpatiaLite backends.
+ if self.query.transformed_srid and ( self.connection.ops.oracle or
+ self.connection.ops.spatialite ):
+ sel_fmt = "'SRID=%d;'||%s" % (self.query.transformed_srid, sel_fmt)
+ else:
+ sel_fmt = '%s'
+ return sel_fmt
+
+ # Private API utilities, subject to change.
+ def _field_column(self, field, table_alias=None, column=None):
+ """
+ Helper function that returns the database column for the given field.
+ The table and column are returned (quoted) in the proper format, e.g.,
+ `"geoapp_city"."point"`. If `table_alias` is not specified, the
+ database table associated with the model of this `GeoQuery` will be
+ used. If `column` is specified, it will be used instead of the value
+ in `field.column`.
+ """
+ if table_alias is None: table_alias = self.query.get_meta().db_table
+ return "%s.%s" % (self.quote_name_unless_alias(table_alias),
+ self.connection.ops.quote_name(column or field.column))
+
+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, 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_date(str(date))
+ if isinstance(date, datetime.datetime):
+ date = date.date()
+ yield date
+
+class SQLDateTimeCompiler(compiler.SQLDateTimeCompiler, GeoSQLCompiler):
+ """
+ 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:
+ datetime = row[offset]
+ if self.connection.ops.oracle:
+ datetime = self.resolve_columns(row, fields)[offset]
+ elif needs_string_cast:
+ datetime = typecast_timestamp(str(datetime))
+ # Datetimes are artifically returned in UTC on databases that
+ # don't support time zone. Restore the zone used in the query.
+ if settings.USE_TZ:
+ datetime = datetime.replace(tzinfo=None)
+ datetime = timezone.make_aware(datetime, self.query.tzinfo)
+ yield datetime
diff --git a/lib/python2.7/site-packages/django/contrib/gis/db/models/sql/conversion.py b/lib/python2.7/site-packages/django/contrib/gis/db/models/sql/conversion.py
new file mode 100644
index 0000000..160b623
--- /dev/null
+++ b/lib/python2.7/site-packages/django/contrib/gis/db/models/sql/conversion.py
@@ -0,0 +1,27 @@
+"""
+This module holds simple classes used by GeoQuery.convert_values
+to convert geospatial values from the database.
+"""
+
+class BaseField(object):
+ empty_strings_allowed = True
+ def get_internal_type(self):
+ "Overloaded method so OracleQuery.convert_values doesn't balk."
+ return None
+
+class AreaField(BaseField):
+ "Wrapper for Area values."
+ def __init__(self, area_att):
+ self.area_att = area_att
+
+class DistanceField(BaseField):
+ "Wrapper for Distance values."
+ def __init__(self, distance_att):
+ self.distance_att = distance_att
+
+class GeomField(BaseField):
+ """
+ Wrapper for Geometry values. It is a lightweight alternative to
+ using GeometryField (which requires an SQL query upon instantiation).
+ """
+ pass
diff --git a/lib/python2.7/site-packages/django/contrib/gis/db/models/sql/query.py b/lib/python2.7/site-packages/django/contrib/gis/db/models/sql/query.py
new file mode 100644
index 0000000..5877f29
--- /dev/null
+++ b/lib/python2.7/site-packages/django/contrib/gis/db/models/sql/query.py
@@ -0,0 +1,121 @@
+from django.db import connections
+from django.db.models.query import sql
+
+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
+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
+
+
+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.
+ """
+ # Overridding the valid query terms.
+ query_terms = ALL_TERMS
+ aggregates_module = gis_aggregates
+
+ compiler = 'GeoSQLCompiler'
+
+ #### Methods overridden from the base Query class ####
+ def __init__(self, model, where=GeoWhereNode):
+ 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.
+ self.custom_select = {}
+ self.transformed_srid = None
+ self.extra_select_fields = {}
+
+ def clone(self, *args, **kwargs):
+ obj = super(GeoQuery, self).clone(*args, **kwargs)
+ # Customized selection dictionary and transformed srid flag have
+ # to also be added to obj.
+ obj.custom_select = self.custom_select.copy()
+ obj.transformed_srid = self.transformed_srid
+ obj.extra_select_fields = self.extra_select_fields.copy()
+ return obj
+
+ def convert_values(self, value, field, connection):
+ """
+ Using the same routines that Oracle does we can convert our
+ extra selection objects into Geometry and Distance objects.
+ TODO: Make converted objects 'lazy' for less overhead.
+ """
+ if connection.ops.oracle:
+ # Running through Oracle's first.
+ value = super(GeoQuery, self).convert_values(value, field or GeomField(), connection)
+
+ if value is None:
+ # Output from spatial function is NULL (e.g., called
+ # function on a geometry field with NULL value).
+ pass
+ elif isinstance(field, DistanceField):
+ # Using the field's distance attribute, can instantiate
+ # `Distance` with the right context.
+ value = Distance(**{field.distance_att : value})
+ elif isinstance(field, AreaField):
+ value = Area(**{field.area_att : value})
+ elif isinstance(field, (GeomField, GeometryField)) and value:
+ value = Geometry(value)
+ elif field is not None:
+ return super(GeoQuery, self).convert_values(value, field, connection)
+ return value
+
+ def get_aggregation(self, using):
+ # Remove any aggregates marked for reduction from the subquery
+ # and move them to the outer AggregateQuery.
+ connection = connections[using]
+ for alias, aggregate in self.aggregate_select.items():
+ if isinstance(aggregate, gis_aggregates.GeoAggregate):
+ if not getattr(aggregate, 'is_extent', False) or connection.ops.oracle:
+ self.extra_select_fields[alias] = GeomField()
+ return super(GeoQuery, self).get_aggregation(using)
+
+ def resolve_aggregate(self, value, aggregate, connection):
+ """
+ Overridden from GeoQuery's normalize to handle the conversion of
+ GeoAggregate objects.
+ """
+ if isinstance(aggregate, self.aggregates_module.GeoAggregate):
+ if aggregate.is_extent:
+ if aggregate.is_extent == '3D':
+ return connection.ops.convert_extent3d(value)
+ else:
+ return connection.ops.convert_extent(value)
+ else:
+ return connection.ops.convert_geom(value, aggregate.source)
+ else:
+ return super(GeoQuery, self).resolve_aggregate(value, aggregate, connection)
+
+ # Private API utilities, subject to change.
+ def _geo_field(self, field_name=None):
+ """
+ Returns the first Geometry field encountered; or specified via the
+ `field_name` keyword. The `field_name` may be a string specifying
+ the geometry field on this GeoQuery's model, or a lookup string
+ to a geometry field via a ForeignKey relation.
+ """
+ if field_name is None:
+ # Incrementing until the first geographic field is found.
+ for fld in self.model._meta.fields:
+ if isinstance(fld, GeometryField): return fld
+ return False
+ 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)
diff --git a/lib/python2.7/site-packages/django/contrib/gis/db/models/sql/where.py b/lib/python2.7/site-packages/django/contrib/gis/db/models/sql/where.py
new file mode 100644
index 0000000..c29533b
--- /dev/null
+++ b/lib/python2.7/site-packages/django/contrib/gis/db/models/sql/where.py
@@ -0,0 +1,91 @@
+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