where.py 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
  1. from django.db.models.constants import LOOKUP_SEP
  2. from django.db.models.fields import FieldDoesNotExist
  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. class GeoConstraint(Constraint):
  7. """
  8. This subclass overrides `process` to better handle geographic SQL
  9. construction.
  10. """
  11. def __init__(self, init_constraint):
  12. self.alias = init_constraint.alias
  13. self.col = init_constraint.col
  14. self.field = init_constraint.field
  15. def process(self, lookup_type, value, connection):
  16. if isinstance(value, SQLEvaluator):
  17. # Make sure the F Expression destination field exists, and
  18. # set an `srid` attribute with the same as that of the
  19. # destination.
  20. geo_fld = GeoWhereNode._check_geo_field(value.opts, value.expression.name)
  21. if not geo_fld:
  22. raise ValueError('No geographic field found in expression.')
  23. value.srid = geo_fld.srid
  24. db_type = self.field.db_type(connection=connection)
  25. params = self.field.get_db_prep_lookup(lookup_type, value, connection=connection)
  26. return (self.alias, self.col, db_type), params
  27. class GeoWhereNode(WhereNode):
  28. """
  29. Used to represent the SQL where-clause for spatial databases --
  30. these are tied to the GeoQuery class that created it.
  31. """
  32. def _prepare_data(self, data):
  33. if isinstance(data, (list, tuple)):
  34. obj, lookup_type, value = data
  35. if (isinstance(obj, Constraint) and
  36. isinstance(obj.field, GeometryField)):
  37. data = (GeoConstraint(obj), lookup_type, value)
  38. return super(GeoWhereNode, self)._prepare_data(data)
  39. def make_atom(self, child, qn, connection):
  40. lvalue, lookup_type, value_annot, params_or_value = child
  41. if isinstance(lvalue, GeoConstraint):
  42. data, params = lvalue.process(lookup_type, params_or_value, connection)
  43. spatial_sql, spatial_params = connection.ops.spatial_lookup_sql(
  44. data, lookup_type, params_or_value, lvalue.field, qn)
  45. return spatial_sql, spatial_params + params
  46. else:
  47. return super(GeoWhereNode, self).make_atom(child, qn, connection)
  48. @classmethod
  49. def _check_geo_field(cls, opts, lookup):
  50. """
  51. Utility for checking the given lookup with the given model options.
  52. The lookup is a string either specifying the geographic field, e.g.
  53. 'point, 'the_geom', or a related lookup on a geographic field like
  54. 'address__point'.
  55. If a GeometryField exists according to the given lookup on the model
  56. options, it will be returned. Otherwise returns None.
  57. """
  58. # This takes into account the situation where the lookup is a
  59. # lookup to a related geographic field, e.g., 'address__point'.
  60. field_list = lookup.split(LOOKUP_SEP)
  61. # Reversing so list operates like a queue of related lookups,
  62. # and popping the top lookup.
  63. field_list.reverse()
  64. fld_name = field_list.pop()
  65. try:
  66. geo_fld = opts.get_field(fld_name)
  67. # If the field list is still around, then it means that the
  68. # lookup was for a geometry field across a relationship --
  69. # thus we keep on getting the related model options and the
  70. # model field associated with the next field in the list
  71. # until there's no more left.
  72. while len(field_list):
  73. opts = geo_fld.rel.to._meta
  74. geo_fld = opts.get_field(field_list.pop())
  75. except (FieldDoesNotExist, AttributeError):
  76. return False
  77. # Finally, make sure we got a Geographic field and return.
  78. if isinstance(geo_fld, GeometryField):
  79. return geo_fld
  80. else:
  81. return False