1 import os, re, sys |
|
2 |
|
3 from django.conf import settings |
|
4 from django.core.management import call_command |
|
5 from django.db import connection |
|
6 from django.db.backends.creation import TEST_DATABASE_PREFIX |
|
7 from django.contrib.gis.db.backend.util import getstatusoutput |
|
8 |
|
9 def create_lang(db_name, verbosity=1): |
|
10 "Sets up the pl/pgsql language on the given database." |
|
11 |
|
12 # Getting the command-line options for the shell command |
|
13 options = get_cmd_options(db_name) |
|
14 |
|
15 # Constructing the 'createlang' command. |
|
16 createlang_cmd = 'createlang %splpgsql' % options |
|
17 if verbosity >= 1: print createlang_cmd |
|
18 |
|
19 # Must have database super-user privileges to execute createlang -- it must |
|
20 # also be in your path. |
|
21 status, output = getstatusoutput(createlang_cmd) |
|
22 |
|
23 # Checking the status of the command, 0 => execution successful |
|
24 if status: |
|
25 raise Exception("Error executing 'plpgsql' command: %s\n" % output) |
|
26 |
|
27 def _create_with_cursor(db_name, verbosity=1, autoclobber=False): |
|
28 "Creates database with psycopg2 cursor." |
|
29 qn = connection.ops.quote_name |
|
30 |
|
31 # Constructing the necessary SQL to create the database. |
|
32 create_sql = 'CREATE DATABASE %s' % qn(db_name) |
|
33 |
|
34 # If there's a template database for PostGIS set, then use it. |
|
35 if hasattr(settings, 'POSTGIS_TEMPLATE'): |
|
36 create_sql += ' TEMPLATE %s' % qn(settings.POSTGIS_TEMPLATE) |
|
37 |
|
38 # The DATABASE_USER must possess the privileges to create a spatial database. |
|
39 if settings.DATABASE_USER: |
|
40 create_sql += ' OWNER %s' % qn(settings.DATABASE_USER) |
|
41 |
|
42 cursor = connection.cursor() |
|
43 connection.creation.set_autocommit() |
|
44 |
|
45 try: |
|
46 # Trying to create the database first. |
|
47 cursor.execute(create_sql) |
|
48 except Exception, e: |
|
49 if 'already exists' in e.pgerror.lower(): |
|
50 # Database already exists, drop and recreate if user agrees. |
|
51 if not autoclobber: |
|
52 confirm = raw_input("\nIt appears the database, %s, already exists. Type 'yes' to delete it, or 'no' to cancel: " % db_name) |
|
53 if autoclobber or confirm == 'yes': |
|
54 if verbosity >= 1: print 'Destroying old spatial database...' |
|
55 drop_db(db_name) |
|
56 if verbosity >= 1: print 'Creating new spatial database...' |
|
57 cursor.execute(create_sql) |
|
58 else: |
|
59 raise Exception('Spatial database creation canceled.') |
|
60 else: |
|
61 raise Exception('Spatial database creation failed: "%s"' % e.pgerror.strip()) |
|
62 |
|
63 created_regex = re.compile(r'^createdb: database creation failed: ERROR: database ".+" already exists') |
|
64 def _create_with_shell(db_name, verbosity=1, autoclobber=False): |
|
65 """ |
|
66 If no spatial database already exists, then using a cursor will not work. |
|
67 Thus, a `createdb` command will be issued through the shell to bootstrap |
|
68 creation of the spatial database. |
|
69 |
|
70 TODO: Actually allow this method to be used without a spatial database |
|
71 in place first. |
|
72 """ |
|
73 # Getting the command-line options for the shell command |
|
74 options = get_cmd_options(False) |
|
75 if hasattr(settings, 'POSTGIS_TEMPLATE'): |
|
76 options += '-T %s ' % settings.POSTGIS_TEMPlATE |
|
77 |
|
78 create_cmd = 'createdb -O %s %s%s' % (settings.DATABASE_USER, options, db_name) |
|
79 if verbosity >= 1: print create_cmd |
|
80 |
|
81 # Attempting to create the database. |
|
82 status, output = getstatusoutput(create_cmd) |
|
83 |
|
84 if status: |
|
85 if created_regex.match(output): |
|
86 if not autoclobber: |
|
87 confirm = raw_input("\nIt appears the database, %s, already exists. Type 'yes' to delete it, or 'no' to cancel: " % db_name) |
|
88 if autoclobber or confirm == 'yes': |
|
89 if verbosity >= 1: print 'Destroying old spatial database...' |
|
90 drop_cmd = 'dropdb %s%s' % (options, db_name) |
|
91 status, output = getstatusoutput(drop_cmd) |
|
92 if status != 0: |
|
93 raise Exception('Could not drop database %s: %s' % (db_name, output)) |
|
94 if verbosity >= 1: print 'Creating new spatial database...' |
|
95 status, output = getstatusoutput(create_cmd) |
|
96 if status != 0: |
|
97 raise Exception('Could not create database after dropping: %s' % output) |
|
98 else: |
|
99 raise Exception('Spatial Database Creation canceled.') |
|
100 else: |
|
101 raise Exception('Unknown error occurred in creating database: %s' % output) |
|
102 |
|
103 def create_test_spatial_db(verbosity=1, autoclobber=False, interactive=False): |
|
104 "Creates a test spatial database based on the settings." |
|
105 |
|
106 # Making sure we're using PostgreSQL and psycopg2 |
|
107 if settings.DATABASE_ENGINE != 'postgresql_psycopg2': |
|
108 raise Exception('Spatial database creation only supported postgresql_psycopg2 platform.') |
|
109 |
|
110 # Getting the spatial database name |
|
111 db_name = get_spatial_db(test=True) |
|
112 _create_with_cursor(db_name, verbosity=verbosity, autoclobber=autoclobber) |
|
113 |
|
114 # If a template database is used, then don't need to do any of the following. |
|
115 if not hasattr(settings, 'POSTGIS_TEMPLATE'): |
|
116 # Creating the db language, does not need to be done on NT platforms |
|
117 # since the PostGIS installer enables this capability. |
|
118 if os.name != 'nt': |
|
119 create_lang(db_name, verbosity=verbosity) |
|
120 |
|
121 # Now adding in the PostGIS routines. |
|
122 load_postgis_sql(db_name, verbosity=verbosity) |
|
123 |
|
124 if verbosity >= 1: print 'Creation of spatial database %s successful.' % db_name |
|
125 |
|
126 # Closing the connection |
|
127 connection.close() |
|
128 settings.DATABASE_NAME = db_name |
|
129 connection.settings_dict["DATABASE_NAME"] = db_name |
|
130 can_rollback = connection.creation._rollback_works() |
|
131 settings.DATABASE_SUPPORTS_TRANSACTIONS = can_rollback |
|
132 connection.settings_dict["DATABASE_SUPPORTS_TRANSACTIONS"] = can_rollback |
|
133 |
|
134 # Syncing the database |
|
135 call_command('syncdb', verbosity=verbosity, interactive=interactive) |
|
136 |
|
137 def drop_db(db_name=False, test=False): |
|
138 """ |
|
139 Drops the given database (defaults to what is returned from |
|
140 get_spatial_db()). All exceptions are propagated up to the caller. |
|
141 """ |
|
142 if not db_name: db_name = get_spatial_db(test=test) |
|
143 cursor = connection.cursor() |
|
144 cursor.execute('DROP DATABASE %s' % connection.ops.quote_name(db_name)) |
|
145 |
|
146 def get_cmd_options(db_name): |
|
147 "Obtains the command-line PostgreSQL connection options for shell commands." |
|
148 # The db_name parameter is optional |
|
149 options = '' |
|
150 if db_name: |
|
151 options += '-d %s ' % db_name |
|
152 if settings.DATABASE_USER: |
|
153 options += '-U %s ' % settings.DATABASE_USER |
|
154 if settings.DATABASE_HOST: |
|
155 options += '-h %s ' % settings.DATABASE_HOST |
|
156 if settings.DATABASE_PORT: |
|
157 options += '-p %s ' % settings.DATABASE_PORT |
|
158 return options |
|
159 |
|
160 def get_spatial_db(test=False): |
|
161 """ |
|
162 Returns the name of the spatial database. The 'test' keyword may be set |
|
163 to return the test spatial database name. |
|
164 """ |
|
165 if test: |
|
166 if settings.TEST_DATABASE_NAME: |
|
167 test_db_name = settings.TEST_DATABASE_NAME |
|
168 else: |
|
169 test_db_name = TEST_DATABASE_PREFIX + settings.DATABASE_NAME |
|
170 return test_db_name |
|
171 else: |
|
172 if not settings.DATABASE_NAME: |
|
173 raise Exception('must configure DATABASE_NAME in settings.py') |
|
174 return settings.DATABASE_NAME |
|
175 |
|
176 def load_postgis_sql(db_name, verbosity=1): |
|
177 """ |
|
178 This routine loads up the PostGIS SQL files lwpostgis.sql and |
|
179 spatial_ref_sys.sql. |
|
180 """ |
|
181 # Getting the path to the PostGIS SQL |
|
182 try: |
|
183 # POSTGIS_SQL_PATH may be placed in settings to tell GeoDjango where the |
|
184 # PostGIS SQL files are located. This is especially useful on Win32 |
|
185 # platforms since the output of pg_config looks like "C:/PROGRA~1/..". |
|
186 sql_path = settings.POSTGIS_SQL_PATH |
|
187 except AttributeError: |
|
188 status, sql_path = getstatusoutput('pg_config --sharedir') |
|
189 if status: |
|
190 sql_path = '/usr/local/share' |
|
191 |
|
192 # The PostGIS SQL post-creation files. |
|
193 lwpostgis_file = os.path.join(sql_path, 'lwpostgis.sql') |
|
194 srefsys_file = os.path.join(sql_path, 'spatial_ref_sys.sql') |
|
195 if not os.path.isfile(lwpostgis_file): |
|
196 raise Exception('Could not find PostGIS function definitions in %s' % lwpostgis_file) |
|
197 if not os.path.isfile(srefsys_file): |
|
198 raise Exception('Could not find PostGIS spatial reference system definitions in %s' % srefsys_file) |
|
199 |
|
200 # Getting the psql command-line options, and command format. |
|
201 options = get_cmd_options(db_name) |
|
202 cmd_fmt = 'psql %s-f "%%s"' % options |
|
203 |
|
204 # Now trying to load up the PostGIS functions |
|
205 cmd = cmd_fmt % lwpostgis_file |
|
206 if verbosity >= 1: print cmd |
|
207 status, output = getstatusoutput(cmd) |
|
208 if status: |
|
209 raise Exception('Error in loading PostGIS lwgeometry routines.') |
|
210 |
|
211 # Now trying to load up the Spatial Reference System table |
|
212 cmd = cmd_fmt % srefsys_file |
|
213 if verbosity >= 1: print cmd |
|
214 status, output = getstatusoutput(cmd) |
|
215 if status: |
|
216 raise Exception('Error in loading PostGIS spatial_ref_sys table.') |
|
217 |
|
218 # Setting the permissions because on Windows platforms the owner |
|
219 # of the spatial_ref_sys and geometry_columns tables is always |
|
220 # the postgres user, regardless of how the db is created. |
|
221 if os.name == 'nt': set_permissions(db_name) |
|
222 |
|
223 def set_permissions(db_name): |
|
224 """ |
|
225 Sets the permissions on the given database to that of the user specified |
|
226 in the settings. Needed specifically for PostGIS on Win32 platforms. |
|
227 """ |
|
228 cursor = connection.cursor() |
|
229 user = settings.DATABASE_USER |
|
230 cursor.execute('ALTER TABLE geometry_columns OWNER TO %s' % user) |
|
231 cursor.execute('ALTER TABLE spatial_ref_sys OWNER TO %s' % user) |
|