web/lib/django/contrib/gis/db/models/sql/where.py
changeset 38 77b6da96e6f1
equal deleted inserted replaced
37:8d941af65caf 38:77b6da96e6f1
       
     1 from django.db.models.fields import Field, FieldDoesNotExist
       
     2 from django.db.models.sql.constants import LOOKUP_SEP
       
     3 from django.db.models.sql.expressions import SQLEvaluator
       
     4 from django.db.models.sql.where import Constraint, WhereNode
       
     5 from django.contrib.gis.db.models.fields import GeometryField
       
     6 
       
     7 class GeoConstraint(Constraint):
       
     8     """
       
     9     This subclass overrides `process` to better handle geographic SQL
       
    10     construction.
       
    11     """
       
    12     def __init__(self, init_constraint):
       
    13         self.alias = init_constraint.alias
       
    14         self.col = init_constraint.col
       
    15         self.field = init_constraint.field
       
    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
       
    29 
       
    30 class GeoWhereNode(WhereNode):
       
    31     """
       
    32     Used to represent the SQL where-clause for spatial databases --
       
    33     these are tied to the GeoQuery class that created it.
       
    34     """
       
    35     def add(self, data, connector):
       
    36         if isinstance(data, (list, tuple)):
       
    37             obj, lookup_type, value = data
       
    38             if ( isinstance(obj, Constraint) and
       
    39                  isinstance(obj.field, GeometryField) ):
       
    40                 data = (GeoConstraint(obj), lookup_type, value)
       
    41         super(GeoWhereNode, self).add(data, connector)
       
    42 
       
    43     def make_atom(self, child, qn, connection):
       
    44         lvalue, lookup_type, value_annot, params_or_value = child
       
    45         if isinstance(lvalue, GeoConstraint):
       
    46             data, params = lvalue.process(lookup_type, params_or_value, connection)
       
    47             spatial_sql = connection.ops.spatial_lookup_sql(data, lookup_type, params_or_value, lvalue.field, qn)
       
    48             return spatial_sql, params
       
    49         else:
       
    50             return super(GeoWhereNode, self).make_atom(child, qn, connection)
       
    51 
       
    52     @classmethod
       
    53     def _check_geo_field(cls, opts, lookup):
       
    54         """
       
    55         Utility for checking the given lookup with the given model options.
       
    56         The lookup is a string either specifying the geographic field, e.g.
       
    57         'point, 'the_geom', or a related lookup on a geographic field like
       
    58         'address__point'.
       
    59 
       
    60         If a GeometryField exists according to the given lookup on the model
       
    61         options, it will be returned.  Otherwise returns None.
       
    62         """
       
    63         # This takes into account the situation where the lookup is a
       
    64         # lookup to a related geographic field, e.g., 'address__point'.
       
    65         field_list = lookup.split(LOOKUP_SEP)
       
    66 
       
    67         # Reversing so list operates like a queue of related lookups,
       
    68         # and popping the top lookup.
       
    69         field_list.reverse()
       
    70         fld_name = field_list.pop()
       
    71 
       
    72         try:
       
    73             geo_fld = opts.get_field(fld_name)
       
    74             # If the field list is still around, then it means that the
       
    75             # lookup was for a geometry field across a relationship --
       
    76             # thus we keep on getting the related model options and the
       
    77             # model field associated with the next field in the list
       
    78             # until there's no more left.
       
    79             while len(field_list):
       
    80                 opts = geo_fld.rel.to._meta
       
    81                 geo_fld = opts.get_field(field_list.pop())
       
    82         except (FieldDoesNotExist, AttributeError):
       
    83             return False
       
    84 
       
    85         # Finally, make sure we got a Geographic field and return.
       
    86         if isinstance(geo_fld, GeometryField):
       
    87             return geo_fld
       
    88         else:
       
    89             return False