Removed support for custom SQL per deprecation timeline.

This commit is contained in:
Tim Graham 2014-12-26 13:56:08 -05:00
parent a420f83e7d
commit 4aa089a9a9
26 changed files with 10 additions and 368 deletions

View File

@ -233,7 +233,7 @@ class ManagementUtility(object):
subcommand_cls = self.fetch_command(cwords[0])
# special case: add the names of installed apps to options
if cwords[0] in ('dumpdata', 'sql', 'sqlall', 'sqlclear',
'sqlcustom', 'sqlindexes', 'sqlmigrate', 'sqlsequencereset', 'test'):
'sqlindexes', 'sqlmigrate', 'sqlsequencereset', 'test'):
try:
app_configs = apps.get_app_configs()
# Get the last part of the dotted path as the app name.

View File

@ -45,9 +45,6 @@ class Command(BaseCommand):
"Django to create, modify, and delete the table"
)
yield "# Feel free to rename the models, but don't rename db_table values or field names."
yield "#"
yield "# Also note: You'll have to insert the output of 'django-admin sqlcustom [app_label]'"
yield "# into your database."
yield "from __future__ import unicode_literals"
yield ''
yield 'from %s import models' % self.db_module

View File

@ -4,14 +4,12 @@ from __future__ import unicode_literals
from collections import OrderedDict
from importlib import import_module
import time
import traceback
import warnings
from django.apps import apps
from django.core.management import call_command
from django.core.management.base import BaseCommand, CommandError
from django.core.management.color import no_style
from django.core.management.sql import custom_sql_for_model, emit_post_migrate_signal, emit_pre_migrate_signal
from django.core.management.sql import emit_post_migrate_signal, emit_pre_migrate_signal
from django.db import connections, router, transaction, DEFAULT_DB_ALIAS
from django.db.migrations.executor import MigrationExecutor
from django.db.migrations.loader import AmbiguityError
@ -47,7 +45,6 @@ class Command(BaseCommand):
self.verbosity = options.get('verbosity')
self.interactive = options.get('interactive')
self.show_traceback = options.get('traceback')
# Import the 'management' module within each installed app, to register
# dispatcher events.
@ -73,7 +70,7 @@ class Command(BaseCommand):
no_color=options.get('no_color'),
settings=options.get('settings'),
stdout=self.stdout,
traceback=self.show_traceback,
traceback=options.get('traceback'),
verbosity=self.verbosity,
)
@ -167,18 +164,6 @@ class Command(BaseCommand):
self.stdout.write(self.style.MIGRATE_HEADING("Synchronizing apps without migrations:"))
self.sync_apps(connection, executor.loader.unmigrated_apps)
# The test runner requires us to flush after a syncdb but before migrations,
# so do that here.
if options.get("test_flush", False):
call_command(
'flush',
verbosity=max(self.verbosity - 1, 0),
interactive=False,
database=db,
reset_sequences=False,
inhibit_post_migrate=True,
)
# Migrate!
if self.verbosity >= 1:
self.stdout.write(self.style.MIGRATE_HEADING("Running migrations:"))
@ -299,41 +284,4 @@ class Command(BaseCommand):
finally:
cursor.close()
# The connection may have been closed by a syncdb handler.
cursor = connection.cursor()
try:
# Install custom SQL for the app (but only if this
# is a model we've just created)
if self.verbosity >= 1:
self.stdout.write(" Installing custom SQL...\n")
for app_name, model_list in manifest.items():
for model in model_list:
if model in created_models:
custom_sql = custom_sql_for_model(model, no_style(), connection)
if custom_sql:
if self.verbosity >= 2:
self.stdout.write(
" Installing custom SQL for %s.%s model\n" %
(app_name, model._meta.object_name)
)
try:
with transaction.atomic(using=connection.alias):
for sql in custom_sql:
cursor.execute(sql)
except Exception as e:
self.stderr.write(
" Failed to install custom SQL for %s.%s model: %s\n"
% (app_name, model._meta.object_name, e)
)
if self.show_traceback:
traceback.print_exc()
else:
if self.verbosity >= 3:
self.stdout.write(
" No custom SQL for %s.%s model\n" %
(app_name, model._meta.object_name)
)
finally:
cursor.close()
return created_models

View File

@ -6,7 +6,7 @@ from django.db import connections, DEFAULT_DB_ALIAS
class Command(AppCommand):
help = "Prints the CREATE TABLE, custom SQL and CREATE INDEX SQL statements for the given model module name(s)."
help = "Prints the CREATE TABLE and CREATE INDEX SQL statements for the given model module name(s)."
output_transaction = True

