123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272 |
- 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
- def check_for_migrations(app_config, connection):
- # Inner import, else tests imports it too early as it needs settings
- from django.db.migrations.loader import MigrationLoader
- loader = MigrationLoader(connection)
- if app_config.label in loader.migrated_apps:
- raise CommandError(
- "App '%s' has migrations. Only the sqlmigrate and sqlflush commands "
- "can be used when an app has migrations." % app_config.label
- )
- def sql_create(app_config, style, connection):
- "Returns a list of the CREATE TABLE SQL statements for the given app."
- check_for_migrations(app_config, connection)
- if connection.settings_dict['ENGINE'] == 'django.db.backends.dummy':
- # This must be the "dummy" database backend, which means the user
- # hasn't set ENGINE for the database.
- raise CommandError(
- "Django doesn't know which syntax to use for your SQL statements,\n"
- "because you haven't properly specified the ENGINE setting for the database.\n"
- "see: https://docs.djangoproject.com/en/%s/ref/settings/#databases" % get_docs_version()
- )
- # Get installed models, so we generate REFERENCES right.
- # We trim models from the current app so that the sqlreset command does not
- # generate invalid SQL (leaving models out of known_models is harmless, so
- # we can be conservative).
- app_models = list(app_config.get_models(include_auto_created=True))
- final_output = []
- tables = connection.introspection.table_names()
- known_models = set(model for model in connection.introspection.installed_models(tables) if model not in app_models)
- pending_references = {}
- for model in router.get_migratable_models(app_config, connection.alias, include_auto_created=True):
- output, references = connection.creation.sql_create_model(model, style, known_models)
- final_output.extend(output)
- for refto, refs in references.items():
- pending_references.setdefault(refto, []).extend(refs)
- if refto in known_models:
- final_output.extend(connection.creation.sql_for_pending_references(refto, style, pending_references))
- final_output.extend(connection.creation.sql_for_pending_references(model, style, pending_references))
- # Keep track of the fact that we've created the table for this model.
- known_models.add(model)
- # Handle references to tables that are from other apps
- # but don't exist physically.
- not_installed_models = set(pending_references.keys())
- if not_installed_models:
- alter_sql = []
- for model in not_installed_models:
- alter_sql.extend('-- ' + sql for sql in
- connection.creation.sql_for_pending_references(model, style, pending_references))
- if alter_sql:
- final_output.append('-- The following references should be added but depend on non-existent tables:')
- final_output.extend(alter_sql)
- return final_output
- def sql_delete(app_config, style, connection, close_connection=True):
- "Returns a list of the DROP TABLE SQL statements for the given app."
- check_for_migrations(app_config, connection)
- # This should work even if a connection isn't available
- try:
- cursor = connection.cursor()
- except Exception:
- cursor = None
- try:
- # Figure out which tables already exist
- if cursor:
- table_names = connection.introspection.table_names(cursor)
- else:
- table_names = []
- output = []
- # Output DROP TABLE statements for standard application tables.
- to_delete = set()
- references_to_delete = {}
- app_models = router.get_migratable_models(app_config, connection.alias, include_auto_created=True)
- for model in app_models:
- if cursor and connection.introspection.table_name_converter(model._meta.db_table) in table_names:
- # The table exists, so it needs to be dropped
- opts = model._meta
- for f in opts.local_fields:
- if f.rel and f.rel.to not in to_delete:
- references_to_delete.setdefault(f.rel.to, []).append((model, f))
- to_delete.add(model)
- for model in app_models:
- if connection.introspection.table_name_converter(model._meta.db_table) in table_names:
- output.extend(connection.creation.sql_destroy_model(model, references_to_delete, style))
- finally:
- # Close database connection explicitly, in case this output is being piped
- # directly into a database client, to avoid locking issues.
- if cursor and close_connection:
- cursor.close()
- connection.close()
- if not output:
- output.append('-- App creates no tables in the database. Nothing to do.')
- return output[::-1] # Reverse it, to deal with table dependencies.
- def sql_flush(style, connection, only_django=False, reset_sequences=True, allow_cascade=False):
- """
- Returns a list of the SQL statements used to flush the database.
- If only_django is True, then only table names that have associated Django
- models and are in INSTALLED_APPS will be included.
- """
- if only_django:
- tables = connection.introspection.django_table_names(only_existing=True, include_views=False)
- else:
- tables = connection.introspection.table_names(include_views=False)
- seqs = connection.introspection.sequence_list() if reset_sequences else ()
- statements = connection.ops.sql_flush(style, tables, seqs, allow_cascade)
- 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."
- check_for_migrations(app_config, connection)
- output = []
- for model in router.get_migratable_models(app_config, connection.alias, include_auto_created=True):
- output.extend(connection.creation.sql_indexes_for_model(model, style))
- return output
- def sql_destroy_indexes(app_config, style, connection):
- "Returns a list of the DROP INDEX SQL statements for all models in the given app."
- check_for_migrations(app_config, connection)
- output = []
- for model in router.get_migratable_models(app_config, connection.alias, include_auto_created=True):
- output.extend(connection.creation.sql_destroy_indexes_for_model(model, style))
- return output
- def sql_all(app_config, style, connection):
- check_for_migrations(app_config, 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)
- )
- def _split_statements(content):
- # Private API only called from code that emits a RemovedInDjango19Warning.
- comment_re = re.compile(r"^((?:'[^']*'|[^'])*?)--.*$")
- statements = []
- statement = []
- for line in content.split("\n"):
- cleaned_line = comment_re.sub(r"\1", line).strip()
- if not cleaned_line:
- continue
- statement.append(cleaned_line)
- if cleaned_line.endswith(";"):
- statements.append(" ".join(statement))
- statement = []
- 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():
- if app_config.models_module is None:
- continue
- if verbosity >= 2:
- print("Running pre-migrate handlers for application %s" % app_config.label)
- models.signals.pre_migrate.send(
- sender=app_config,
- app_config=app_config,
- verbosity=verbosity,
- interactive=interactive,
- using=db)
- def emit_post_migrate_signal(verbosity, interactive, db):
- # Emit the post_migrate signal for every application.
- for app_config in apps.get_app_configs():
- if app_config.models_module is None:
- continue
- if verbosity >= 2:
- print("Running post-migrate handlers for application %s" % app_config.label)
- models.signals.post_migrate.send(
- sender=app_config,
- app_config=app_config,
- verbosity=verbosity,
- interactive=interactive,
- using=db)
|