1
0
mirror of https://github.com/django/django.git synced 2025-07-04 09:49:12 +00:00

gis: geographic queries may now take geos geometry objects as an argument and geometry objects with different srid's are automatically transformed to the srid of the field; synced up parse_lookup() and lookup_inner() to r5609; minor change in GeometryProxy.

git-svn-id: http://code.djangoproject.com/svn/django/branches/gis@5762 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Justin Bronn 2007-07-26 02:18:26 +00:00
parent df45fba3ea
commit 3754b13bec
3 changed files with 64 additions and 15 deletions

View File

@ -5,10 +5,9 @@ from django.contrib.gis.db.models.postgis import POSTGIS_TERMS, quotename
from django.contrib.gis.oldforms import WKTField from django.contrib.gis.oldforms import WKTField
from django.utils.functional import curry from django.utils.functional import curry
from django.contrib.gis.geos import GEOSGeometry, GEOSException from django.contrib.gis.geos import GEOSGeometry, GEOSException
from types import StringType
#TODO: Flesh out widgets. #TODO: Flesh out widgets.
#TODO: GEOS and GDAL/OGR operations through fields as proxy.
#TODO: pythonic usage, like "for point in zip.polygon" and "if point in polygon".
class GeometryField(Field): class GeometryField(Field):
"The base GIS field -- maps to the OpenGIS Specification Geometry type." "The base GIS field -- maps to the OpenGIS Specification Geometry type."
@ -114,15 +113,44 @@ class GeometryField(Field):
return "NoField" return "NoField"
def get_db_prep_lookup(self, lookup_type, value): def get_db_prep_lookup(self, lookup_type, value):
"""Returns field's value prepared for database lookup; the SRID of the geometry is "Returns field's value prepared for database lookup, accepts WKT and GEOS Geometries for the value."
included by default in these queries.""" if not bool(value): return None
if lookup_type in POSTGIS_TERMS: if lookup_type in POSTGIS_TERMS:
return [value and ("SRID=%d;%s" % (self._srid, value)) or None] if isinstance(value, GEOSGeometry):
raise TypeError("Field has invalid lookup: %s" % lookup_type) # GEOSGeometry instance passed in.
if value.srid != self._srid:
# Returning a dictionary instructs the parse_lookup() to add what's in the 'where' key
# to the where parameters, since we need to transform the geometry in the query.
return {'where' : "Transform(%s,%s)",
'params' : [value, self._srid]
}
else:
# Just return the GEOSGeometry, it has its own psycopg2 adaptor.
return [value]
elif isinstance(value, StringType):
# String instance passed in, assuming WKT.
# TODO: Any validation needed here to prevent SQL injection?
return ["SRID=%d;%s" % (self._srid, value)]
else:
raise TypeError("Invalid type (%s) used for field lookup value." % str(type(value)))
else:
raise TypeError("Field has invalid lookup: %s" % lookup_type)
def get_db_prep_save(self, value): def get_db_prep_save(self, value):
"Making sure the SRID is included before saving." "Prepares the value for saving in the database."
return value and ("SRID=%d;%s" % (self._srid, value)) or None if not bool(value): return None
if isinstance(value, GEOSGeometry):
return value
else:
return ("SRID=%d;%s" % (self._srid, wkt))
def get_placeholder(self, value):
"Provides a proper substitution value for "
if isinstance(value, GEOSGeometry) and value.srid != self._srid:
# Adding Transform() to the SQL placeholder.
return 'Transform(%%s, %s)' % self._srid
else:
return '%s'
def get_manipulator_field_objs(self): def get_manipulator_field_objs(self):
"Using the WKTField (defined above) to be our manipulator." "Using the WKTField (defined above) to be our manipulator."

View File

