Browse Source

Fixed #27061 -- Added a TEST['TEMPLATE'] setting for PostgreSQL.

Chris Jerdonek 8 years ago
parent
commit
a3db480393

+ 22 - 9
django/db/backends/postgresql/creation.py

@@ -5,28 +5,42 @@ from django.db.backends.base.creation import BaseDatabaseCreation
 
 class DatabaseCreation(BaseDatabaseCreation):
 
+    def _quote_name(self, name):
+        return self.connection.ops.quote_name(name)
+
+    def _get_database_create_suffix(self, encoding=None, template=None):
+        suffix = ""
+        if encoding:
+            suffix += " ENCODING '{}'".format(encoding)
+        if template:
+            suffix += " TEMPLATE {}".format(self._quote_name(template))
+        if suffix:
+            suffix = "WITH" + suffix
+        return suffix
+
     def sql_table_creation_suffix(self):
         test_settings = self.connection.settings_dict['TEST']
         assert test_settings['COLLATION'] is None, (
             "PostgreSQL does not support collation setting at database creation time."
         )
-        if test_settings['CHARSET']:
-            return "WITH ENCODING '%s'" % test_settings['CHARSET']
-        return ''
+        return self._get_database_create_suffix(
+            encoding=test_settings['CHARSET'],
+            template=test_settings.get('TEMPLATE'),
+        )
 
     def _clone_test_db(self, number, verbosity, keepdb=False):
         # CREATE DATABASE ... WITH TEMPLATE ... requires closing connections
         # to the template database.
         self.connection.close()
 
-        qn = self.connection.ops.quote_name
         source_database_name = self.connection.settings_dict['NAME']
         target_database_name = self.get_test_db_clone_settings(number)['NAME']
+        suffix = self._get_database_create_suffix(template=source_database_name)
+        creation_sql = "CREATE DATABASE {} {}".format(self._quote_name(target_database_name), suffix)
 
         with self._nodb_connection.cursor() as cursor:
             try:
-                cursor.execute("CREATE DATABASE %s WITH TEMPLATE %s" % (
-                    qn(target_database_name), qn(source_database_name)))
+                cursor.execute(creation_sql)
             except Exception as e:
                 if keepdb:
                     return
@@ -35,9 +49,8 @@ class DatabaseCreation(BaseDatabaseCreation):
                         print("Destroying old test database for alias %s..." % (
                             self._get_database_display_str(verbosity, target_database_name),
                         ))
-                    cursor.execute("DROP DATABASE %s" % qn(target_database_name))
-                    cursor.execute("CREATE DATABASE %s WITH TEMPLATE %s" % (
-                        qn(target_database_name), qn(source_database_name)))
+                    cursor.execute("DROP DATABASE %s" % self._quote_name(target_database_name))
+                    cursor.execute(creation_sql)
                 except Exception as e:
                     sys.stderr.write("Got an error cloning the test database: %s\n" % e)
                     sys.exit(2)

+ 10 - 0
docs/ref/databases.txt

@@ -164,6 +164,16 @@ lookups that use the ``LIKE`` operator in their SQL, as is done with the
 
 .. _PostgreSQL operator class: http://www.postgresql.org/docs/current/static/indexes-opclass.html
 
+Test database templates
+-----------------------
+
+.. versionadded:: 1.11
+
+You can use the :setting:`TEST['TEMPLATE'] <TEST_TEMPLATE>` setting to specify
+a `template`_ (e.g. ``'template0'``) from which to create a test database.
+
+.. _template: https://www.postgresql.org/docs/current/static/sql-createdatabase.html
+
 Speeding up test execution with non-durable settings
 ----------------------------------------------------
 

+ 14 - 0
docs/ref/settings.txt

@@ -738,6 +738,20 @@ the database state between tests if you don't have transactions). You can set
 this to ``False`` to speed up creation time if you don't have any test classes
 with :ref:`serialized_rollback=True <test-case-serialized-rollback>`.
 
+.. setting:: TEST_TEMPLATE
+
+``TEMPLATE``
+^^^^^^^^^^^^
+
+.. versionadded:: 1.11
+
+This is a PostgreSQL-specific setting.
+
+The name of a `template`_ (e.g. ``'template0'``) from which to create the test
+database.
+
+.. _template: https://www.postgresql.org/docs/current/static/sql-createdatabase.html
+
 .. setting:: TEST_CREATE
 
 ``CREATE_DB``

+ 3 - 0
docs/releases/1.11.txt

@@ -159,6 +159,9 @@ Database backends
   on PostgreSQL 9.5+ and Oracle to execute queries with
   ``FOR UPDATE SKIP LOCKED``.
 
+* Added the :setting:`TEST['TEMPLATE'] <TEST_TEMPLATE>` setting to let
+  PostgreSQL users specify a template for creating the test database.
+
 Email
 ~~~~~
 

+ 49 - 1
tests/backends/test_creation.py

@@ -1,9 +1,12 @@
 import copy
+import unittest
+from contextlib import contextmanager
 
-from django.db import DEFAULT_DB_ALIAS, connections
+from django.db import DEFAULT_DB_ALIAS, connection, connections
 from django.db.backends.base.creation import (
     TEST_DATABASE_PREFIX, BaseDatabaseCreation,
 )
+from django.db.backends.postgresql.creation import DatabaseCreation
 from django.test import SimpleTestCase
 
 
@@ -40,3 +43,48 @@ class TestDbSignatureTests(SimpleTestCase):
         test_connection.settings_dict['TEST'] = {'NAME': test_name}
         signature = BaseDatabaseCreation(test_connection).test_db_signature()
         self.assertEqual(signature[3], test_name)
+
+
+@unittest.skipUnless(connection.vendor == 'postgresql', "PostgreSQL-specific tests")
+class PostgreSQLDatabaseCreationTests(SimpleTestCase):
+
+    @contextmanager
+    def changed_test_settings(self, **kwargs):
+        settings = connection.settings_dict['TEST']
+        saved_values = {}
+        for name in kwargs:
+            if name in settings:
+                saved_values[name] = settings[name]
+
+        for name, value in kwargs.items():
+            settings[name] = value
+        try:
+            yield
+        finally:
+            for name, value in kwargs.items():
+                if name in saved_values:
+                    settings[name] = saved_values[name]
+                else:
+                    del settings[name]
+
+    def check_sql_table_creation_suffix(self, settings, expected):
+        with self.changed_test_settings(**settings):
+            creation = DatabaseCreation(connection)
+            suffix = creation.sql_table_creation_suffix()
+            self.assertEqual(suffix, expected)
+
+    def test_sql_table_creation_suffix_with_none_settings(self):
+        settings = dict(CHARSET=None, TEMPLATE=None)
+        self.check_sql_table_creation_suffix(settings, "")
+
+    def test_sql_table_creation_suffix_with_encoding(self):
+        settings = dict(CHARSET='UTF8')
+        self.check_sql_table_creation_suffix(settings, "WITH ENCODING 'UTF8'")
+
+    def test_sql_table_creation_suffix_with_template(self):
+        settings = dict(TEMPLATE='template0')
+        self.check_sql_table_creation_suffix(settings, 'WITH TEMPLATE "template0"')
+
+    def test_sql_table_creation_suffix_with_encoding_and_template(self):
+        settings = dict(CHARSET='UTF8', TEMPLATE='template0')
+        self.check_sql_table_creation_suffix(settings, '''WITH ENCODING 'UTF8' TEMPLATE "template0"''')