View File

@ -1,24 +0,0 @@
from __future__ import unicode_literals
from django.core.management.base import AppCommand
from django.core.management.sql import sql_custom
from django.db import connections, DEFAULT_DB_ALIAS
class Command(AppCommand):
help = "Prints the custom table modifying SQL statements for the given app name(s)."
output_transaction = True
def add_arguments(self, parser):
super(Command, self).add_arguments(parser)
parser.add_argument('--database', default=DEFAULT_DB_ALIAS,
help='Nominates a database to print the SQL for. Defaults to the '
'"default" database.')
def handle_app_config(self, app_config, **options):
if app_config.models_module is None:
return
connection = connections[options['database']]
statements = sql_custom(app_config, self.style, connection)
return '\n'.join(statements)

View File

@ -1,15 +1,10 @@
from __future__ import unicode_literals
import io
import os
import re
import warnings
from django.apps import apps
from django.conf import settings
from django.core.management.base import CommandError
from django.db import models, router
from django.utils.deprecation import RemovedInDjango19Warning
from django.utils.version import get_docs_version
@ -140,21 +135,6 @@ def sql_flush(style, connection, only_django=False, reset_sequences=True, allow_
return statements
def sql_custom(app_config, style, connection):
"Returns a list of the custom table modifying SQL statements for the given app."
check_for_migrations(app_config, connection)
output = []
app_models = router.get_migratable_models(app_config, connection.alias)
for model in app_models:
output.extend(custom_sql_for_model(model, style, connection))
return output
def sql_indexes(app_config, style, connection):
"Returns a list of the CREATE INDEX SQL statements for all models in the given app."
@ -184,7 +164,6 @@ def sql_all(app_config, style, connection):
"Returns a list of CREATE TABLE SQL, initial-data inserts, and CREATE INDEX SQL for the given module."
return (
sql_create(app_config, style, connection) +
sql_custom(app_config, style, connection) +
sql_indexes(app_config, style, connection)
)
@ -205,43 +184,6 @@ def _split_statements(content):
return statements
def custom_sql_for_model(model, style, connection):
opts = model._meta
app_dirs = []
app_dir = apps.get_app_config(model._meta.app_label).path
app_dirs.append(os.path.normpath(os.path.join(app_dir, 'sql')))
# Deprecated location -- remove in Django 1.9
old_app_dir = os.path.normpath(os.path.join(app_dir, 'models/sql'))
if os.path.exists(old_app_dir):
warnings.warn("Custom SQL location '<app_label>/models/sql' is "
"deprecated, use '<app_label>/sql' instead.",
RemovedInDjango19Warning)
app_dirs.append(old_app_dir)
output = []
# Post-creation SQL should come before any initial SQL data is loaded.
# However, this should not be done for models that are unmanaged or
# for fields that are part of a parent model (via model inheritance).
if opts.managed:
post_sql_fields = [f for f in opts.local_fields if hasattr(f, 'post_create_sql')]
for f in post_sql_fields:
output.extend(f.post_create_sql(style, model._meta.db_table))
# Find custom SQL, if it's available.
backend_name = connection.settings_dict['ENGINE'].split('.')[-1]
sql_files = []
for app_dir in app_dirs:
sql_files.append(os.path.join(app_dir, "%s.%s.sql" % (opts.model_name, backend_name)))
sql_files.append(os.path.join(app_dir, "%s.sql" % opts.model_name))
for sql_file in sql_files:
if os.path.exists(sql_file):
with io.open(sql_file, encoding=settings.FILE_CHARSET) as fp:
output.extend(connection.ops.prepare_sql_script(fp.read(), _allow_fallback=True))
return output
def emit_pre_migrate_signal(verbosity, interactive, db):
# Emit the pre_migrate signal for every application.
for app_config in apps.get_app_configs():

View File

@ -366,7 +366,6 @@ class BaseDatabaseCreation(object):
verbosity=max(verbosity - 1, 0),
interactive=False,
database=self.connection.alias,
test_flush=True,
)
# We then serialize the current state of the database into a string

View File

