From 30cbd5d360a2b8becdef7c6a0c939e0633a5468e Mon Sep 17 00:00:00 2001
From: Claude Paroz <claude@2xlibre.net>
Date: Sun, 14 Sep 2014 20:26:16 +0200
Subject: [PATCH] Replaced DatabaseCreation sql methods by schema editor
 equivalents

Also used schema editor in migrate to sync unmigrated apps (sync_apps).
Refs #22340. Thanks Tim Graham for the review.
---
 django/core/management/commands/migrate.py | 64 +++++-----------------
 django/db/backends/creation.py             |  5 ++
 django/db/backends/schema.py               | 29 ++++++----
 tests/backends/tests.py                    |  7 ++-
 tests/commands_sql/tests.py                | 10 +++-
 tests/model_options/test_tablespaces.py    |  9 ++-
 6 files changed, 54 insertions(+), 70 deletions(-)

diff --git a/django/core/management/commands/migrate.py b/django/core/management/commands/migrate.py
index 69ccbd3548..d63cb1f339 100644
--- a/django/core/management/commands/migrate.py
+++ b/django/core/management/commands/migrate.py
@@ -225,14 +225,12 @@ class Command(BaseCommand):
         try:
             # Get a list of already installed *models* so that references work right.
             tables = connection.introspection.table_names(cursor)
-            seen_models = connection.introspection.installed_models(tables)
             created_models = set()
-            pending_references = {}
 
             # Build the manifest of apps and models that are to be synchronized
             all_models = [
                 (app_config.label,
-                    router.get_migratable_models(app_config, connection.alias, include_auto_created=True))
+                    router.get_migratable_models(app_config, connection.alias, include_auto_created=False))
                 for app_config in apps.get_app_configs()
                 if app_config.models_module is not None and app_config.label in app_labels
             ]
@@ -256,34 +254,27 @@ class Command(BaseCommand):
             if self.verbosity >= 1:
                 self.stdout.write("  Creating tables...\n")
             with transaction.atomic(using=connection.alias, savepoint=connection.features.can_rollback_ddl):
+                deferred_sql = []
                 for app_name, model_list in manifest.items():
                     for model in model_list:
-                        # Create the model's database table, if it doesn't already exist.
+                        if model._meta.proxy or not model._meta.managed:
+                            continue
                         if self.verbosity >= 3:
                             self.stdout.write(
                                 "    Processing %s.%s model\n" % (app_name, model._meta.object_name)
                             )
-                        sql, references = connection.creation.sql_create_model(model, no_style(), seen_models)
-                        seen_models.add(model)
+                        with connection.schema_editor() as editor:
+                            if self.verbosity >= 1:
+                                self.stdout.write("    Creating table %s\n" % model._meta.db_table)
+                            editor.create_model(model)
+                            deferred_sql.extend(editor.deferred_sql)
+                            editor.deferred_sql = []
                         created_models.add(model)
-                        for refto, refs in references.items():
-                            pending_references.setdefault(refto, []).extend(refs)
-                            if refto in seen_models:
-                                sql.extend(
-                                    connection.creation.sql_for_pending_references(
-                                        refto, no_style(), pending_references,
-                                    )
-                                )
-                        sql.extend(
-                            connection.creation.sql_for_pending_references(
-                                model, no_style(), pending_references
-                            )
-                        )
-                        if self.verbosity >= 1 and sql:
-                            self.stdout.write("    Creating table %s\n" % model._meta.db_table)
-                        for statement in sql:
-                            cursor.execute(statement)
-                        tables.append(connection.introspection.table_name_converter(model._meta.db_table))
+
+                if self.verbosity >= 1:
+                    self.stdout.write("    Running deferred SQL...\n")
+                for statement in deferred_sql:
+                    cursor.execute(statement)
         finally:
             cursor.close()
 
@@ -321,31 +312,6 @@ class Command(BaseCommand):
                                     "    No custom SQL for %s.%s model\n" %
                                     (app_name, model._meta.object_name)
                                 )
