web/lib/django/contrib/gis/db/models/sql/where.py
changeset 0 0d40e90630ef
child 29 cc9b7e14412b
equal deleted inserted replaced
-1:000000000000 0:0d40e90630ef
       
     1 from django.db import connection
       
     2 from django.db.models.fields import Field, FieldDoesNotExist
       
     3 from django.db.models.sql.constants import LOOKUP_SEP
       
     4 from django.db.models.sql.expressions import SQLEvaluator
       
     5 from django.db.models.sql.where import WhereNode
       
     6 from django.contrib.gis.db.backend import get_geo_where_clause, SpatialBackend
       
     7 from django.contrib.gis.db.models.fields import GeometryField
       
     8 qn = connection.ops.quote_name
       
     9 
       
    10 class GeoAnnotation(object):
       
    11     """
       
    12     The annotation used for GeometryFields; basically a placeholder
       
    13     for metadata needed by the `get_geo_where_clause` of the spatial
       
    14     backend.
       
    15     """
       
    16     def __init__(self, field, value, where):
       
    17         self.geodetic = field.geodetic
       
    18         self.geom_type = field.geom_type
       
    19         self.value = value
       
    20         self.where = tuple(where)
       
    21 
       
    22 class GeoWhereNode(WhereNode):
       
    23     """
       
    24     Used to represent the SQL where-clause for spatial databases --
       
    25     these are tied to the GeoQuery class that created it.
       
    26     """
       
    27     def add(self, data, connector):
       
    28         """
       
    29         This is overridden from the regular WhereNode to handle the 
       
    30         peculiarties of GeometryFields, because they need a special 
       
    31         annotation object that contains the spatial metadata from the 
       
    32         field to generate the spatial SQL.
       
    33         """
       
    34         if not isinstance(data, (list, tuple)):
       
    35             return super(WhereNode, self).add(data, connector)
       
    36 
       
    37         obj, lookup_type, value = data
       
    38         col, field = obj.col, obj.field
       
    39 
       
    40         if not hasattr(field, "geom_type"):
       
    41             # Not a geographic field, so call `WhereNode.add`.
       
    42             return super(GeoWhereNode, self).add(data, connector)
       
    43         else:
       
    44             if isinstance(value, SQLEvaluator):
       
    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 
       
    99     @classmethod
       
   100     def _check_geo_field(cls, opts, lookup):
       
   101         """
       
   102         Utility for checking the given lookup with the given model options.  
       
   103         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
       
   105         'address__point'.
       
   106 
       
   107         If a GeometryField exists according to the given lookup on the model
       
   108         options, it will be returned.  Otherwise returns None.
       
   109         """
       
   110         # This takes into account the situation where the lookup is a
       
   111         # lookup to a related geographic field, e.g., 'address__point'.
       
   112         field_list = lookup.split(LOOKUP_SEP)
       
   113 
       
   114         # Reversing so list operates like a queue of related lookups,
       
   115         # and popping the top lookup.
       
   116         field_list.reverse()
       
   117         fld_name = field_list.pop()
       
   118 
       
   119         try:
       
   120             geo_fld = opts.get_field(fld_name)
       
   121             # If the field list is still around, then it means that the
       
   122             # lookup was for a geometry field across a relationship --
       
   123             # thus we keep on getting the related model options and the
       
   124             # model field associated with the next field in the list 
       
   125             # until there's no more left.
       
   126             while len(field_list):
       
   127                 opts = geo_fld.rel.to._meta
       
   128                 geo_fld = opts.get_field(field_list.pop())
       
   129         except (FieldDoesNotExist, AttributeError):
       
   130             return False
       
   131 
       
   132         # Finally, make sure we got a Geographic field and return.
       
   133         if isinstance(geo_fld, GeometryField):
       
   134             return geo_fld
       
   135         else:
       
   136             return False