@ -3,15 +3,7 @@ Providing initial data for models
=================================
It's sometimes useful to pre-populate your database with hard-coded data when
you're first setting up an app. There's a couple of ways you can have Django
automatically create this data: you can provide `initial data via fixtures`_, or
you can provide `initial data as SQL`_.
In general, using a fixture is a cleaner method since it's database-agnostic,
but initial SQL is also quite a bit more flexible.
.. _initial data as sql: `providing initial sql data`_
.. _initial data via fixtures: `providing initial data with fixtures`_
you're first setting up an app. You can provide initial data via fixtures.
.. _initial-data-via-fixtures:
@ -91,77 +83,3 @@ directories.
Fixtures are also used by the :ref:`testing framework
<topics-testing-fixtures>` to help set up a consistent test environment.
.. _initial-sql:
Providing initial SQL data
==========================
.. deprecated:: 1.7
If an application uses migrations, there is no loading of initial SQL data
(including backend-specific SQL data). Since migrations will be required
for applications in Django 1.9, this behavior is considered deprecated.
If you want to use initial SQL for an app, consider doing it in a
:ref:`data migration <data-migrations>`.
Django provides a hook for passing the database arbitrary SQL that's executed
just after the CREATE TABLE statements when you run :djadmin:`migrate`. You can
use this hook to populate default records, or you could also create SQL
functions, views, triggers, etc.
The hook is simple: Django just looks for a file called ``sql/<modelname>.sql``,
in your app directory, where ``<modelname>`` is the model's name in lowercase.
So, if you had a ``Person`` model in an app called ``myapp``, you could add
arbitrary SQL to the file ``sql/person.sql`` inside your ``myapp`` directory.
Here's an example of what the file might contain:
.. code-block:: sql
INSERT INTO myapp_person (first_name, last_name) VALUES ('John', 'Lennon');
INSERT INTO myapp_person (first_name, last_name) VALUES ('Paul', 'McCartney');
Each SQL file, if given, is expected to contain valid SQL statements
which will insert the desired data (e.g., properly-formatted
``INSERT`` statements separated by semicolons).
The SQL files are read by the :djadmin:`sqlcustom` and :djadmin:`sqlall`
commands in :doc:`manage.py </ref/django-admin>`. Refer to the :doc:`manage.py
documentation </ref/django-admin>` for more information.
Note that if you have multiple SQL data files, there's no guarantee of
the order in which they're executed. The only thing you can assume is
that, by the time your custom data files are executed, all the
database tables already will have been created.
.. admonition:: Initial SQL data and testing
This technique *cannot* be used to provide initial data for
testing purposes. Django's test framework flushes the contents of
the test database after each test; as a result, any data added
using the custom SQL hook will be lost.
If you require data for a test case, you should add it using
either a :ref:`test fixture <topics-testing-fixtures>`, or
programmatically add it during the ``setUp()`` of your test case.
Database-backend-specific SQL data
----------------------------------
There's also a hook for backend-specific SQL data. For example, you
can have separate initial-data files for PostgreSQL and SQLite. For
each app, Django looks for a file called
``<app_label>/sql/<modelname>.<backend>.sql``, where ``<app_label>`` is
your app directory, ``<modelname>`` is the model's name in lowercase
and ``<backend>`` is the last part of the module name provided for the
:setting:`ENGINE <DATABASE-ENGINE>` in your settings file (e.g., if you have
defined a database with an :setting:`ENGINE <DATABASE-ENGINE>` value of
``django.db.backends.sqlite3``, Django will look for
``<app_label>/sql/<modelname>.sqlite3.sql``).
Backend-specific SQL data is executed before non-backend-specific SQL
data. For example, if your app contains the files ``sql/person.sql``
and ``sql/person.sqlite3.sql`` and you're installing the app on
SQLite, Django will execute the contents of
``sql/person.sqlite3.sql`` first, then ``sql/person.sql``.

View File

@ -543,9 +543,7 @@ Now, run :djadmin:`migrate` again to create those model tables in your database:
Apply all migrations: polls
Synchronizing apps without migrations:
Creating tables...
Installing custom SQL...
Installing indexes...
Installed 0 object(s) from 0 fixture(s)
Running migrations:
Applying polls.0001_initial... OK

View File

@ -84,9 +84,6 @@ given model module name(s).
.BI "sqlclear [" "app_label ..." "]"
Prints the DROP TABLE SQL statements for the given app name(s).
.TP
.BI "sqlcustom [" "app_label ..." "]"
Prints the custom SQL statements for the given app name(s).
.TP
.BI "sqlflush [" "app_label ..." "]"
Prints the SQL statements that would be executed for the "flush" command.
.TP

View File

