From 867a74495f33b0434c796ef94efdb3093fe101db Mon Sep 17 00:00:00 2001 From: Justin Bronn Date: Sun, 30 Sep 2007 04:19:31 +0000 Subject: [PATCH] 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 --- django/contrib/gis/db/backend/__init__.py | 12 +-- .../gis/db/backend/postgis/__init__.py | 9 +- .../gis/db/backend/postgis/creation.py | 91 +++++++++++-------- .../contrib/gis/db/backend/postgis/field.py | 11 ++- .../gis/db/backend/postgis/management.py | 8 +- .../contrib/gis/db/backend/postgis/query.py | 8 +- 6 files changed, 82 insertions(+), 57 deletions(-) diff --git a/django/contrib/gis/db/backend/__init__.py b/django/contrib/gis/db/backend/__init__.py index 50f7fbc34c..651a1976c5 100644 --- a/django/contrib/gis/db/backend/__init__.py +++ b/django/contrib/gis/db/backend/__init__.py @@ -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 diff --git a/django/contrib/gis/db/backend/postgis/__init__.py b/django/contrib/gis/db/backend/postgis/__init__.py index 5f2e8f06a1..33ef9a43fa 100644 --- a/django/contrib/gis/db/backend/postgis/__init__.py +++ b/django/contrib/gis/db/backend/postgis/__init__.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.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' + + diff --git a/django/contrib/gis/db/backend/postgis/creation.py b/django/contrib/gis/db/backend/postgis/creation.py index 1d0c60010d..994edb10e7 100644 --- a/django/contrib/gis/db/backend/postgis/creation.py +++ b/django/contrib/gis/db/backend/postgis/creation.py @@ -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) diff --git a/django/contrib/gis/db/backend/postgis/field.py b/django/contrib/gis/db/backend/postgis/field.py index 2f2834f31f..c1cfc5e97e 100644 --- a/django/contrib/gis/db/backend/postgis/field.py +++ b/django/contrib/gis/db/backend/postgis/field.py @@ -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 diff --git a/django/contrib/gis/db/backend/postgis/management.py b/django/contrib/gis/db/backend/postgis/management.py index e323d83f7d..c1cb32a04f 100644 --- a/django/contrib/gis/db/backend/postgis/management.py +++ b/django/contrib/gis/db/backend/postgis/management.py @@ -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) - - - diff --git a/django/contrib/gis/db/backend/postgis/query.py b/django/contrib/gis/db/backend/postgis/query.py index 4979e5b1c9..d8b13eb37f 100644 --- a/django/contrib/gis/db/backend/postgis/query.py +++ b/django/contrib/gis/db/backend/postgis/query.py @@ -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)."