mirror of
https://github.com/django/django.git
synced 2025-07-04 09:49:12 +00:00
gis: PostGIS backend improvements: test spatial databases may now be created on NT platforms; changed exception style.
git-svn-id: http://code.djangoproject.com/svn/django/branches/gis@6439 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
53a0514e2b
commit
867a74495f
@ -8,8 +8,8 @@
|
|||||||
(2) The parse_lookup() function, used for spatial SQL construction by
|
(2) The parse_lookup() function, used for spatial SQL construction by
|
||||||
the GeoQuerySet.
|
the GeoQuerySet.
|
||||||
|
|
||||||
Currently only PostGIS is supported, but someday backends will be aded for
|
Currently only PostGIS is supported, but someday backends will be added for
|
||||||
additional spatial databases.
|
additional spatial databases (e.g., Oracle, DB2).
|
||||||
"""
|
"""
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import connection
|
from django.db import connection
|
||||||
@ -20,11 +20,11 @@ from django.utils.datastructures import SortedDict
|
|||||||
if settings.DATABASE_ENGINE == 'postgresql_psycopg2':
|
if settings.DATABASE_ENGINE == 'postgresql_psycopg2':
|
||||||
# PostGIS is the spatial database, getting the rquired modules, renaming as necessary.
|
# PostGIS is the spatial database, getting the rquired modules, renaming as necessary.
|
||||||
from django.contrib.gis.db.backend.postgis import \
|
from django.contrib.gis.db.backend.postgis import \
|
||||||
PostGISField as GeoBackendField, \
|
PostGISField as GeoBackendField, POSTGIS_TERMS as GIS_TERMS, \
|
||||||
POSTGIS_TERMS as GIS_TERMS, \
|
create_spatial_db, geo_quotename, get_geo_where_clause, \
|
||||||
create_spatial_db, geo_quotename, get_geo_where_clause
|
ASGML, ASKML, UNION
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError, 'No Geographic Backend exists for %s' % settings.DATABASE_NAME
|
raise NotImplementedError('No Geographic Backend exists for %s' % settings.DATABASE_NAME)
|
||||||
|
|
||||||
#### query.py overloaded functions ####
|
#### query.py overloaded functions ####
|
||||||
# parse_lookup() and lookup_inner() are modified from their django/db/models/query.py
|
# parse_lookup() and lookup_inner() are modified from their django/db/models/query.py
|
||||||
|
@ -8,11 +8,16 @@ from django.contrib.gis.db.backend.postgis.query import \
|
|||||||
from django.contrib.gis.db.backend.postgis.creation import create_spatial_db
|
from django.contrib.gis.db.backend.postgis.creation import create_spatial_db
|
||||||
from django.contrib.gis.db.backend.postgis.field import PostGISField
|
from django.contrib.gis.db.backend.postgis.field import PostGISField
|
||||||
|
|
||||||
# Whether PostGIS has AsKML() support.
|
# Functions used by GeoManager methods, and not via lookup types.
|
||||||
if MAJOR_VERSION == 1:
|
if MAJOR_VERSION == 1:
|
||||||
# AsKML() only supported in versions 1.2.1+
|
|
||||||
if MINOR_VERSION1 == 3:
|
if MINOR_VERSION1 == 3:
|
||||||
ASKML = 'ST_AsKML'
|
ASKML = 'ST_AsKML'
|
||||||
|
ASGML = 'ST_AsGML'
|
||||||
|
UNION = 'ST_Union'
|
||||||
elif MINOR_VERSION1 == 2 and MINOR_VERSION2 >= 1:
|
elif MINOR_VERSION1 == 2 and MINOR_VERSION2 >= 1:
|
||||||
ASKML = 'AsKML'
|
ASKML = 'AsKML'
|
||||||
|
ASGML = 'AsGML'
|
||||||
|
UNION = 'GeomUnion'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -2,9 +2,16 @@ from django.conf import settings
|
|||||||
from django.core.management import call_command
|
from django.core.management import call_command
|
||||||
from django.db import connection
|
from django.db import connection
|
||||||
from django.test.utils import _set_autocommit, TEST_DATABASE_PREFIX
|
from django.test.utils import _set_autocommit, TEST_DATABASE_PREFIX
|
||||||
from commands import getstatusoutput
|
|
||||||
import os, re, sys
|
import os, re, sys
|
||||||
|
|
||||||
|
def getstatusoutput(cmd):
|
||||||
|
"A simpler version of getstatusoutput that works on win32 platforms."
|
||||||
|
stdin, stdout, stderr = os.popen3(cmd)
|
||||||
|
output = stdout.read()
|
||||||
|
if output.endswith('\n'): output = output[:-1]
|
||||||
|
status = stdin.close()
|
||||||
|
return status, output
|
||||||
|
|
||||||
def create_lang(db_name, verbosity=1):
|
def create_lang(db_name, verbosity=1):
|
||||||
"Sets up the pl/pgsql language on the given database."
|
"Sets up the pl/pgsql language on the given database."
|
||||||
|
|
||||||
@ -20,8 +27,8 @@ def create_lang(db_name, verbosity=1):
|
|||||||
status, output = getstatusoutput(createlang_cmd)
|
status, output = getstatusoutput(createlang_cmd)
|
||||||
|
|
||||||
# Checking the status of the command, 0 => execution successful
|
# Checking the status of the command, 0 => execution successful
|
||||||
if status != 0:
|
if status:
|
||||||
raise Exception, "Error executing 'plpgsql' command: %s\n" % output
|
raise Exception("Error executing 'plpgsql' command: %s\n" % output)
|
||||||
|
|
||||||
def _create_with_cursor(db_name, verbosity=1, autoclobber=False):
|
def _create_with_cursor(db_name, verbosity=1, autoclobber=False):
|
||||||
"Creates database with psycopg2 cursor."
|
"Creates database with psycopg2 cursor."
|
||||||
@ -48,7 +55,7 @@ def _create_with_cursor(db_name, verbosity=1, autoclobber=False):
|
|||||||
if verbosity >= 1: print 'Creating new spatial database...'
|
if verbosity >= 1: print 'Creating new spatial database...'
|
||||||
cursor.execute(create_sql)
|
cursor.execute(create_sql)
|
||||||
else:
|
else:
|
||||||
raise Exception, 'Spatial Database Creation canceled.'
|
raise Exception('Spatial Database Creation canceled.')
|
||||||
|
|
||||||
created_regex = re.compile(r'^createdb: database creation failed: ERROR: database ".+" already exists')
|
created_regex = re.compile(r'^createdb: database creation failed: ERROR: database ".+" already exists')
|
||||||
def _create_with_shell(db_name, verbosity=1, autoclobber=False):
|
def _create_with_shell(db_name, verbosity=1, autoclobber=False):
|
||||||
@ -65,7 +72,8 @@ def _create_with_shell(db_name, verbosity=1, autoclobber=False):
|
|||||||
|
|
||||||
# Attempting to create the database.
|
# Attempting to create the database.
|
||||||
status, output = getstatusoutput(create_cmd)
|
status, output = getstatusoutput(create_cmd)
|
||||||
if status != 0:
|
|
||||||
|
if status:
|
||||||
if created_regex.match(output):
|
if created_regex.match(output):
|
||||||
if not autoclobber:
|
if not autoclobber:
|
||||||
confirm = raw_input("\nIt appears the database, %s, already exists. Type 'yes' to delete it, or 'no' to cancel: " % db_name)
|
confirm = raw_input("\nIt appears the database, %s, already exists. Type 'yes' to delete it, or 'no' to cancel: " % db_name)
|
||||||
@ -74,27 +82,22 @@ def _create_with_shell(db_name, verbosity=1, autoclobber=False):
|
|||||||
drop_cmd = 'dropdb %s%s' % (options, db_name)
|
drop_cmd = 'dropdb %s%s' % (options, db_name)
|
||||||
status, output = getstatusoutput(drop_cmd)
|
status, output = getstatusoutput(drop_cmd)
|
||||||
if status != 0:
|
if status != 0:
|
||||||
raise Exception, 'Could not drop database %s: %s' % (db_name, output)
|
raise Exception('Could not drop database %s: %s' % (db_name, output))
|
||||||
if verbosity >= 1: print 'Creating new spatial database...'
|
if verbosity >= 1: print 'Creating new spatial database...'
|
||||||
status, output = getstatusoutput(create_cmd)
|
status, output = getstatusoutput(create_cmd)
|
||||||
if status != 0:
|
if status != 0:
|
||||||
raise Exception, 'Could not create database after dropping: %s' % output
|
raise Exception('Could not create database after dropping: %s' % output)
|
||||||
else:
|
else:
|
||||||
raise Exception, 'Spatial Database Creation canceled.'
|
raise Exception('Spatial Database Creation canceled.')
|
||||||
else:
|
else:
|
||||||
raise Exception, 'Unknown error occurred in creating database: %s' % output
|
raise Exception('Unknown error occurred in creating database: %s' % output)
|
||||||
|
|
||||||
def create_spatial_db(test=False, verbosity=1, autoclobber=False, interactive=False):
|
def create_spatial_db(test=False, verbosity=1, autoclobber=False, interactive=False):
|
||||||
"Creates a spatial database based on the settings."
|
"Creates a spatial database based on the settings."
|
||||||
|
|
||||||
# Making sure we're using PostgreSQL and psycopg2
|
# Making sure we're using PostgreSQL and psycopg2
|
||||||
if settings.DATABASE_ENGINE != 'postgresql_psycopg2':
|
if settings.DATABASE_ENGINE != 'postgresql_psycopg2':
|
||||||
raise Exception, 'Spatial database creation only supported postgresql_psycopg2 platform.'
|
raise Exception('Spatial database creation only supported postgresql_psycopg2 platform.')
|
||||||
|
|
||||||
# This routine depends on getstatusoutput(), which does not work on Windows.
|
|
||||||
# TODO: Consider executing shell commands with popen for Windows compatibility
|
|
||||||
if os.name == 'nt':
|
|
||||||
raise Exception, 'Automatic spatial database creation only supported on *NIX platforms.'
|
|
||||||
|
|
||||||
# Getting the spatial database name
|
# Getting the spatial database name
|
||||||
if test:
|
if test:
|
||||||
@ -104,7 +107,9 @@ def create_spatial_db(test=False, verbosity=1, autoclobber=False, interactive=Fa
|
|||||||
db_name = get_spatial_db()
|
db_name = get_spatial_db()
|
||||||
_create_with_shell(db_name, verbosity=verbosity, autoclobber=autoclobber)
|
_create_with_shell(db_name, verbosity=verbosity, autoclobber=autoclobber)
|
||||||
|
|
||||||
# Creating the db language.
|
# Creating the db language, does not need to be done on NT platforms
|
||||||
|
# since the PostGIS installer enables this capability.
|
||||||
|
if os.name != 'nt':
|
||||||
create_lang(db_name, verbosity=verbosity)
|
create_lang(db_name, verbosity=verbosity)
|
||||||
|
|
||||||
# Now adding in the PostGIS routines.
|
# Now adding in the PostGIS routines.
|
||||||
@ -119,18 +124,14 @@ def create_spatial_db(test=False, verbosity=1, autoclobber=False, interactive=Fa
|
|||||||
# Syncing the database
|
# Syncing the database
|
||||||
call_command('syncdb', verbosity=verbosity, interactive=interactive)
|
call_command('syncdb', verbosity=verbosity, interactive=interactive)
|
||||||
|
|
||||||
# Get a cursor (even though we don't need one yet). This has
|
|
||||||
# the side effect of initializing the test database.
|
|
||||||
cursor = connection.cursor()
|
|
||||||
|
|
||||||
def drop_db(db_name=False, test=False):
|
def drop_db(db_name=False, test=False):
|
||||||
"""
|
"""
|
||||||
Drops the given database (defaults to what is returned from get_spatial_db().
|
Drops the given database (defaults to what is returned from
|
||||||
All exceptions are propagated up to the caller.
|
get_spatial_db()). All exceptions are propagated up to the caller.
|
||||||
"""
|
"""
|
||||||
if not db_name: db_name = get_spatial_db(test=test)
|
if not db_name: db_name = get_spatial_db(test=test)
|
||||||
cursor = connection.cursor()
|
cursor = connection.cursor()
|
||||||
cursor.execute("DROP DATABASE %s" % connection.ops.quote_name(db_name))
|
cursor.execute('DROP DATABASE %s' % connection.ops.quote_name(db_name))
|
||||||
|
|
||||||
def get_cmd_options(db_name):
|
def get_cmd_options(db_name):
|
||||||
"Obtains the command-line PostgreSQL connection options for shell commands."
|
"Obtains the command-line PostgreSQL connection options for shell commands."
|
||||||
@ -159,7 +160,7 @@ def get_spatial_db(test=False):
|
|||||||
return test_db_name
|
return test_db_name
|
||||||
else:
|
else:
|
||||||
if not settings.DATABASE_NAME:
|
if not settings.DATABASE_NAME:
|
||||||
raise Exception, 'must configure DATABASE_NAME in settings.py'
|
raise Exception('must configure DATABASE_NAME in settings.py')
|
||||||
return settings.DATABASE_NAME
|
return settings.DATABASE_NAME
|
||||||
|
|
||||||
def load_postgis_sql(db_name, verbosity=1):
|
def load_postgis_sql(db_name, verbosity=1):
|
||||||
@ -171,35 +172,51 @@ def load_postgis_sql(db_name, verbosity=1):
|
|||||||
# Getting the path to the PostGIS SQL
|
# Getting the path to the PostGIS SQL
|
||||||
try:
|
try:
|
||||||
# POSTGIS_SQL_PATH may be placed in settings to tell GeoDjango where the
|
# POSTGIS_SQL_PATH may be placed in settings to tell GeoDjango where the
|
||||||
# PostGIS SQL files are located
|
# PostGIS SQL files are located. This is especially useful on Win32
|
||||||
|
# platforms since the output of pg_config looks like "C:/PROGRA~1/..".
|
||||||
sql_path = settings.POSTGIS_SQL_PATH
|
sql_path = settings.POSTGIS_SQL_PATH
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
status, sql_path = getstatusoutput('pg_config --sharedir')
|
status, sql_path = getstatusoutput('pg_config --sharedir')
|
||||||
if status != 0:
|
if status:
|
||||||
sql_path = '/usr/local/share'
|
sql_path = '/usr/local/share'
|
||||||
|
|
||||||
# The PostGIS SQL post-creation files.
|
# The PostGIS SQL post-creation files.
|
||||||
lwpostgis_file = os.path.join(sql_path, 'lwpostgis.sql')
|
lwpostgis_file = os.path.join(sql_path, 'lwpostgis.sql')
|
||||||
srefsys_file = os.path.join(sql_path, 'spatial_ref_sys.sql')
|
srefsys_file = os.path.join(sql_path, 'spatial_ref_sys.sql')
|
||||||
if not os.path.isfile(lwpostgis_file):
|
if not os.path.isfile(lwpostgis_file):
|
||||||
raise Exception, 'Could not find PostGIS function definitions in %s' % lwpostgis_file
|
raise Exception('Could not find PostGIS function definitions in %s' % lwpostgis_file)
|
||||||
if not os.path.isfile(srefsys_file):
|
if not os.path.isfile(srefsys_file):
|
||||||
raise Exception, 'Could not find PostGIS spatial reference system definitions in %s' % srefsys_file
|
raise Exception('Could not find PostGIS spatial reference system definitions in %s' % srefsys_file)
|
||||||
|
|
||||||
# Getting the psql command-line options.
|
# Getting the psql command-line options, and command format.
|
||||||
options = get_cmd_options(db_name)
|
options = get_cmd_options(db_name)
|
||||||
|
cmd_fmt = 'psql %s-f "%%s"' % options
|
||||||
|
|
||||||
# Now trying to load up the PostGIS functions
|
# Now trying to load up the PostGIS functions
|
||||||
cmd = 'psql %s-f %s' % (options, lwpostgis_file)
|
cmd = cmd_fmt % lwpostgis_file
|
||||||
if verbosity >= 1: print cmd
|
if verbosity >= 1: print cmd
|
||||||
status, output = getstatusoutput(cmd)
|
status, output = getstatusoutput(cmd)
|
||||||
if status != 0:
|
if status:
|
||||||
raise Exception, 'Error in loading PostGIS lwgeometry routines.'
|
raise Exception('Error in loading PostGIS lwgeometry routines.')
|
||||||
|
|
||||||
# Now trying to load up the Spatial Reference System table
|
# Now trying to load up the Spatial Reference System table
|
||||||
cmd = 'psql %s-f %s' % (options, srefsys_file)
|
cmd = cmd_fmt % srefsys_file
|
||||||
if verbosity >= 1: print cmd
|
if verbosity >= 1: print cmd
|
||||||
status, output = getstatusoutput(cmd)
|
status, output = getstatusoutput(cmd)
|
||||||
if status !=0:
|
if status:
|
||||||
raise Exception, 'Error in loading PostGIS spatial_ref_sys table.'
|
raise Exception('Error in loading PostGIS spatial_ref_sys table.')
|
||||||
|
|
||||||
|
# Setting the permissions because on Windows platforms the owner
|
||||||
|
# of the spatial_ref_sys and geometry_columns tables is always
|
||||||
|
# the postgres user, regardless of how the db is created.
|
||||||
|
if os.name == 'nt': set_permissions(db_name)
|
||||||
|
|
||||||
|
def set_permissions(db_name):
|
||||||
|
"""
|
||||||
|
Sets the permissions on the given database to that of the user specified
|
||||||
|
in the settings. Needed specifically for PostGIS on Win32 platforms.
|
||||||
|
"""
|
||||||
|
cursor = connection.cursor()
|
||||||
|
user = settings.DATABASE_USER
|
||||||
|
cursor.execute('ALTER TABLE geometry_columns OWNER TO %s' % user)
|
||||||
|
cursor.execute('ALTER TABLE spatial_ref_sys OWNER TO %s' % user)
|
||||||
|
@ -5,7 +5,8 @@ from types import StringType
|
|||||||
|
|
||||||
class PostGISField(Field):
|
class PostGISField(Field):
|
||||||
def _add_geom(self, style, db_table):
|
def _add_geom(self, style, db_table):
|
||||||
"""Constructs the addition of the geometry to the table using the
|
"""
|
||||||
|
Constructs the addition of the geometry to the table using the
|
||||||
AddGeometryColumn(...) PostGIS (and OGC standard) stored procedure.
|
AddGeometryColumn(...) PostGIS (and OGC standard) stored procedure.
|
||||||
|
|
||||||
Takes the style object (provides syntax highlighting) and the
|
Takes the style object (provides syntax highlighting) and the
|
||||||
@ -98,10 +99,14 @@ class PostGISField(Field):
|
|||||||
if isinstance(value, GEOSGeometry):
|
if isinstance(value, GEOSGeometry):
|
||||||
return value
|
return value
|
||||||
else:
|
else:
|
||||||
return ("SRID=%d;%s" % (self._srid, wkt))
|
raise TypeError('Geometry Proxy should only return GEOSGeometry objects.')
|
||||||
|
|
||||||
def get_placeholder(self, value):
|
def get_placeholder(self, value):
|
||||||
"Provides a proper substitution value for "
|
"""
|
||||||
|
Provides a proper substitution value for Geometries that are not in the
|
||||||
|
SRID of the field. Specifically, this routine will substitute in the
|
||||||
|
ST_Transform() function call.
|
||||||
|
"""
|
||||||
if isinstance(value, GEOSGeometry) and value.srid != self._srid:
|
if isinstance(value, GEOSGeometry) and value.srid != self._srid:
|
||||||
# Adding Transform() to the SQL placeholder.
|
# Adding Transform() to the SQL placeholder.
|
||||||
return 'ST_Transform(%%s, %s)' % self._srid
|
return 'ST_Transform(%%s, %s)' % self._srid
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
"""
|
"""
|
||||||
This utility module is for obtaining information about the PostGIS installation.
|
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.
|
See PostGIS docs at Ch. 6.2.1 for more information on these functions.
|
||||||
"""
|
"""
|
||||||
@ -48,9 +49,6 @@ def postgis_version_tuple():
|
|||||||
minor1 = int(m.group('minor1'))
|
minor1 = int(m.group('minor1'))
|
||||||
minor2 = int(m.group('minor2'))
|
minor2 = int(m.group('minor2'))
|
||||||
else:
|
else:
|
||||||
raise Exception, 'Could not parse PostGIS version string: %s' % version
|
raise Exception('Could not parse PostGIS version string: %s' % version)
|
||||||
|
|
||||||
return (version, major, minor1, minor2)
|
return (version, major, minor1, minor2)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ POSTGIS_VERSION, MAJOR_VERSION, MINOR_VERSION1, MINOR_VERSION2 = postgis_version
|
|||||||
# TODO: Confirm tests with PostGIS versions 1.1.x -- should work.
|
# TODO: Confirm tests with PostGIS versions 1.1.x -- should work.
|
||||||
# Versions <= 1.0.x do not use GEOS C API, and will not be supported.
|
# Versions <= 1.0.x do not use GEOS C API, and will not be supported.
|
||||||
if MAJOR_VERSION != 1 or (MAJOR_VERSION == 1 and MINOR_VERSION1 < 1):
|
if MAJOR_VERSION != 1 or (MAJOR_VERSION == 1 and MINOR_VERSION1 < 1):
|
||||||
raise Exception, 'PostGIS version %s not supported.' % POSTGIS_VERSION
|
raise Exception('PostGIS version %s not supported.' % POSTGIS_VERSION)
|
||||||
|
|
||||||
# PostGIS-specific operators. The commented descriptions of these
|
# PostGIS-specific operators. The commented descriptions of these
|
||||||
# operators come from Section 6.2.2 of the official PostGIS documentation.
|
# operators come from Section 6.2.2 of the official PostGIS documentation.
|
||||||
@ -145,11 +145,11 @@ def get_geo_where_clause(lookup_type, table_prefix, field_name, value):
|
|||||||
|
|
||||||
# Ensuring that a tuple _value_ was passed in from the user
|
# Ensuring that a tuple _value_ was passed in from the user
|
||||||
if not isinstance(value, tuple) or len(value) != 2:
|
if not isinstance(value, tuple) or len(value) != 2:
|
||||||
raise TypeError, '2-element tuple required for %s lookup type.' % lookup_type
|
raise TypeError('2-element tuple required for %s lookup type.' % lookup_type)
|
||||||
|
|
||||||
# Ensuring the argument type matches what we expect.
|
# Ensuring the argument type matches what we expect.
|
||||||
if not isinstance(value[1], arg_type):
|
if not isinstance(value[1], arg_type):
|
||||||
raise TypeError, 'Argument type should be %s, got %s instead.' % (arg_type, type(value[1]))
|
raise TypeError('Argument type should be %s, got %s instead.' % (arg_type, type(value[1])))
|
||||||
|
|
||||||
return "%s(%s%s, %%s, %%s)" % (func, table_prefix, field_name)
|
return "%s(%s%s, %%s, %%s)" % (func, table_prefix, field_name)
|
||||||
else:
|
else:
|
||||||
@ -161,7 +161,7 @@ def get_geo_where_clause(lookup_type, table_prefix, field_name, value):
|
|||||||
if lookup_type == 'isnull':
|
if lookup_type == 'isnull':
|
||||||
return "%s%s IS %sNULL" % (table_prefix, field_name, (not value and 'NOT ' or ''))
|
return "%s%s IS %sNULL" % (table_prefix, field_name, (not value and 'NOT ' or ''))
|
||||||
|
|
||||||
raise TypeError, "Got invalid lookup_type: %s" % repr(lookup_type)
|
raise TypeError("Got invalid lookup_type: %s" % repr(lookup_type))
|
||||||
|
|
||||||
def geo_quotename(value, dbl=False):
|
def geo_quotename(value, dbl=False):
|
||||||
"Returns the quotation used for PostGIS on a given value (uses single quotes by default)."
|
"Returns the quotation used for PostGIS on a given value (uses single quotes by default)."
|
||||||
|
Loading…
x
Reference in New Issue
Block a user