mirror of
https://github.com/django/django.git
synced 2025-07-04 01:39:20 +00:00
gis: created backend module, that will (in the future) allow for support of different spatial databases; improved postgis-specific code in preparation for PostGIS 1.2.2 (and beyond).
git-svn-id: http://code.djangoproject.com/svn/django/branches/gis@5776 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
7a98b6128e
commit
ffcc38ebe8
@ -1,92 +1,29 @@
|
||||
# This module is meant to re-define the helper routines used by the
|
||||
# django.db.models.query objects to be customized for PostGIS.
|
||||
"""
|
||||
This module provides the backend for spatial SQL construction with Django.
|
||||
|
||||
Specifically, this module will import the correct routines and modules
|
||||
needed for GeoDjango
|
||||
|
||||
(1) GeoBackEndField, a base class needed for GeometryField.
|
||||
(2) The parse_lookup() function, used for spatial SQL construction by
|
||||
the GeoQuerySet.
|
||||
|
||||
Currently only PostGIS is supported, but someday backends will be aded for
|
||||
additional spatial databases.
|
||||
"""
|
||||
from django.conf import settings
|
||||
from django.db import backend
|
||||
from django.db.models.query import LOOKUP_SEPARATOR, field_choices, find_field, FieldFound, QUERY_TERMS, get_where_clause
|
||||
from django.utils.datastructures import SortedDict
|
||||
|
||||
# PostGIS-specific operators. The commented descriptions of these
|
||||
# operators come from Section 6.2.2 of the official PostGIS documentation.
|
||||
POSTGIS_OPERATORS = {
|
||||
# The "&<" operator returns true if A's bounding box overlaps or is to the left of B's bounding box.
|
||||
'overlaps_left' : '&< %s',
|
||||
# The "&>" operator returns true if A's bounding box overlaps or is to the right of B's bounding box.
|
||||
'overlaps_right' : '&> %s',
|
||||
# The "<<" operator returns true if A's bounding box is strictly to the left of B's bounding box.
|
||||
'left' : '<< %s',
|
||||
# The ">>" operator returns true if A's bounding box is strictly to the right of B's bounding box.
|
||||
'right' : '>> %s',
|
||||
# The "&<|" operator returns true if A's bounding box overlaps or is below B's bounding box.
|
||||
'overlaps_below' : '&<| %s',
|
||||
# The "|&>" operator returns true if A's bounding box overlaps or is above B's bounding box.
|
||||
'overlaps_above' : '|&> %s',
|
||||
# The "<<|" operator returns true if A's bounding box is strictly below B's bounding box.
|
||||
'strictly_below' : '<<| %s',
|
||||
# The "|>>" operator returns true if A's bounding box is strictly above B's bounding box.
|
||||
'strictly_above' : '|>> %s',
|
||||
# The "~=" operator is the "same as" operator. It tests actual geometric equality of two features. So if
|
||||
# A and B are the same feature, vertex-by-vertex, the operator returns true.
|
||||
'same_as' : '~= %s',
|
||||
'exact' : '~= %s',
|
||||
# The "@" operator returns true if A's bounding box is completely contained by B's bounding box.
|
||||
'contained' : '@ %s',
|
||||
# The "~" operator returns true if A's bounding box completely contains B's bounding box.
|
||||
'bbcontains' : '~ %s',
|
||||
# The "&&" operator is the "overlaps" operator. If A's bounding boux overlaps B's bounding box the
|
||||
# operator returns true.
|
||||
'bboverlaps' : '&& %s',
|
||||
}
|
||||
|
||||
# PostGIS Geometry Functions -- most of these use GEOS.
|
||||
POSTGIS_GEOMETRY_FUNCTIONS = {
|
||||
#'distance' : 'Distance', -- doesn't work right now.
|
||||
'equals' : 'Equals',
|
||||
'disjoint' : 'Disjoint',
|
||||
'touches' : 'Touches',
|
||||
'crosses' : 'Crosses',
|
||||
'within' : 'Within',
|
||||
'overlaps' : 'Overlaps',
|
||||
'contains' : 'Contains',
|
||||
'intersects' : 'Intersects',
|
||||
'relate' : 'Relate',
|
||||
}
|
||||
|
||||
# Any other lookup types that do not require a mapping.
|
||||
MISC_TERMS = ['isnull']
|
||||
|
||||
# The quotation used for postgis (uses single quotes).
|
||||
def quotename(value, dbl=False):
|
||||
if dbl: return '"%s"' % value
|
||||
else: return "'%s'" % value
|
||||
|
||||
# These are the PostGIS-customized QUERY_TERMS, combines both the operators
|
||||
# and the geometry functions.
|
||||
POSTGIS_TERMS = list(POSTGIS_OPERATORS.keys()) # Getting the operators first
|
||||
POSTGIS_TERMS += list(POSTGIS_GEOMETRY_FUNCTIONS.keys()) # Adding on the Geometry Functions
|
||||
POSTGIS_TERMS += MISC_TERMS # Adding any other miscellaneous terms (e.g., 'isnull')
|
||||
POSTGIS_TERMS = tuple(POSTGIS_TERMS) # Making immutable
|
||||
|
||||
def get_geo_where_clause(lookup_type, table_prefix, field_name, value):
|
||||
if table_prefix.endswith('.'):
|
||||
table_prefix = backend.quote_name(table_prefix[:-1])+'.'
|
||||
field_name = backend.quote_name(field_name)
|
||||
|
||||
# See if a PostGIS operator matches the lookup type first
|
||||
try:
|
||||
return '%s%s %s' % (table_prefix, field_name, (POSTGIS_OPERATORS[lookup_type] % '%s'))
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
# See if a PostGIS Geometry function matches the lookup type next
|
||||
try:
|
||||
return '%s(%s%s, %%s)' % (POSTGIS_GEOMETRY_FUNCTIONS[lookup_type], table_prefix, field_name)
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
# For any other lookup type
|
||||
if lookup_type == 'isnull':
|
||||
return "%s%s IS %sNULL" % (table_prefix, field_name, (not value and 'NOT ' or ''))
|
||||
|
||||
raise TypeError, "Got invalid lookup_type: %s" % repr(lookup_type)
|
||||
if settings.DATABASE_ENGINE == 'postgresql_psycopg2':
|
||||
# PostGIS is the spatial database, getting the rquired modules, renaming as necessary.
|
||||
from postgis import \
|
||||
PostGISField as GeoBackendField, \
|
||||
POSTGIS_TERMS as GIS_TERMS, \
|
||||
create_spatial_db, get_geo_where_clause
|
||||
else:
|
||||
raise NotImplementedError, 'No Geographic Backend exists for %s' % settings.DATABASE_NAME
|
||||
|
||||
#### query.py overloaded functions ####
|
||||
# parse_lookup() and lookup_inner() are modified from their django/db/models/query.py
|
||||
@ -130,7 +67,7 @@ def parse_lookup(kwarg_items, opts):
|
||||
if lookup_type == 'pk':
|
||||
lookup_type = 'exact'
|
||||
path.append(None)
|
||||
elif len(path) == 0 or not ((lookup_type in QUERY_TERMS) or (lookup_type in POSTGIS_TERMS)):
|
||||
elif len(path) == 0 or not ((lookup_type in QUERY_TERMS) or (lookup_type in GIS_TERMS)):
|
||||
path.append(lookup_type)
|
||||
lookup_type = 'exact'
|
||||
|
7
django/contrib/gis/db/backend/postgis/__init__.py
Normal file
7
django/contrib/gis/db/backend/postgis/__init__.py
Normal file
@ -0,0 +1,7 @@
|
||||
"""
|
||||
The PostGIS spatial database backend module.
|
||||
"""
|
||||
from query import get_geo_where_clause, GEOM_FUNC_PREFIX, POSTGIS_TERMS
|
||||
from creation import create_spatial_db
|
||||
from field import PostGISField
|
||||
|
107
django/contrib/gis/db/backend/postgis/field.py
Normal file
107
django/contrib/gis/db/backend/postgis/field.py
Normal file
@ -0,0 +1,107 @@
|
||||
from django.db.models.fields import Field # Django base Field class
|
||||
from django.contrib.gis.geos import GEOSGeometry, GEOSException
|
||||
from types import StringType
|
||||
from query import POSTGIS_TERMS, quotename
|
||||
|
||||
class PostGISField(Field):
|
||||
def _add_geom(self, style, db_table):
|
||||
"""Constructs the addition of the geometry to the table using the
|
||||
AddGeometryColumn(...) PostGIS (and OGC standard) stored procedure.
|
||||
|
||||
Takes the style object (provides syntax highlighting) and the
|
||||
database table as parameters.
|
||||
"""
|
||||
sql = style.SQL_KEYWORD('SELECT ') + \
|
||||
style.SQL_TABLE('AddGeometryColumn') + '(' + \
|
||||
style.SQL_TABLE(quotename(db_table)) + ', ' + \
|
||||
style.SQL_FIELD(quotename(self.column)) + ', ' + \
|
||||
style.SQL_FIELD(str(self._srid)) + ', ' + \
|
||||
style.SQL_COLTYPE(quotename(self._geom)) + ', ' + \
|
||||
style.SQL_KEYWORD(str(self._dim)) + ');'
|
||||
|
||||
if not self.null:
|
||||
# Add a NOT NULL constraint to the field
|
||||
sql += '\n' + \
|
||||
style.SQL_KEYWORD('ALTER TABLE ') + \
|
||||
style.SQL_TABLE(quotename(db_table, dbl=True)) + \
|
||||
style.SQL_KEYWORD(' ALTER ') + \
|
||||
style.SQL_FIELD(quotename(self.column, dbl=True)) + \
|
||||
style.SQL_KEYWORD(' SET NOT NULL') + ';'
|
||||
return sql
|
||||
|
||||
def _geom_index(self, style, db_table,
|
||||
index_type='GIST', index_opts='GIST_GEOMETRY_OPS'):
|
||||
"Creates a GiST index for this geometry field."
|
||||
sql = style.SQL_KEYWORD('CREATE INDEX ') + \
|
||||
style.SQL_TABLE(quotename('%s_%s_id' % (db_table, self.column), dbl=True)) + \
|
||||
style.SQL_KEYWORD(' ON ') + \
|
||||
style.SQL_TABLE(quotename(db_table, dbl=True)) + \
|
||||
style.SQL_KEYWORD(' USING ') + \
|
||||
style.SQL_COLTYPE(index_type) + ' ( ' + \
|
||||
style.SQL_FIELD(quotename(self.column, dbl=True)) + ' ' + \
|
||||
style.SQL_KEYWORD(index_opts) + ' );'
|
||||
return sql
|
||||
|
||||
def _post_create_sql(self, style, db_table):
|
||||
"""Returns SQL that will be executed after the model has been
|
||||
created. Geometry columns must be added after creation with the
|
||||
PostGIS AddGeometryColumn() function."""
|
||||
|
||||
# Getting the AddGeometryColumn() SQL necessary to create a PostGIS
|
||||
# geometry field.
|
||||
post_sql = self._add_geom(style, db_table)
|
||||
|
||||
# If the user wants to index this data, then get the indexing SQL as well.
|
||||
if self._index:
|
||||
return '%s\n%s' % (post_sql, self._geom_index(style, db_table))
|
||||
else:
|
||||
return post_sql
|
||||
|
||||
def _post_delete_sql(self, style, db_table):
|
||||
"Drops the geometry column."
|
||||
sql = style.SQL_KEYWORD('SELECT ') + \
|
||||
style.SQL_KEYWORD('DropGeometryColumn') + '(' + \
|
||||
style.SQL_TABLE(quotename(db_table)) + ', ' + \
|
||||
style.SQL_FIELD(quotename(self.column)) + ');'
|
||||
return sql
|
||||
|
||||
def get_db_prep_lookup(self, lookup_type, value):
|
||||
"Returns field's value prepared for database lookup, accepts WKT and GEOS Geometries for the value."
|
||||
if lookup_type in POSTGIS_TERMS:
|
||||
if lookup_type == 'isnull': return [value] # special case for NULL geometries.
|
||||
if not bool(value): return [None] # If invalid value passed in.
|
||||
if isinstance(value, GEOSGeometry):
|
||||
# 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):
|
||||
"Prepares the value for saving in the database."
|
||||
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'
|
36
django/contrib/gis/db/backend/postgis/management.py
Normal file
36
django/contrib/gis/db/backend/postgis/management.py
Normal file
@ -0,0 +1,36 @@
|
||||
"""
|
||||
This utility module is for obtaining information about the PostGIS installation.
|
||||
|
||||
See PostGIS docs at Ch. 6.2.1 for more information on these functions.
|
||||
"""
|
||||
|
||||
def _get_postgis_func(func):
|
||||
"Helper routine for calling PostGIS functions and returning their result."
|
||||
from django.db import connection
|
||||
cursor = connection.cursor()
|
||||
cursor.execute('SELECT %s()' % func)
|
||||
row = cursor.fetchone()
|
||||
cursor.close()
|
||||
return row[0]
|
||||
|
||||
def postgis_geos_version():
|
||||
"Returns the version of the GEOS library used with PostGIS."
|
||||
return _get_postgis_func('postgis_geos_version')
|
||||
|
||||
def postgis_lib_version():
|
||||
"Returns the version number of the PostGIS library used with PostgreSQL."
|
||||
return _get_postgis_func('postgis_lib_version')
|
||||
|
||||
def postgis_proj_version():
|
||||
"Returns the version of the PROJ.4 library used with PostGIS."
|
||||
return _get_postgis_func('postgis_proj_version')
|
||||
|
||||
def postgis_version():
|
||||
"Returns PostGIS version number and compile-time options."
|
||||
return _get_postgis_func('postgis_version')
|
||||
|
||||
def postgis_full_version():
|
||||
"Returns PostGIS version number and compile-time options."
|
||||
return _get_postgis_func('postgis_full_version')
|
||||
|
||||
|
127
django/contrib/gis/db/backend/postgis/query.py
Normal file
127
django/contrib/gis/db/backend/postgis/query.py
Normal file
@ -0,0 +1,127 @@
|
||||
"""
|
||||
This module contains the spatial lookup types, and the get_geo_where_clause()
|
||||
routine for PostGIS.
|
||||
"""
|
||||
from django.db import backend
|
||||
from management import postgis_lib_version
|
||||
|
||||
# Getting the PostGIS version
|
||||
POSTGIS_VERSION = postgis_lib_version()
|
||||
MAJOR_VERSION, MINOR_VERSION1, MINOR_VERSION2 = map(int, POSTGIS_VERSION.split('.'))
|
||||
|
||||
# The supported PostGIS versions.
|
||||
# TODO: Confirm tests with PostGIS versions 1.1.x -- should work. Versions <= 1.0.x didn't use GEOS C API.
|
||||
if MAJOR_VERSION != 1 or MINOR_VERSION1 <= 1:
|
||||
raise Exception, 'PostGIS version %s not supported.' % POSTGIS_VERSION
|
||||
|
||||
# PostGIS-specific operators. The commented descriptions of these
|
||||
# operators come from Section 6.2.2 of the official PostGIS documentation.
|
||||
POSTGIS_OPERATORS = {
|
||||
# The "&<" operator returns true if A's bounding box overlaps or is to the left of B's bounding box.
|
||||
'overlaps_left' : '&<',
|
||||
# The "&>" operator returns true if A's bounding box overlaps or is to the right of B's bounding box.
|
||||
'overlaps_right' : '&>',
|
||||
# The "<<" operator returns true if A's bounding box is strictly to the left of B's bounding box.
|
||||
'left' : '<<',
|
||||
# The ">>" operator returns true if A's bounding box is strictly to the right of B's bounding box.
|
||||
'right' : '>>',
|
||||
# The "&<|" operator returns true if A's bounding box overlaps or is below B's bounding box.
|
||||
'overlaps_below' : '&<|',
|
||||
# The "|&>" operator returns true if A's bounding box overlaps or is above B's bounding box.
|
||||
'overlaps_above' : '|&>',
|
||||
# The "<<|" operator returns true if A's bounding box is strictly below B's bounding box.
|
||||
'strictly_below' : '<<|',
|
||||
# The "|>>" operator returns true if A's bounding box is strictly above B's bounding box.
|
||||
'strictly_above' : '|>>',
|
||||
# The "~=" operator is the "same as" operator. It tests actual geometric equality of two features. So if
|
||||
# A and B are the same feature, vertex-by-vertex, the operator returns true.
|
||||
'same_as' : '~=',
|
||||
'exact' : '~=',
|
||||
# The "@" operator returns true if A's bounding box is completely contained by B's bounding box.
|
||||
'contained' : '@',
|
||||
# The "~" operator returns true if A's bounding box completely contains B's bounding box.
|
||||
'bbcontains' : '~',
|
||||
# The "&&" operator is the "overlaps" operator. If A's bounding boux overlaps B's bounding box the
|
||||
# operator returns true.
|
||||
'bboverlaps' : '&&',
|
||||
}
|
||||
|
||||
# PostGIS Geometry Relationship Functions -- most of these use GEOS.
|
||||
#
|
||||
# For PostGIS >= 1.2.2 these routines will do a bounding box query first before calling
|
||||
# the more expensive GEOS routines (called 'inline index magic').
|
||||
#
|
||||
POSTGIS_GEOMETRY_FUNCTIONS = {
|
||||
'equals' : '%sEquals',
|
||||
'disjoint' : '%sDisjoint',
|
||||
'touches' : '%sTouches',
|
||||
'crosses' : '%sCrosses',
|
||||
'within' : '%sWithin',
|
||||
'overlaps' : '%sOverlaps',
|
||||
'contains' : '%sContains',
|
||||
'intersects' : '%sIntersects',
|
||||
'relate' : ('%sRelate', str),
|
||||
}
|
||||
|
||||
# Versions of PostGIS >= 1.2.2 changed their naming convention to be 'SQL-MM-centric'.
|
||||
# Practically, this means that 'ST_' is appended to geometry function names.
|
||||
if MINOR_VERSION1 >= 2 and MINOR_VERSION2 >= 2:
|
||||
# The ST_DWithin, ST_CoveredBy, and ST_Covers routines become available in 1.2.2.
|
||||
POSTGIS_GEOMETRY_FUNCTIONS.update(
|
||||
{'dwithin' : ('%sDWithin', float),
|
||||
'coveredby' : '%sCoveredBy',
|
||||
'covers' : '%sCovers',
|
||||
}
|
||||
)
|
||||
GEOM_FUNC_PREFIX = 'ST_'
|
||||
else:
|
||||
GEOM_FUNC_PREFIX = ''
|
||||
|
||||
# Updating with the geometry function prefix.
|
||||
for k, v in POSTGIS_GEOMETRY_FUNCTIONS.items():
|
||||
if isinstance(v, tuple):
|
||||
v = list(v)
|
||||
v[0] = v[0] % GEOM_FUNC_PREFIX
|
||||
v = tuple(v)
|
||||
else:
|
||||
v = v % GEOM_FUNC_PREFIX
|
||||
POSTGIS_GEOMETRY_FUNCTIONS[k] = v
|
||||
|
||||
# Any other lookup types that do not require a mapping.
|
||||
MISC_TERMS = ['isnull']
|
||||
|
||||
# The quotation used for postgis (uses single quotes).
|
||||
def quotename(value, dbl=False):
|
||||
if dbl: return '"%s"' % value
|
||||
else: return "'%s'" % value
|
||||
|
||||
# These are the PostGIS-customized QUERY_TERMS -- a list of the lookup types
|
||||
# allowed for geographic queries.
|
||||
POSTGIS_TERMS = list(POSTGIS_OPERATORS.keys()) # Getting the operators first
|
||||
POSTGIS_TERMS += list(POSTGIS_GEOMETRY_FUNCTIONS.keys()) # Adding on the Geometry Functions
|
||||
POSTGIS_TERMS += MISC_TERMS # Adding any other miscellaneous terms (e.g., 'isnull')
|
||||
POSTGIS_TERMS = tuple(POSTGIS_TERMS) # Making immutable
|
||||
|
||||
def get_geo_where_clause(lookup_type, table_prefix, field_name, value):
|
||||
"Returns the SQL WHERE clause for use in PostGIS SQL construction."
|
||||
if table_prefix.endswith('.'):
|
||||
table_prefix = backend.quote_name(table_prefix[:-1])+'.'
|
||||
field_name = backend.quote_name(field_name)
|
||||
|
||||
# See if a PostGIS operator matches the lookup type first
|
||||
try:
|
||||
return '%s%s %s %%s' % (table_prefix, field_name, POSTGIS_OPERATORS[lookup_type])
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
# See if a PostGIS Geometry function matches the lookup type next
|
||||
try:
|
||||
return '%s(%s%s, %%s)' % (POSTGIS_GEOMETRY_FUNCTIONS[lookup_type], table_prefix, field_name)
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
# Handling 'isnull' lookup type
|
||||
if lookup_type == 'isnull':
|
||||
return "%s%s IS %sNULL" % (table_prefix, field_name, (not value and 'NOT ' or ''))
|
||||
|
||||
raise TypeError, "Got invalid lookup_type: %s" % repr(lookup_type)
|
@ -1,15 +1,10 @@
|
||||
# The Django base Field class.
|
||||
from django.db.models.fields import Field
|
||||
from django.contrib.gis.db.backend import GeoBackendField # depends on the spatial database backend.
|
||||
from django.contrib.gis.db.models.proxy import GeometryProxy
|
||||
from django.contrib.gis.db.models.postgis import POSTGIS_TERMS, quotename
|
||||
from django.contrib.gis.oldforms import WKTField
|
||||
from django.utils.functional import curry
|
||||
from django.contrib.gis.geos import GEOSGeometry, GEOSException
|
||||
from types import StringType
|
||||
|
||||
#TODO: Flesh out widgets.
|
||||
|
||||
class GeometryField(Field):
|
||||
class GeometryField(GeoBackendField):
|
||||
"The base GIS field -- maps to the OpenGIS Specification Geometry type."
|
||||
|
||||
# The OpenGIS Geometry name.
|
||||
@ -33,67 +28,6 @@ class GeometryField(Field):
|
||||
self._dim = dim
|
||||
super(GeometryField, self).__init__(**kwargs) # Calling the parent initializtion function
|
||||
|
||||
def _add_geom(self, style, db_table):
|
||||
"""Constructs the addition of the geometry to the table using the
|
||||
AddGeometryColumn(...) PostGIS (and OGC standard) stored procedure.
|
||||
|
||||
Takes the style object (provides syntax highlighting) and the
|
||||
database table as parameters.
|
||||
"""
|
||||
sql = style.SQL_KEYWORD('SELECT ') + \
|
||||
style.SQL_TABLE('AddGeometryColumn') + '(' + \
|
||||
style.SQL_TABLE(quotename(db_table)) + ', ' + \
|
||||
style.SQL_FIELD(quotename(self.column)) + ', ' + \
|
||||
style.SQL_FIELD(str(self._srid)) + ', ' + \
|
||||
style.SQL_COLTYPE(quotename(self._geom)) + ', ' + \
|
||||
style.SQL_KEYWORD(str(self._dim)) + ');'
|
||||
|
||||
if not self.null:
|
||||
# Add a NOT NULL constraint to the field
|
||||
sql += '\n' + \
|
||||
style.SQL_KEYWORD('ALTER TABLE ') + \
|
||||
style.SQL_TABLE(quotename(db_table, dbl=True)) + \
|
||||
style.SQL_KEYWORD(' ALTER ') + \
|
||||
style.SQL_FIELD(quotename(self.column, dbl=True)) + \
|
||||
style.SQL_KEYWORD(' SET NOT NULL') + ';'
|
||||
return sql
|
||||
|
||||
def _geom_index(self, style, db_table,
|
||||
index_type='GIST', index_opts='GIST_GEOMETRY_OPS'):
|
||||
"Creates a GiST index for this geometry field."
|
||||
sql = style.SQL_KEYWORD('CREATE INDEX ') + \
|
||||
style.SQL_TABLE(quotename('%s_%s_id' % (db_table, self.column), dbl=True)) + \
|
||||
style.SQL_KEYWORD(' ON ') + \
|
||||
style.SQL_TABLE(quotename(db_table, dbl=True)) + \
|
||||
style.SQL_KEYWORD(' USING ') + \
|
||||
style.SQL_COLTYPE(index_type) + ' ( ' + \
|
||||
style.SQL_FIELD(quotename(self.column, dbl=True)) + ' ' + \
|
||||
style.SQL_KEYWORD(index_opts) + ' );'
|
||||
return sql
|
||||
|
||||
def _post_create_sql(self, style, db_table):
|
||||
"""Returns SQL that will be executed after the model has been
|
||||
created. Geometry columns must be added after creation with the
|
||||
PostGIS AddGeometryColumn() function."""
|
||||
|
||||
# Getting the AddGeometryColumn() SQL necessary to create a PostGIS
|
||||
# geometry field.
|
||||
post_sql = self._add_geom(style, db_table)
|
||||
|
||||
# If the user wants to index this data, then get the indexing SQL as well.
|
||||
if self._index:
|
||||
return '%s\n%s' % (post_sql, self._geom_index(style, db_table))
|
||||
else:
|
||||
return post_sql
|
||||
|
||||
def _post_delete_sql(self, style, db_table):
|
||||
"Drops the geometry column."
|
||||
sql = style.SQL_KEYWORD('SELECT ') + \
|
||||
style.SQL_KEYWORD('DropGeometryColumn') + '(' + \
|
||||
style.SQL_TABLE(quotename(db_table)) + ', ' + \
|
||||
style.SQL_FIELD(quotename(self.column)) + ');'
|
||||
return sql
|
||||
|
||||
def contribute_to_class(self, cls, name):
|
||||
super(GeometryField, self).contribute_to_class(cls, name)
|
||||
|
||||
@ -112,47 +46,6 @@ class GeometryField(Field):
|
||||
def get_internal_type(self):
|
||||
return "NoField"
|
||||
|
||||
def get_db_prep_lookup(self, lookup_type, value):
|
||||
"Returns field's value prepared for database lookup, accepts WKT and GEOS Geometries for the value."
|
||||
if lookup_type in POSTGIS_TERMS:
|
||||
if lookup_type == 'isnull': return [value] # special case for NULL geometries.
|
||||
if not bool(value): return [None] # If invalid value passed in.
|
||||
if isinstance(value, GEOSGeometry):
|
||||
# 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):
|
||||
"Prepares the value for saving in the database."
|
||||
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):
|
||||
"Using the WKTField (defined above) to be our manipulator."
|
||||
return [WKTField]
|
||||
|
@ -1,7 +1,7 @@
|
||||
from django.db.models.query import Q, QuerySet
|
||||
from django.db import backend
|
||||
from django.contrib.gis.db.models.fields import GeometryField
|
||||
from django.contrib.gis.db.models.postgis import parse_lookup
|
||||
from django.contrib.gis.db.backend import parse_lookup # parse_lookup depends on the spatial database backend.
|
||||
from django.db.models.fields import FieldDoesNotExist
|
||||
import operator
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
from copy import copy
|
||||
from unittest import TestSuite, TextTestRunner
|
||||
from django.contrib.gis.utils import create_spatial_db
|
||||
from django.contrib.gis.db.backend import create_spatial_db
|
||||
from django.db import connection
|
||||
from django.test.utils import destroy_test_db
|
||||
|
||||
|
@ -91,7 +91,7 @@ class GeoModelTest(unittest.TestCase):
|
||||
nmi.save()
|
||||
|
||||
def test005_left_right(self):
|
||||
"Testing the left ('<<') right ('>>') operators."
|
||||
"Testing the 'left' and 'right' lookup types."
|
||||
|
||||
# Left: A << B => true if xmax(A) < xmin(B)
|
||||
# Right: A >> B => true if xmin(A) > xmax(B)
|
||||
|
Loading…
x
Reference in New Issue
Block a user