@ -989,9 +989,6 @@ sqlall <app_label app_label ...>
Prints the CREATE TABLE and initial-data SQL statements for the given app name(s).
Refer to the description of :djadmin:`sqlcustom` for an explanation of how to
specify initial data.
The :djadminopt:`--database` option can be used to specify the database for
which to print the SQL.
@ -1012,30 +1009,6 @@ Prints the DROP TABLE SQL statements for the given app name(s).
The :djadminopt:`--database` option can be used to specify the database for
which to print the SQL.
sqlcustom <app_label app_label ...>
-----------------------------------
.. django-admin:: sqlcustom
Prints the custom SQL statements for the given app name(s).
For each model in each specified app, this command looks for the file
``<app_label>/sql/<modelname>.sql``, where ``<app_label>`` is the given app
name and ``<modelname>`` is the model's name in lowercase. For example, if you
have an app ``news`` that includes a ``Story`` model, ``sqlcustom`` will
attempt to read a file ``news/sql/story.sql`` and append it to the output of
this command.
Each of the SQL files, if given, is expected to contain valid SQL. The SQL
files are piped directly into the database after all of the models'
table-creation statements have been executed. Use this SQL hook to make any
table modifications, or insert any SQL functions into the database.
Note that the order in which the SQL files are processed is undefined.
The :djadminopt:`--database` option can be used to specify the database for
which to print the SQL.
sqldropindexes <app_label app_label ...>
----------------------------------------

View File

@ -1295,8 +1295,7 @@ The possible values for :attr:`~ForeignKey.on_delete` are found in
Take no action. If your database backend enforces referential
integrity, this will cause an :exc:`~django.db.IntegrityError` unless
you manually add an SQL ``ON DELETE`` constraint to the database field
(perhaps using :ref:`initial sql<initial-sql>`).
you manually add an SQL ``ON DELETE`` constraint to the database field.
.. attribute:: ForeignKey.swappable

View File

@ -738,7 +738,7 @@ Management Commands
* :djadmin:`collectstatic` command with symlink option is now supported on
Windows NT 6 (Windows Vista and newer).
* :ref:`initial-sql` now works better if the sqlparse_ Python library is
* Initial SQL data now works better if the sqlparse_ Python library is
installed.
Note that it's deprecated in favor of the
@ -1517,8 +1517,8 @@ Custom SQL location for models package
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Previously, if models were organized in a package (``myapp/models/``) rather
than simply ``myapp/models.py``, Django would look for :ref:`initial SQL data
<initial-sql>` in ``myapp/models/sql/``. This bug has been fixed so that Django
than simply ``myapp/models.py``, Django would look for initial SQL data in
``myapp/models/sql/``. This bug has been fixed so that Django
will search ``myapp/sql/`` as documented. After this issue was fixed, migrations
were added which deprecates initial SQL data. Thus, while this change still
exists, the deprecation is irrelevant as the entire feature will be removed in

View File

@ -585,7 +585,6 @@ Springmeyer
sql
sqlall
sqlclear
sqlcustom
sqldropindexes
sqlflush
sqlindexes

View File

@ -140,9 +140,7 @@ database to make sure they work as expected::
Apply all migrations: books
Synchronizing apps without migrations:
Creating tables...
Installing custom SQL...
Installing indexes...
Installed 0 object(s) from 0 fixture(s)
Running migrations:
Applying books.0003_auto... OK

View File

@ -984,15 +984,6 @@ The most straightforward way of creating a fixture is to use the
already have some data in your database. See the :djadmin:`dumpdata
documentation<dumpdata>` for more details.
.. admonition:: Initial SQL data and testing
Django provides a second way to insert initial data into models --
the :ref:`custom SQL hook <initial-sql>`. However, this technique
*cannot* be used to provide initial data for testing purposes.
Django's test framework flushes the contents of the test database
after each test; as a result, any data added using the custom SQL
hook will be lost.
Once you've created a fixture and placed it in a ``fixtures`` directory in one
of your :setting:`INSTALLED_APPS`, you can use it in your unit tests by
specifying a ``fixtures`` class attribute on your :class:`django.test.TestCase`

View File

@ -1386,7 +1386,6 @@ class CommandTypes(AdminScriptTestCase):
out, err = self.run_manage(args)
self.assertNoOutput(err)
self.assertOutput(out, "Checks the entire Django project for potential problems.")
self.assertEqual(out.count('optional arguments'), 1)
def test_color_style(self):
style = color.no_style()

