app/django/contrib/gis/db/backend/postgis/creation.py
author Sverre Rabbelier <sverre@rabbelier.nl>
Mon, 07 Sep 2009 20:27:37 +0200
changeset 2878 cf4b80992451
parent 323 ff1a9aa48cfd
permissions -rw-r--r--
merge with wrong head

import os, re, sys

from django.conf import settings
from django.core.management import call_command
from django.db import connection
from django.db.backends.creation import TEST_DATABASE_PREFIX

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."

    # Getting the command-line options for the shell command
    options = get_cmd_options(db_name)

    # Constructing the 'createlang' command.
    createlang_cmd = 'createlang %splpgsql' % options
    if verbosity >= 1: print createlang_cmd

    # Must have database super-user privileges to execute createlang -- it must
    #  also be in your path.
    status, output = getstatusoutput(createlang_cmd)

    # Checking the status of the command, 0 => execution successful
    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."

    # Constructing the necessary SQL to create the database (the DATABASE_USER
    #  must possess the privileges to create a database)
    create_sql = 'CREATE DATABASE %s' % connection.ops.quote_name(db_name)
    if settings.DATABASE_USER:
        create_sql += ' OWNER %s' % settings.DATABASE_USER

    cursor = connection.cursor()
    connection.creation.set_autocommit()

    try:
        # Trying to create the database first.
        cursor.execute(create_sql)
        #print create_sql
    except Exception, e:
        # Drop and recreate, if necessary.
        if not autoclobber:
            confirm = raw_input("\nIt appears the database, %s, already exists. Type 'yes' to delete it, or 'no' to cancel: " % db_name)
        if autoclobber or confirm == 'yes':
            if verbosity >= 1: print 'Destroying old spatial database...'
            drop_db(db_name)
            if verbosity >= 1: print 'Creating new spatial database...'
            cursor.execute(create_sql)
        else:
            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):
    """
    If no spatial database already exists, then using a cursor will not work.
     Thus, a `createdb` command will be issued through the shell to bootstrap
     creation of the spatial database.
    """

    # Getting the command-line options for the shell command
    options = get_cmd_options(False)
    create_cmd = 'createdb -O %s %s%s' % (settings.DATABASE_USER, options, db_name)
    if verbosity >= 1: print create_cmd

    # Attempting to create the database.
    status, output = getstatusoutput(create_cmd)

    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)
            if autoclobber or confirm == 'yes':
                if verbosity >= 1: print 'Destroying old spatial database...'
                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))
                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)
            else:
                raise Exception('Spatial Database Creation canceled.')
        else:
            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.')

    # Getting the spatial database name
    if test:
        db_name = get_spatial_db(test=True)
        _create_with_cursor(db_name, verbosity=verbosity, autoclobber=autoclobber)
    else:
        db_name = get_spatial_db()
        _create_with_shell(db_name, verbosity=verbosity, autoclobber=autoclobber)

    # 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)

    if verbosity >= 1: print 'Creation of spatial database %s successful.' % db_name

    # Closing the connection
    connection.close()
    settings.DATABASE_NAME = db_name

    # Syncing the database
    call_command('syncdb', verbosity=verbosity, interactive=interactive)

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.
    """
    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))

def get_cmd_options(db_name):
    "Obtains the command-line PostgreSQL connection options for shell commands."
    # The db_name parameter is optional
    options = ''
    if db_name:
        options += '-d %s ' % db_name
    if settings.DATABASE_USER:
        options += '-U %s ' % settings.DATABASE_USER
    if settings.DATABASE_HOST:
        options += '-h %s ' % settings.DATABASE_HOST
    if settings.DATABASE_PORT:
        options += '-p %s ' % settings.DATABASE_PORT
    return options

def get_spatial_db(test=False):
    """
    Returns the name of the spatial database.  The 'test' keyword may be set
     to return the test spatial database name.
    """
    if test:
        if settings.TEST_DATABASE_NAME:
            test_db_name = settings.TEST_DATABASE_NAME
        else:
            test_db_name = TEST_DATABASE_PREFIX + settings.DATABASE_NAME
        return test_db_name
    else:
        if not settings.DATABASE_NAME:
            raise Exception('must configure DATABASE_NAME in settings.py')
        return settings.DATABASE_NAME

def load_postgis_sql(db_name, verbosity=1):
    """
    This routine loads up the PostGIS SQL files lwpostgis.sql and
     spatial_ref_sys.sql.
    """

    # 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.  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:
            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')
    if not os.path.isfile(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)

    # 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 = cmd_fmt % lwpostgis_file
    if verbosity >= 1: print cmd
    status, output = getstatusoutput(cmd)
    if status:
        raise Exception('Error in loading PostGIS lwgeometry routines.')

    # Now trying to load up the Spatial Reference System table
    cmd = cmd_fmt % srefsys_file
    if verbosity >= 1: print cmd
    status, output = getstatusoutput(cmd)
    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)