-
-            if self.verbosity >= 1:
-                self.stdout.write("  Installing indexes...\n")
-
-            # Install SQL indices for all newly created models
-            for app_name, model_list in manifest.items():
-                for model in model_list:
-                    if model in created_models:
-                        index_sql = connection.creation.sql_indexes_for_model(model, no_style())
-                        if index_sql:
-                            if self.verbosity >= 2:
-                                self.stdout.write(
-                                    "    Installing index for %s.%s model\n" %
-                                    (app_name, model._meta.object_name)
-                                )
-                            savepoint = connection.features.can_rollback_ddl
-                            try:
-                                with transaction.atomic(using=connection.alias, savepoint=savepoint):
-                                    for sql in index_sql:
-                                        cursor.execute(sql)
-                            except Exception as e:
-                                self.stderr.write(
-                                    "    Failed to install index for %s.%s model: %s\n" %
-                                    (app_name, model._meta.object_name, e)
-                                )
         finally:
             cursor.close()
 
diff --git a/django/db/backends/creation.py b/django/db/backends/creation.py
index d3def4ebe2..6ccc6a3236 100644
--- a/django/db/backends/creation.py
+++ b/django/db/backends/creation.py
@@ -1,8 +1,10 @@
 import hashlib
 import sys
 import time
+import warnings
 
 from django.conf import settings
+from django.utils.deprecation import RemovedInDjango20Warning
 from django.utils.encoding import force_bytes
 from django.utils.six.moves import input
 from django.utils.six import StringIO
@@ -192,6 +194,9 @@ class BaseDatabaseCreation(object):
         """
         Returns the CREATE INDEX SQL statements for a single model.
         """
+        warnings.warn("DatabaseCreation.sql_indexes_for_model is deprecated, "
+                      "use the equivalent method of the schema editor instead.",
+                      RemovedInDjango20Warning)
         if not model._meta.managed or model._meta.proxy or model._meta.swapped:
             return []
         output = []
diff --git a/django/db/backends/schema.py b/django/db/backends/schema.py
index 0457bf6907..474c1cf8a1 100644
--- a/django/db/backends/schema.py
+++ b/django/db/backends/schema.py
@@ -123,17 +123,18 @@ class BaseDatabaseSchemaEditor(object):
         # Work out nullability
         null = field.null
         # If we were told to include a default value, do so
-        default_value = self.effective_default(field)
         include_default = include_default and not self.skip_default(field)
-        if include_default and default_value is not None:
-            if self.connection.features.requires_literal_defaults:
-                # Some databases can't take defaults as a parameter (oracle)
-                # If this is the case, the individual schema backend should
-                # implement prepare_default
-                sql += " DEFAULT %s" % self.prepare_default(default_value)
-            else:
-                sql += " DEFAULT %s"
-                params += [default_value]
+        if include_default:
+            default_value = self.effective_default(field)
+            if default_value is not None:
+                if self.connection.features.requires_literal_defaults:
+                    # Some databases can't take defaults as a parameter (oracle)
+                    # If this is the case, the individual schema backend should
+                    # implement prepare_default
+                    sql += " DEFAULT %s" % self.prepare_default(default_value)
+                else:
+                    sql += " DEFAULT %s"
+                    params += [default_value]
         # Oracle treats the empty string ('') as null, so coerce the null
         # option whenever '' is a possible value.
         if (field.empty_strings_allowed and not field.primary_key and
@@ -247,6 +248,7 @@ class BaseDatabaseSchemaEditor(object):
                 autoinc_sql = self.connection.ops.autoinc_sql(model._meta.db_table, field.column)
                 if autoinc_sql:
                     self.deferred_sql.extend(autoinc_sql)
+
         # Add any unique_togethers
         for fields in model._meta.unique_together:
             columns = [model._meta.get_field_by_name(field)[0].column for field in fields]
@@ -258,7 +260,12 @@ class BaseDatabaseSchemaEditor(object):
             "table": self.quote_name(model._meta.db_table),
             "definition": ", ".join(column_sqls)
         }
-        self.execute(sql, params)
+        if model._meta.db_tablespace:
+            tablespace_sql = self.connection.ops.tablespace_sql(model._meta.db_tablespace)
+            if tablespace_sql:
+                sql += ' ' + tablespace_sql
+        # Prevent using [] as params, in the case a literal '%' is used in the definition
+        self.execute(sql, params or None)
 
         # Add any field index and index_together's (deferred as SQLite3 _remake_table needs it)
         self.deferred_sql.extend(self._model_indexes_sql(model))