View File

@ -78,7 +78,7 @@ class BashCompletionTests(unittest.TestCase):
"Subcommands can be autocompleted"
self._user_input('django-admin sql')
output = self._run_autocomplete()
self.assertEqual(output, ['sql sqlall sqlclear sqlcustom sqldropindexes sqlflush sqlindexes sqlmigrate sqlsequencereset'])
self.assertEqual(output, ['sql sqlall sqlclear sqldropindexes sqlflush sqlindexes sqlmigrate sqlsequencereset'])
def test_completed_subcommand(self):
"Show option flags in case a subcommand is completed"

View File

@ -13,10 +13,3 @@ class Article(models.Model):
class Meta:
app_label = 'fixtures_model_package'
ordering = ('-pub_date', 'headline')
class Book(models.Model):
name = models.CharField(max_length=100)
class Meta:
ordering = ('name',)

View File

@ -1,2 +0,0 @@
-- Deprecated search path for custom SQL -- remove in Django 1.9
INSERT INTO fixtures_model_package_book (name) VALUES ('My Deprecated Book');

View File

@ -1 +0,0 @@
INSERT INTO fixtures_model_package_book (name) VALUES ('My Book');

View File

@ -4,7 +4,6 @@ import warnings
from django.core import management
from django.test import TestCase
from django.utils.six import StringIO
from .models import Article
@ -66,18 +65,3 @@ class FixtureTestCase(TestCase):
],
lambda a: a.headline,
)
class InitialSQLTests(TestCase):
def test_custom_sql(self):
"""
#14300 -- Verify that custom_sql_for_model searches `app/sql` and not
`app/models/sql` (the old location will work until Django 1.9)
"""
out = StringIO()
management.call_command("sqlcustom", "fixtures_model_package", stdout=out)
output = out.getvalue()
self.assertIn("INSERT INTO fixtures_model_package_book (name) VALUES ('My Book')", output)
# value from deprecated search path models/sql (remove in Django 1.9)
self.assertIn("Deprecated Book", output)

View File

@ -1,9 +0,0 @@
"""
Regression tests for initial SQL insertion.
"""
from django.db import models
class Simple(models.Model):
name = models.CharField(max_length=50)

View File

@ -1,12 +0,0 @@
-- a comment
INSERT INTO initial_sql_regress_simple (name) VALUES ('John'); -- another comment
INSERT INTO initial_sql_regress_simple (name) VALUES ('-- Comment Man');
INSERT INTO initial_sql_regress_simple (name) VALUES ('Paul');
INSERT INTO
initial_sql_regress_simple (name) VALUES ('Ringo');
INSERT INTO initial_sql_regress_simple (name) VALUES ('George');
INSERT INTO initial_sql_regress_simple (name) VALUES ('Miles O''Brien');
INSERT INTO initial_sql_regress_simple (name) VALUES ('Semicolon;Man');
INSERT INTO initial_sql_regress_simple (name) VALUES ('"100%" of % are not placeholders');
INSERT INTO initial_sql_regress_simple (name) VALUES ('This line has a Windows line ending');

View File

@ -1,45 +0,0 @@
from django.core.management.color import no_style
from django.core.management.sql import custom_sql_for_model
from django.db import connections, DEFAULT_DB_ALIAS
from django.test import TestCase, override_settings
from .models import Simple
class InitialSQLTests(TestCase):
"""
The format of the included SQL file for this test suite is important.
It must end with a trailing newline in order to test the fix for #2161.
"""
def test_initial_sql(self):
"""
As pointed out by #14661, test data loaded by custom SQL
can't be relied upon; as a result, the test framework flushes the
data contents before every test. This test validates that this has
occurred.
"""
self.assertEqual(Simple.objects.count(), 0)
def test_custom_sql(self):
"""
Simulate the custom SQL loading by migrate.
"""
connection = connections[DEFAULT_DB_ALIAS]
custom_sql = custom_sql_for_model(Simple, no_style(), connection)
with connection.cursor() as cursor:
for sql in custom_sql:
cursor.execute(sql)
self.assertEqual(Simple.objects.count(), 9)
self.assertEqual(
Simple.objects.get(name__contains='placeholders').name,
'"100%" of % are not placeholders'
)
@override_settings(DEBUG=True)
def test_custom_sql_debug(self):
"""
Same test, ensure that CursorDebugWrapper doesn't alter sql loading
(#3485).
"""
self.test_custom_sql()