web/lib/django/contrib/gis/db/models/sql/where.py
changeset 29 cc9b7e14412b
parent 0 0d40e90630ef
equal deleted inserted replaced
28:b758351d191f 29:cc9b7e14412b
     1 from django.db import connection
       
     2 from django.db.models.fields import Field, FieldDoesNotExist
     1 from django.db.models.fields import Field, FieldDoesNotExist
     3 from django.db.models.sql.constants import LOOKUP_SEP
     2 from django.db.models.sql.constants import LOOKUP_SEP
     4 from django.db.models.sql.expressions import SQLEvaluator
     3 from django.db.models.sql.expressions import SQLEvaluator
     5 from django.db.models.sql.where import WhereNode
     4 from django.db.models.sql.where import Constraint, WhereNode
     6 from django.contrib.gis.db.backend import get_geo_where_clause, SpatialBackend
       
     7 from django.contrib.gis.db.models.fields import GeometryField
     5 from django.contrib.gis.db.models.fields import GeometryField
     8 qn = connection.ops.quote_name
       
     9 
     6 
    10 class GeoAnnotation(object):
     7 class GeoConstraint(Constraint):
    11     """
     8     """
    12     The annotation used for GeometryFields; basically a placeholder
     9     This subclass overrides `process` to better handle geographic SQL
    13     for metadata needed by the `get_geo_where_clause` of the spatial
    10     construction.
    14     backend.
       
    15     """
    11     """
    16     def __init__(self, field, value, where):
    12     def __init__(self, init_constraint):
    17         self.geodetic = field.geodetic
    13         self.alias = init_constraint.alias
    18         self.geom_type = field.geom_type
    14         self.col = init_constraint.col
    19         self.value = value
    15         self.field = init_constraint.field
    20         self.where = tuple(where)
    16 
       
    17     def process(self, lookup_type, value, connection):
       
    18         if isinstance(value, SQLEvaluator):
       
    19             # Make sure the F Expression destination field exists, and
       
    20             # set an `srid` attribute with the same as that of the
       
    21             # destination.
       
    22             geo_fld = GeoWhereNode._check_geo_field(value.opts, value.expression.name)
       
    23             if not geo_fld:
       
    24                 raise ValueError('No geographic field found in expression.')
       
    25             value.srid = geo_fld.srid
       
    26         db_type = self.field.db_type(connection=connection)
       
    27         params = self.field.get_db_prep_lookup(lookup_type, value, connection=connection)
       
    28         return (self.alias, self.col, db_type), params
    21 
    29 
    22 class GeoWhereNode(WhereNode):
    30 class GeoWhereNode(WhereNode):
    23     """
    31     """
    24     Used to represent the SQL where-clause for spatial databases --
    32     Used to represent the SQL where-clause for spatial databases --
    25     these are tied to the GeoQuery class that created it.
    33     these are tied to the GeoQuery class that created it.
    26     """
    34     """
    27     def add(self, data, connector):
    35     def add(self, data, connector):
    28         """
    36         if isinstance(data, (list, tuple)):
    29         This is overridden from the regular WhereNode to handle the 
    37             obj, lookup_type, value = data
    30         peculiarties of GeometryFields, because they need a special 
    38             if ( isinstance(obj, Constraint) and
    31         annotation object that contains the spatial metadata from the 
    39                  isinstance(obj.field, GeometryField) ):
    32         field to generate the spatial SQL.
    40                 data = (GeoConstraint(obj), lookup_type, value)
    33         """
    41         super(GeoWhereNode, self).add(data, connector)
    34         if not isinstance(data, (list, tuple)):
       
    35             return super(WhereNode, self).add(data, connector)
       
    36 
    42 
    37         obj, lookup_type, value = data
    43     def make_atom(self, child, qn, connection):
    38         col, field = obj.col, obj.field
    44         lvalue, lookup_type, value_annot, params_or_value = child
    39 
    45         if isinstance(lvalue, GeoConstraint):
    40         if not hasattr(field, "geom_type"):
    46             data, params = lvalue.process(lookup_type, params_or_value, connection)
    41             # Not a geographic field, so call `WhereNode.add`.
    47             spatial_sql = connection.ops.spatial_lookup_sql(data, lookup_type, params_or_value, lvalue.field, qn)
    42             return super(GeoWhereNode, self).add(data, connector)
    48             return spatial_sql, params
    43         else:
    49         else:
    44             if isinstance(value, SQLEvaluator):
    50             return super(GeoWhereNode, self).make_atom(child, qn, connection)
    45                 # Getting the geographic field to compare with from the expression.
       
    46                 geo_fld = self._check_geo_field(value.opts, value.expression.name)
       
    47                 if not geo_fld:
       
    48                     raise ValueError('No geographic field found in expression.')
       
    49 
       
    50                 # Get the SRID of the geometry field that the expression was meant 
       
    51                 # to operate on -- it's needed to determine whether transformation 
       
    52                 # SQL is necessary.
       
    53                 srid = geo_fld.srid
       
    54 
       
    55                 # Getting the quoted representation of the geometry column that
       
    56                 # the expression is operating on.
       
    57                 geo_col = '%s.%s' % tuple(map(qn, value.cols[value.expression]))
       
    58 
       
    59                 # If it's in a different SRID, we'll need to wrap in 
       
    60                 # transformation SQL.
       
    61                 if not srid is None and srid != field.srid and SpatialBackend.transform:
       
    62                     placeholder = '%s(%%s, %s)' % (SpatialBackend.transform, field.srid)
       
    63                 else:
       
    64                     placeholder = '%s'
       
    65 
       
    66                 # Setting these up as if we had called `field.get_db_prep_lookup()`.
       
    67                 where =  [placeholder % geo_col]
       
    68                 params = ()
       
    69             else:
       
    70                 # `GeometryField.get_db_prep_lookup` returns a where clause
       
    71                 # substitution array in addition to the parameters.
       
    72                 where, params = field.get_db_prep_lookup(lookup_type, value)
       
    73 
       
    74             # The annotation will be a `GeoAnnotation` object that
       
    75             # will contain the necessary geometry field metadata for
       
    76             # the `get_geo_where_clause` to construct the appropriate
       
    77             # spatial SQL when `make_atom` is called.
       
    78             annotation = GeoAnnotation(field, value, where)
       
    79             return super(WhereNode, self).add(((obj.alias, col, field.db_type()), lookup_type, annotation, params), connector)
       
    80 
       
    81     def make_atom(self, child, qn):
       
    82         obj, lookup_type, value_annot, params = child
       
    83 
       
    84         if isinstance(value_annot, GeoAnnotation):
       
    85             if lookup_type in SpatialBackend.gis_terms:
       
    86                 # Getting the geographic where clause; substitution parameters
       
    87                 # will be populated in the GeoFieldSQL object returned by the
       
    88                 # GeometryField.
       
    89                 alias, col, db_type = obj
       
    90                 gwc = get_geo_where_clause(alias, col, lookup_type, value_annot)
       
    91                 return gwc % value_annot.where, params
       
    92             else:
       
    93                 raise TypeError('Invalid lookup type: %r' % lookup_type)
       
    94         else:
       
    95             # If not a GeometryField, call the `make_atom` from the 
       
    96             # base class.
       
    97             return super(GeoWhereNode, self).make_atom(child, qn)
       
    98 
    51 
    99     @classmethod
    52     @classmethod
   100     def _check_geo_field(cls, opts, lookup):
    53     def _check_geo_field(cls, opts, lookup):
   101         """
    54         """
   102         Utility for checking the given lookup with the given model options.  
    55         Utility for checking the given lookup with the given model options.
   103         The lookup is a string either specifying the geographic field, e.g.
    56         The lookup is a string either specifying the geographic field, e.g.
   104         'point, 'the_geom', or a related lookup on a geographic field like
    57         'point, 'the_geom', or a related lookup on a geographic field like
   105         'address__point'.
    58         'address__point'.
   106 
    59 
   107         If a GeometryField exists according to the given lookup on the model
    60         If a GeometryField exists according to the given lookup on the model
   119         try:
    72         try:
   120             geo_fld = opts.get_field(fld_name)
    73             geo_fld = opts.get_field(fld_name)
   121             # If the field list is still around, then it means that the
    74             # If the field list is still around, then it means that the
   122             # lookup was for a geometry field across a relationship --
    75             # lookup was for a geometry field across a relationship --
   123             # thus we keep on getting the related model options and the
    76             # thus we keep on getting the related model options and the
   124             # model field associated with the next field in the list 
    77             # model field associated with the next field in the list
   125             # until there's no more left.
    78             # until there's no more left.
   126             while len(field_list):
    79             while len(field_list):
   127                 opts = geo_fld.rel.to._meta
    80                 opts = geo_fld.rel.to._meta
   128                 geo_fld = opts.get_field(field_list.pop())
    81                 geo_fld = opts.get_field(field_list.pop())
   129         except (FieldDoesNotExist, AttributeError):
    82         except (FieldDoesNotExist, AttributeError):