/django/contrib/gis/db/models/sql/where.py
Python | 89 lines | 65 code | 7 blank | 17 comment | 9 complexity | 1aa4846fe64700a94cca0806b49d0948 MD5 | raw file
Possible License(s): BSD-3-Clause
1from django.db.models.fields import Field, FieldDoesNotExist 2from django.db.models.sql.constants import LOOKUP_SEP 3from django.db.models.sql.expressions import SQLEvaluator 4from django.db.models.sql.where import Constraint, WhereNode 5from django.contrib.gis.db.models.fields import GeometryField 6 7class 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 30class 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