diff --git a/tests/backends/tests.py b/tests/backends/tests.py
index 5e2c447755..d25cde675a 100644
--- a/tests/backends/tests.py
+++ b/tests/backends/tests.py
@@ -111,9 +111,10 @@ class SQLiteTests(TestCase):
         Check that auto_increment fields are created with the AUTOINCREMENT
         keyword in order to be monotonically increasing. Refs #10164.
         """
-        statements = connection.creation.sql_create_model(models.Square,
-            style=no_style())
-        match = re.search('"id" ([^,]+),', statements[0][0])
+        with connection.schema_editor(collect_sql=True) as editor:
+            editor.create_model(models.Square)
+            statements = editor.collected_sql
+        match = re.search('"id" ([^,]+),', statements[0])
         self.assertIsNotNone(match)
         self.assertEqual('integer NOT NULL PRIMARY KEY AUTOINCREMENT',
             match.group(1), "Wrong SQL used to create an auto-increment "
diff --git a/tests/commands_sql/tests.py b/tests/commands_sql/tests.py
index e381c031a5..e1d4272616 100644
--- a/tests/commands_sql/tests.py
+++ b/tests/commands_sql/tests.py
@@ -2,6 +2,7 @@ from __future__ import unicode_literals
 
 import re
 import unittest
+import warnings
 
 from django.apps import apps
 from django.core.management.color import no_style
@@ -10,6 +11,7 @@ from django.core.management.sql import (sql_create, sql_delete, sql_indexes,
 from django.db import connections, DEFAULT_DB_ALIAS
 from django.test import TestCase, override_settings
 from django.utils import six
+from django.utils.deprecation import RemovedInDjango20Warning
 
 # See also initial_sql_regress for 'custom_sql_for_model' tests
 
@@ -67,7 +69,9 @@ class SQLCommandsTestCase(TestCase):
 
     def test_sql_indexes(self):
         app_config = apps.get_app_config('commands_sql')
-        output = sql_indexes(app_config, no_style(), connections[DEFAULT_DB_ALIAS])
+        with warnings.catch_warnings():
+            warnings.simplefilter("ignore", category=RemovedInDjango20Warning)
+            output = sql_indexes(app_config, no_style(), connections[DEFAULT_DB_ALIAS])
         # PostgreSQL creates one additional index for CharField
         self.assertIn(self.count_ddl(output, 'CREATE INDEX'), [3, 4])
 
@@ -79,7 +83,9 @@ class SQLCommandsTestCase(TestCase):
 
     def test_sql_all(self):
         app_config = apps.get_app_config('commands_sql')
-        output = sql_all(app_config, no_style(), connections[DEFAULT_DB_ALIAS])
+        with warnings.catch_warnings():
+            warnings.simplefilter("ignore", category=RemovedInDjango20Warning)
+            output = sql_all(app_config, no_style(), connections[DEFAULT_DB_ALIAS])
 
         self.assertEqual(self.count_ddl(output, 'CREATE TABLE'), 3)
         # PostgreSQL creates one additional index for CharField
diff --git a/tests/model_options/test_tablespaces.py b/tests/model_options/test_tablespaces.py
index ac331bd36c..515cd8a032 100644
--- a/tests/model_options/test_tablespaces.py
+++ b/tests/model_options/test_tablespaces.py
@@ -3,7 +3,6 @@ from __future__ import unicode_literals
 from django.apps import apps
 from django.conf import settings
 from django.db import connection
-from django.core.management.color import no_style
 from django.test import TestCase, skipIfDBFeature, skipUnlessDBFeature
 
 from .models.tablespaces import (Article, ArticleRef, Authors, Reviewers,
@@ -11,13 +10,13 @@ from .models.tablespaces import (Article, ArticleRef, Authors, Reviewers,
 
 
 def sql_for_table(model):
-    return '\n'.join(connection.creation.sql_create_model(model,
-                                                          no_style())[0])
+    with connection.schema_editor(collect_sql=True) as editor:
+        editor.create_model(model)
+    return editor.collected_sql[0]
 
 
 def sql_for_index(model):
-    return '\n'.join(connection.creation.sql_indexes_for_model(model,
-                                                               no_style()))
+    return '\n'.join(connection.schema_editor()._model_indexes_sql(model))
 
 
 # We can't test the DEFAULT_TABLESPACE and DEFAULT_INDEX_TABLESPACE settings