|
0
|
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 |
|
29
|
4 |
from django.db.models.sql.where import Constraint, WhereNode |
|
0
|
5 |
from django.contrib.gis.db.models.fields import GeometryField |
|
|
6 |
|
|
29
|
7 |
class GeoConstraint(Constraint): |
|
|
8 |
""" |
|
|
9 |
This subclass overrides `process` to better handle geographic SQL |
|
|
10 |
construction. |
|
0
|
11 |
""" |
|
29
|
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 |
|
0
|
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): |
|
29
|
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) |
|
0
|
42 |
|
|
29
|
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 |
|
0
|
49 |
else: |
|
29
|
50 |
return super(GeoWhereNode, self).make_atom(child, qn, connection) |
|
0
|
51 |
|
|
|
52 |
@classmethod |
|
|
53 |
def _check_geo_field(cls, opts, lookup): |
|
|
54 |
""" |
|
29
|
55 |
Utility for checking the given lookup with the given model options. |
|
0
|
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 |
|
29
|
77 |
# model field associated with the next field in the list |
|
0
|
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 |