1
0
mirror of https://github.com/django/django.git synced 2025-07-04 01:39:20 +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:
Justin Bronn 2007-09-30 04:19:31 +00:00
parent 53a0514e2b
commit 867a74495f
6 changed files with 82 additions and 57 deletions

View File

@ -8,8 +8,8 @@
(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.
Currently only PostGIS is supported, but someday backends will be added for
additional spatial databases (e.g., Oracle, DB2).
"""
from django.conf import settings
from django.db import connection
@ -20,11 +20,11 @@ from django.utils.datastructures import SortedDict
if settings.DATABASE_ENGINE == 'postgresql_psycopg2':
# PostGIS is the spatial database, getting the rquired modules, renaming as necessary.
from django.contrib.gis.db.backend.postgis import \
PostGISField as GeoBackendField, \
POSTGIS_TERMS as GIS_TERMS, \
create_spatial_db, geo_quotename, get_geo_where_clause
PostGISField as GeoBackendField, POSTGIS_TERMS as GIS_TERMS, \
create_spatial_db, geo_quotename, get_geo_where_clause, \
ASGML, ASKML, UNION
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 ####
# parse_lookup() and lookup_inner() are modified from their django/db/models/query.py

View File

@ -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.field import PostGISField
# Whether PostGIS has AsKML() support.
# Functions used by GeoManager methods, and not via lookup types.
if MAJOR_VERSION == 1:
# AsKML() only supported in versions 1.2.1+
if MINOR_VERSION1 == 3:
ASKML = 'ST_AsKML'
ASGML = 'ST_AsGML'
UNION = 'ST_Union'
elif MINOR_VERSION1 == 2 and MINOR_VERSION2 >= 1:
ASKML = 'AsKML'
ASGML = 'AsGML'
UNION = 'GeomUnion'

View File

@ -2,9 +2,16 @@ from django.conf import settings
from django.core.management import call_command
from django.db import connection
from django.test.utils import _set_autocommit, TEST_DATABASE_PREFIX
from commands import getstatusoutput
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):
"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)
# Checking the status of the command, 0 => execution successful
if status != 0:
raise Exception, "Error executing 'plpgsql' command: %s\n" % output
if status:
raise Exception("Error executing 'plpgsql' command: %s\n" % output)
def _create_with_cursor(db_name, verbosity=1, autoclobber=False):
"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...'
cursor.execute(create_sql)
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')
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.
status, output = getstatusoutput(create_cmd)
if status != 0:
if status:
if created_regex.match(output):
if not autoclobber:
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)
status, output = getstatusoutput(drop_cmd)
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...'
status, output = getstatusoutput(create_cmd)
if status != 0:
raise Exception, 'Could not create database after dropping: %s' % output
raise Exception('Could not create database after dropping: %s' % output)
else:
raise Exception, 'Spatial Database Creation canceled.'
raise Exception('Spatial Database Creation canceled.')
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):
"Creates a spatial database based on the settings."
# Making sure we're using PostgreSQL and psycopg2
if settings.DATABASE_ENGINE != 'postgresql_psycopg2':
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.'
raise Exception('Spatial database creation only supported postgresql_psycopg2 platform.')
# Getting the spatial database name
if test:
@ -104,8 +107,10 @@ def create_spatial_db(test=False, verbosity=1, autoclobber=False, interactive=Fa
db_name = get_spatial_db()
_create_with_shell(db_name, verbosity=verbosity, autoclobber=autoclobber)
# Creating the db language.
create_lang(db_name, verbosity=verbosity)
# 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)
# Now adding in the PostGIS routines.
load_postgis_sql(db_name, verbosity=verbosity)
@ -118,19 +123,15 @@ def create_spatial_db(test=False, verbosity=1, autoclobber=False, interactive=Fa
# Syncing the database
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):
"""
Drops the given database (defaults to what is returned from get_spatial_db().
All exceptions are propagated up to the caller.
Drops the given database (defaults to what is returned from
get_spatial_db()). All exceptions are propagated up to the caller.
"""
if not db_name: db_name = get_spatial_db(test=test)
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):
"Obtains the command-line PostgreSQL connection options for shell commands."
@ -159,7 +160,7 @@ def get_spatial_db(test=False):
return test_db_name
else:
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
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
try:
# 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
except AttributeError:
status, sql_path = getstatusoutput('pg_config --sharedir')
if status != 0:
if status:
sql_path = '/usr/local/share'
# The PostGIS SQL post-creation files.
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):
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):
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)
cmd_fmt = 'psql %s-f "%%s"' % options
# 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
status, output = getstatusoutput(cmd)
if status != 0:
raise Exception, 'Error in loading PostGIS lwgeometry routines.'
if status:
raise Exception('Error in loading PostGIS lwgeometry routines.')
# 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
status, output = getstatusoutput(cmd)
if status !=0:
raise Exception, 'Error in loading PostGIS spatial_ref_sys table.'
if status:
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)

View File

@ -5,7 +5,8 @@ from types import StringType
class PostGISField(Field):
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.
Takes the style object (provides syntax highlighting) and the
@ -98,10 +99,14 @@ class PostGISField(Field):
if isinstance(value, GEOSGeometry):
return value
else:
return ("SRID=%d;%s" % (self._srid, wkt))
raise TypeError('Geometry Proxy should only return GEOSGeometry objects.')
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:
# Adding Transform() to the SQL placeholder.
return 'ST_Transform(%%s, %s)' % self._srid

View File

@ -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.
"""
@ -48,9 +49,6 @@ def postgis_version_tuple():
minor1 = int(m.group('minor1'))
minor2 = int(m.group('minor2'))
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)

View File

@ -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.
# 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):
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
# 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
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.
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)
else:
@ -161,7 +161,7 @@ def get_geo_where_clause(lookup_type, table_prefix, field_name, value):
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)
raise TypeError("Got invalid lookup_type: %s" % repr(lookup_type))
def geo_quotename(value, dbl=False):
"Returns the quotation used for PostGIS on a given value (uses single quotes by default)."