@ -1,7 +1,7 @@
# This module is meant to re-define the helper routines used by the # This module is meant to re-define the helper routines used by the
# django.db.models.query objects to be customized for PostGIS. # django.db.models.query objects to be customized for PostGIS.
from django.db import backend from django.db import backend
from django.db.models.query import LOOKUP_SEPARATOR, find_field, FieldFound, QUERY_TERMS, get_where_clause from django.db.models.query import LOOKUP_SEPARATOR, field_choices, find_field, FieldFound, QUERY_TERMS, get_where_clause
from django.utils.datastructures import SortedDict from django.utils.datastructures import SortedDict
# PostGIS-specific operators. The commented descriptions of these # PostGIS-specific operators. The commented descriptions of these
@ -84,6 +84,12 @@ def get_geo_where_clause(lookup_type, table_prefix, field_name, value):
raise TypeError, "Got invalid lookup_type: %s" % repr(lookup_type) raise TypeError, "Got invalid lookup_type: %s" % repr(lookup_type)
#### query.py overloaded functions ####
# parse_lookup() and lookup_inner() are modified from their django/db/models/query.py
# counterparts to support constructing SQL for geographic queries.
#
# Status: Synced with r5609.
#
def parse_lookup(kwarg_items, opts): def parse_lookup(kwarg_items, opts):
# Helper function that handles converting API kwargs # Helper function that handles converting API kwargs
# (e.g. "name__exact": "tom") to SQL. # (e.g. "name__exact": "tom") to SQL.
@ -117,7 +123,6 @@ def parse_lookup(kwarg_items, opts):
# If there is only one part, or the last part is not a query # If there is only one part, or the last part is not a query
# term, assume that the query is an __exact # term, assume that the query is an __exact
lookup_type = path.pop() lookup_type = path.pop()
if lookup_type == 'pk': if lookup_type == 'pk':
lookup_type = 'exact' lookup_type = 'exact'
path.append(None) path.append(None)
@ -133,6 +138,8 @@ def parse_lookup(kwarg_items, opts):
# all uses of None as a query value. # all uses of None as a query value.
if lookup_type != 'exact': if lookup_type != 'exact':
raise ValueError, "Cannot use None as a query value" raise ValueError, "Cannot use None as a query value"
elif callable(value):
value = value()
joins2, where2, params2 = lookup_inner(path, lookup_type, value, opts, opts.db_table, None) joins2, where2, params2 = lookup_inner(path, lookup_type, value, opts, opts.db_table, None)
joins.update(joins2) joins.update(joins2)
@ -206,7 +213,6 @@ def lookup_inner(path, lookup_type, value, opts, table, column):
# Does the name belong to a one-to-one, many-to-one, or regular field? # Does the name belong to a one-to-one, many-to-one, or regular field?
field = find_field(name, current_opts.fields, False) field = find_field(name, current_opts.fields, False)
if field: if field:
if field.rel: # One-to-One/Many-to-one field if field.rel: # One-to-One/Many-to-one field
new_table = current_table + '__' + name new_table = current_table + '__' + name
@ -225,7 +231,11 @@ def lookup_inner(path, lookup_type, value, opts, table, column):
except FieldFound: # Match found, loop has been shortcut. except FieldFound: # Match found, loop has been shortcut.
pass pass
else: # No match found. else: # No match found.
raise TypeError, "Cannot resolve keyword '%s' into field" % name choices = field_choices(current_opts.many_to_many, False) + \
field_choices(current_opts.get_all_related_many_to_many_objects(), True) + \
field_choices(current_opts.get_all_related_objects(), True) + \
field_choices(current_opts.fields, False)
raise TypeError, "Cannot resolve keyword '%s' into field. Choices are: %s" % (name, ", ".join(choices))
# Check whether an intermediate join is required between current_table # Check whether an intermediate join is required between current_table
# and new_table. # and new_table.
@ -298,9 +308,20 @@ def lookup_inner(path, lookup_type, value, opts, table, column):
# If the field is a geometry field, then the WHERE clause will need to be obtained # If the field is a geometry field, then the WHERE clause will need to be obtained
# with the get_geo_where_clause() # with the get_geo_where_clause()
if hasattr(field, '_geom'): if hasattr(field, '_geom'):
where.append(get_geo_where_clause(lookup_type, current_table + '.', column, value)) # Getting the geographic where clause.
gwc = get_geo_where_clause(lookup_type, current_table + '.', column, value)
# Getting the geographic parameters from the field.
geo_params = field.get_db_prep_lookup(lookup_type, value)
# If a dictionary was passed back from the field modify the where clause.
if isinstance(geo_params, dict):
gwc = gwc % geo_params['where']
geo_params = geo_params['params']
where.append(gwc)
params.extend(geo_params)
else: else:
where.append(get_where_clause(lookup_type, current_table + '.', column, value)) where.append(get_where_clause(lookup_type, current_table + '.', column, value))
params.extend(field.get_db_prep_lookup(lookup_type, value)) params.extend(field.get_db_prep_lookup(lookup_type, value))
return joins, where, params return joins, where, params

View File

@ -24,7 +24,7 @@ class GeometryProxy(object):
def __set__(self, obj, value): def __set__(self, obj, value):
if isinstance(value, GEOSGeometry): if isinstance(value, GEOSGeometry):
if value and ((value.srid is None) and (self._field._srid is not None)): if value and ((value.srid is None) and (self._field._srid is not None)):
value.set_srid(self._field._srid) value.srid = self._field._srid
obj.__dict__[self._field.attname] = value obj.__dict__[self._field.attname] = value
return value return value