diff --git a/TODO.TXT b/TODO.TXT new file mode 100644 index 0000000000..e4f2dd0e8c --- /dev/null +++ b/TODO.TXT @@ -0,0 +1,88 @@ +TODO +==== + +The follow is a list, more or less in the order I intend to do them of things +that need to be done. I'm trying to be as granular as possible. + +2) Update all old references to ``settings.DATABASE_*`` to reference + ``settings.DATABASES``. This includes the following locations + + * howto/custom-model-fields -- defered since it refers to custom model fields + * internals/contributing -- still needs an update on TEST_* + * ref/settings -- needs to be upddated for TEST_* + * topics/testing -- needs update for the TEST_* settings, plus test refactor + + The remaining items will be fixed as the code for them enters the code base. + + +3) Update all management commands in the following way: + + * ``dumpdata``: By default dump the ``default`` database. Later add a + ``--database`` flag. + * ``loaddata``: Leave as is, will use the standard mechanisms for + determining what DB to save the objects to. Should it get a + ``--database`` flag to overide that? + + These items will be fixed pending both community consensus, and the API + that will go in that's actually necessary for these to happen. + +4) Rig up the test harness to work with multiple databases. This includes: + + * Move all ``TEST_*`` settings to go in ``DATABASE_OPTIONS``. + * Figure out how we can actually test multiple databases. If the user has + more than one database in ``settings.DATABASES`` we can just use the test + database for each of them. Otherwise we are going to have to make some + assumptions. Either just go for SQLite, since that's going to be easiest + (and going forward it will be included in all versions of Python we work + with), or we can try to create a database with ``test_2_`` prefix. + Similar to how we use a ``test_`` prefix by default. +5) Add the ``using`` Meta option. Tests and docs(these are to be assumed at + each stage from here on out). +6) Add the ``using`` method to ``QuerySet``. This will more or less "just + work" across multiple databases that use the same backend. However, it + will fail gratuitously when trying to use 2 different backends. +7) Remove any references to the global ``django.db.connection`` object in the + SQL creation process. This includes(but is probably not limited to): + + * ``django.db.models.sql.where.Where`` + * ``django.db.models.sql.expressions.SQLEvaluator`` + * ``django.db.models.sql.query.Query`` uses ``connection`` in place of + ``self.connection`` in ``self.add_filter`` + * The way we create ``Query`` from ``BaseQuery`` is awkward and hacky. + * ``django.db.models.query.delete_objects`` + * ``django.db.models.query.insert_query`` + * ``django.db.models.base.Model`` + * ``django.db.models.fields.Field`` This uses it, as do it's subclasses. + * ``django.db.models.fields.related`` It's used all over the place here, + including opening a cursor and executing queries, so that's going to + need to be totally refactored. There's a ticket to at least move that + raw SQL and execution to ``Query``/``QuerySet`` so hopefully that makes + it in before I need to tackle this. +8) Implement some way to create a new ``Query`` for a different backend when + we switch. There are several checks against ``self.connection`` prior to + SQL construction, so we either need to defer all these(which will be + difficult, but probably not impossible), or have an alternate strategy, + such as: + + * Remove all tests against ``Query.connection`` before the SQL generation + phase. This involves changing the following methods: + + * ``Query.add_aggregate`` + * ``Query.set_group_by`` + * ``DateQuery.add_date_select`` + * ``Field.get_db_prep_lookup`` + * ``DateField.get_db_prep_value`` + * ``DateTimeField.get_db_prep_value`` + * ``DecimalField.get_db_prep_save`` + * ``TimeField.get_db_prep_value`` + * Implementing the command pattern. + * Having the ``QuerySet`` actually store ``Query`` objects for every + database in ``settings.DATABASES`` and do all the operations against + every single one of them, then when it's time excecute the query just + pick the right ``Query`` object to use. This *does* not scale, though it + could probably be done fairly easily. +9) Fix transaction support. In practice this means changing all the + dictionaries that currently map thread to a boolean to being a dictionary + mapping thread to a set of the affected DBs, and changing all the functions + that use these dictionaries to handle the changes appropriately. +10) Time permitting add support for a ``DatabaseManager``. diff --git a/django/core/management/commands/createcachetable.py b/django/core/management/commands/createcachetable.py index 48fe791f39..1678d57d58 100644 --- a/django/core/management/commands/createcachetable.py +++ b/django/core/management/commands/createcachetable.py @@ -8,7 +8,7 @@ class Command(LabelCommand): args = "" label = 'tablename' - options_list = LabelCommand.options_list + ( + option_list = LabelCommand.option_list + ( make_option('--database', action='store', dest='database', default='default', help='Selects what database to install the cache table to.'), ) @@ -16,7 +16,7 @@ class Command(LabelCommand): requires_model_validation = False def handle_label(self, tablename, **options): - alias = options['alias'] + alias = options['database'] connection = connections[alias] fields = ( # "key" is a reserved word in MySQL, so use "cache_key" instead. diff --git a/django/core/management/commands/dbshell.py b/django/core/management/commands/dbshell.py index 688403f023..2f49f5b3de 100644 --- a/django/core/management/commands/dbshell.py +++ b/django/core/management/commands/dbshell.py @@ -1,12 +1,21 @@ -from django.core.management.base import NoArgsCommand, CommandError +from optparse import make_option -class Command(NoArgsCommand): - help = "Runs the command-line client for the current DATABASE_ENGINE." +from django.core.management.base import BaseCommand, CommandError +from django.db import connections + +class Command(BaseCommand): + help = ("Runs the command-line client for specified database, or the " + "default database if none is provided.") + + option_list = BaseCommand.option_list + ( + make_option('--database', action='store', dest='database', + default='default', help='Selects what database to connection to.'), + ) requires_model_validation = False - def handle_noargs(self, **options): - from django.db import connection + def handle(self, **options): + connection = connections[options['database']] try: connection.client.runshell() except OSError: diff --git a/django/core/management/commands/flush.py b/django/core/management/commands/flush.py index 5d8a36e7a2..1b0218cf18 100644 --- a/django/core/management/commands/flush.py +++ b/django/core/management/commands/flush.py @@ -1,68 +1,77 @@ +from optparse import make_option + +from django.conf import settings +from django.db import connections, transaction, models +from django.core.management import call_command from django.core.management.base import NoArgsCommand, CommandError from django.core.management.color import no_style +from django.core.management.sql import sql_flush, emit_post_sync_signal from django.utils.importlib import import_module -from optparse import make_option + + class Command(NoArgsCommand): option_list = NoArgsCommand.option_list + ( make_option('--noinput', action='store_false', dest='interactive', default=True, help='Tells Django to NOT prompt the user for input of any kind.'), + make_option('--database', action='store', dest='database', + default='', help='Selects what database to flush.'), ) help = "Executes ``sqlflush`` on the current database." def handle_noargs(self, **options): - from django.conf import settings - from django.db import connection, transaction, models - from django.core.management.sql import sql_flush, emit_post_sync_signal - - verbosity = int(options.get('verbosity', 1)) - interactive = options.get('interactive') - - self.style = no_style() - - # Import the 'management' module within each installed app, to register - # dispatcher events. - for app_name in settings.INSTALLED_APPS: - try: - import_module('.management', app_name) - except ImportError: - pass - - sql_list = sql_flush(self.style, only_django=True) - - if interactive: - confirm = raw_input("""You have requested a flush of the database. -This will IRREVERSIBLY DESTROY all data currently in the %r database, -and return each table to the state it was in after syncdb. -Are you sure you want to do this? - - Type 'yes' to continue, or 'no' to cancel: """ % settings.DATABASE_NAME) + if not options['database']: + dbs = connections.all() else: - confirm = 'yes' + dbs = [options['database']] + for connection in dbs: + verbosity = int(options.get('verbosity', 1)) + interactive = options.get('interactive') - if confirm == 'yes': - try: - cursor = connection.cursor() - for sql in sql_list: - cursor.execute(sql) - except Exception, e: - transaction.rollback_unless_managed() - raise CommandError("""Database %s couldn't be flushed. Possible reasons: - * The database isn't running or isn't configured correctly. - * At least one of the expected database tables doesn't exist. - * The SQL was invalid. - Hint: Look at the output of 'django-admin.py sqlflush'. That's the SQL this command wasn't able to run. - The full error: %s""" % (settings.DATABASE_NAME, e)) - transaction.commit_unless_managed() + self.style = no_style() - # Emit the post sync signal. This allows individual - # applications to respond as if the database had been - # sync'd from scratch. - emit_post_sync_signal(models.get_models(), verbosity, interactive) + # Import the 'management' module within each installed app, to register + # dispatcher events. + for app_name in settings.INSTALLED_APPS: + try: + import_module('.management', app_name) + except ImportError: + pass - # Reinstall the initial_data fixture. - from django.core.management import call_command - call_command('loaddata', 'initial_data', **options) + sql_list = sql_flush(self.style, connection, only_django=True) - else: - print "Flush cancelled." + if interactive: + confirm = raw_input("""You have requested a flush of the database. + This will IRREVERSIBLY DESTROY all data currently in the %r database, + and return each table to the state it was in after syncdb. + Are you sure you want to do this? + + Type 'yes' to continue, or 'no' to cancel: """ % connection.settings_dict['DATABASE_NAME']) + else: + confirm = 'yes' + + if confirm == 'yes': + try: + cursor = connection.cursor() + for sql in sql_list: + cursor.execute(sql) + except Exception, e: + transaction.rollback_unless_managed() + raise CommandError("""Database %s couldn't be flushed. Possible reasons: + * The database isn't running or isn't configured correctly. + * At least one of the expected database tables doesn't exist. + * The SQL was invalid. + Hint: Look at the output of 'django-admin.py sqlflush'. That's the SQL this command wasn't able to run. + The full error: %s""" % (connection.settings_dict.DATABASE_NAME, e)) + transaction.commit_unless_managed() + + # Emit the post sync signal. This allows individual + # applications to respond as if the database had been + # sync'd from scratch. + emit_post_sync_signal(models.get_models(), verbosity, interactive, connection) + + # Reinstall the initial_data fixture. + call_command('loaddata', 'initial_data', **options) + + else: + print "Flush cancelled." diff --git a/django/core/management/commands/inspectdb.py b/django/core/management/commands/inspectdb.py index f30a00b7b2..fb22007f6a 100644 --- a/django/core/management/commands/inspectdb.py +++ b/django/core/management/commands/inspectdb.py @@ -1,20 +1,28 @@ +import keyword +from optparse import make_option + from django.core.management.base import NoArgsCommand, CommandError +from django.db import connections class Command(NoArgsCommand): help = "Introspects the database tables in the given database and outputs a Django model module." + option_list = NoArgsCommand.option_list + ( + make_option('--database', action='store', dest='database', + default='default', help='Selects what database to introspect.'), + ) + requires_model_validation = False def handle_noargs(self, **options): try: - for line in self.handle_inspection(): + for line in self.handle_inspection(options): print line except NotImplementedError: raise CommandError("Database inspection isn't supported for the currently selected database backend.") - def handle_inspection(self): - from django.db import connection - import keyword + def handle_inspection(self, options): + connection = connections[options['database']] table2model = lambda table_name: table_name.title().replace('_', '').replace(' ', '').replace('-', '') diff --git a/django/core/management/commands/reset.py b/django/core/management/commands/reset.py index 3e7ca9f689..005c533589 100644 --- a/django/core/management/commands/reset.py +++ b/django/core/management/commands/reset.py @@ -1,11 +1,17 @@ +from optparse import make_option + +from django.conf import settings from django.core.management.base import AppCommand, CommandError from django.core.management.color import no_style -from optparse import make_option +from django.core.management.sql import sql_reset +from django.db import connections, transaction class Command(AppCommand): option_list = AppCommand.option_list + ( make_option('--noinput', action='store_false', dest='interactive', default=True, help='Tells Django to NOT prompt the user for input of any kind.'), + make_option('--database', action='store', dest='database', + default='', help='Selects what database reset.'), ) help = "Executes ``sqlreset`` for the given app(s) in the current database." args = '[appname ...]' @@ -13,40 +19,41 @@ class Command(AppCommand): output_transaction = True def handle_app(self, app, **options): - from django.db import connection, transaction - from django.conf import settings - from django.core.management.sql import sql_reset - - app_name = app.__name__.split('.')[-2] - - self.style = no_style() - - sql_list = sql_reset(app, self.style) - - if options.get('interactive'): - confirm = raw_input(""" -You have requested a database reset. -This will IRREVERSIBLY DESTROY any data for -the "%s" application in the database "%s". -Are you sure you want to do this? - -Type 'yes' to continue, or 'no' to cancel: """ % (app_name, settings.DATABASE_NAME)) + if not options['database']: + dbs = connections.all() else: - confirm = 'yes' + dbs = [options['database']] + for connection in dbs: + app_name = app.__name__.split('.')[-2] - if confirm == 'yes': - try: - cursor = connection.cursor() - for sql in sql_list: - cursor.execute(sql) - except Exception, e: - transaction.rollback_unless_managed() - raise CommandError("""Error: %s couldn't be reset. Possible reasons: - * The database isn't running or isn't configured correctly. - * At least one of the database tables doesn't exist. - * The SQL was invalid. -Hint: Look at the output of 'django-admin.py sqlreset %s'. That's the SQL this command wasn't able to run. -The full error: %s""" % (app_name, app_name, e)) - transaction.commit_unless_managed() - else: - print "Reset cancelled." + self.style = no_style() + + sql_list = sql_reset(app, self.style, connection) + + if options.get('interactive'): + confirm = raw_input(""" + You have requested a database reset. + This will IRREVERSIBLY DESTROY any data for + the "%s" application in the database "%s". + Are you sure you want to do this? + + Type 'yes' to continue, or 'no' to cancel: """ % (app_name, connection.settings_dict['DATABASE_NAME'])) + else: + confirm = 'yes' + + if confirm == 'yes': + try: + cursor = connection.cursor() + for sql in sql_list: + cursor.execute(sql) + except Exception, e: + transaction.rollback_unless_managed() + raise CommandError("""Error: %s couldn't be reset. Possible reasons: + * The database isn't running or isn't configured correctly. + * At least one of the database tables doesn't exist. + * The SQL was invalid. + Hint: Look at the output of 'django-admin.py sqlreset %s'. That's the SQL this command wasn't able to run. + The full error: %s""" % (app_name, app_name, e)) + transaction.commit_unless_managed() + else: + print "Reset cancelled." diff --git a/django/core/management/commands/sql.py b/django/core/management/commands/sql.py index bc68a1e43a..f5f498c319 100644 --- a/django/core/management/commands/sql.py +++ b/django/core/management/commands/sql.py @@ -1,10 +1,18 @@ +from optparse import make_option + from django.core.management.base import AppCommand +from django.core.management.sql import sql_create +from django.db import connections class Command(AppCommand): help = "Prints the CREATE TABLE SQL statements for the given app name(s)." + option_list = AppCommand.option_list + ( + make_option('--database', action='store', dest='database', + default='default', help='Selects what database to print the SQL for.'), + ) + output_transaction = True def handle_app(self, app, **options): - from django.core.management.sql import sql_create - return u'\n'.join(sql_create(app, self.style)).encode('utf-8') + return u'\n'.join(sql_create(app, self.style, connections[options['database']])).encode('utf-8') diff --git a/django/core/management/commands/sqlall.py b/django/core/management/commands/sqlall.py index 5d510f179a..30af486083 100644 --- a/django/core/management/commands/sqlall.py +++ b/django/core/management/commands/sqlall.py @@ -1,10 +1,18 @@ +from optparse import make_option + from django.core.management.base import AppCommand +from django.core.management.sql import sql_all +from django.db import connections class Command(AppCommand): help = "Prints the CREATE TABLE, custom SQL and CREATE INDEX SQL statements for the given model module name(s)." + option_list = AppCommand.option_list + ( + make_option('--database', action='store', dest='database', + default='default', help='Selects what database to print the SQL for.'), + ) + output_transaction = True def handle_app(self, app, **options): - from django.core.management.sql import sql_all - return u'\n'.join(sql_all(app, self.style)).encode('utf-8') + return u'\n'.join(sql_all(app, self.style, connections[options['database']])).encode('utf-8') diff --git a/django/core/management/commands/sqlclear.py b/django/core/management/commands/sqlclear.py index 8550e88e28..cab048fc95 100644 --- a/django/core/management/commands/sqlclear.py +++ b/django/core/management/commands/sqlclear.py @@ -1,10 +1,18 @@ +from optparse import make_option + from django.core.management.base import AppCommand +from django.core.management.sql import sql_delete +from django.db import connections class Command(AppCommand): help = "Prints the DROP TABLE SQL statements for the given app name(s)." + option_list = AppCommand.option_list + ( + make_option('--database', action='store', dest='database', + default='default', help='Selects what database to print the SQL for.'), + ) + output_transaction = True def handle_app(self, app, **options): - from django.core.management.sql import sql_delete - return u'\n'.join(sql_delete(app, self.style)).encode('utf-8') + return u'\n'.join(sql_delete(app, self.style, connections[options['database']])).encode('utf-8') diff --git a/django/core/management/commands/sqlcustom.py b/django/core/management/commands/sqlcustom.py index 465330db7e..745535a8b7 100644 --- a/django/core/management/commands/sqlcustom.py +++ b/django/core/management/commands/sqlcustom.py @@ -1,10 +1,18 @@ +from optparse import make_option + from django.core.management.base import AppCommand +from django.core.management.sql import sql_custom +from django.db import connections class Command(AppCommand): help = "Prints the custom table modifying SQL statements for the given app name(s)." + option_list = AppCommand.option_list + ( + make_option('--database', action='store', dest='database', + default='default', help='Selects what database to print the SQL for.'), + ) + output_transaction = True def handle_app(self, app, **options): - from django.core.management.sql import sql_custom - return u'\n'.join(sql_custom(app, self.style)).encode('utf-8') + return u'\n'.join(sql_custom(app, self.style, connections[options['database']])).encode('utf-8') diff --git a/django/core/management/commands/sqlflush.py b/django/core/management/commands/sqlflush.py index d0f71d3875..f0ae11c37f 100644 --- a/django/core/management/commands/sqlflush.py +++ b/django/core/management/commands/sqlflush.py @@ -1,10 +1,18 @@ +from optparse import make_option + from django.core.management.base import NoArgsCommand +from django.core.management.sql import sql_flush +from django.db import connections class Command(NoArgsCommand): help = "Returns a list of the SQL statements required to return all tables in the database to the state they were in just after they were installed." + option_list = NoArgsCommand.option_list + ( + make_option('--database', action='store', dest='database', + default='default', help='Selects what database to print the SQL for.'), + ) + output_transaction = True def handle_noargs(self, **options): - from django.core.management.sql import sql_flush - return u'\n'.join(sql_flush(self.style, only_django=True)).encode('utf-8') + return u'\n'.join(sql_flush(self.style, connections[options['database']], only_django=True)).encode('utf-8') diff --git a/django/core/management/commands/sqlindexes.py b/django/core/management/commands/sqlindexes.py index 9693588a89..ead8c01dbf 100644 --- a/django/core/management/commands/sqlindexes.py +++ b/django/core/management/commands/sqlindexes.py @@ -1,10 +1,18 @@ +from optparse import make_option + from django.core.management.base import AppCommand +from django.core.management.sql import sql_indexes +from django.db import connections class Command(AppCommand): help = "Prints the CREATE INDEX SQL statements for the given model module name(s)." + option_list = AppCommand.option_list + ( + make_option('--database', action='store', dest='database', + default='default', help='Selects what database to print the SQL for.'), + ) + output_transaction = True def handle_app(self, app, **options): - from django.core.management.sql import sql_indexes - return u'\n'.join(sql_indexes(app, self.style)).encode('utf-8') + return u'\n'.join(sql_indexes(app, self.style, connections[options['database']])).encode('utf-8') diff --git a/django/core/management/commands/sqlreset.py b/django/core/management/commands/sqlreset.py index ec40848c42..4e3035084d 100644 --- a/django/core/management/commands/sqlreset.py +++ b/django/core/management/commands/sqlreset.py @@ -1,10 +1,18 @@ +from optparse import make_option + from django.core.management.base import AppCommand +from django.core.management.sql import sql_reset +from django.db import connections class Command(AppCommand): help = "Prints the DROP TABLE SQL, then the CREATE TABLE SQL, for the given app name(s)." + option_list = AppCommand.option_list + ( + make_option('--database', action='store', dest='database', + default='default', help='Selects what database to print the SQL for.'), + ) + output_transaction = True def handle_app(self, app, **options): - from django.core.management.sql import sql_reset - return u'\n'.join(sql_reset(app, self.style)).encode('utf-8') + return u'\n'.join(sql_reset(app, self.style, connections[options['database']])).encode('utf-8') diff --git a/django/core/management/commands/sqlsequencereset.py b/django/core/management/commands/sqlsequencereset.py index e8dad0bef6..9ade0eb37e 100644 --- a/django/core/management/commands/sqlsequencereset.py +++ b/django/core/management/commands/sqlsequencereset.py @@ -1,9 +1,18 @@ +from optparse import make_option + from django.core.management.base import AppCommand +from django.db import connections, models class Command(AppCommand): help = 'Prints the SQL statements for resetting sequences for the given app name(s).' + + option_list = AppCommand.option_list + ( + make_option('--database', action='store', dest='database', + default='default', help='Selects what database to print the SQL for.'), + ) + output_transaction = True def handle_app(self, app, **options): - from django.db import connection, models + connection = connections[options['database']] return u'\n'.join(connection.ops.sequence_reset_sql(self.style, models.get_models(app))).encode('utf-8') diff --git a/django/core/management/commands/syncdb.py b/django/core/management/commands/syncdb.py index fe51d45bb3..bc40e3cd8c 100644 --- a/django/core/management/commands/syncdb.py +++ b/django/core/management/commands/syncdb.py @@ -1,9 +1,13 @@ -from django.core.management.base import NoArgsCommand -from django.core.management.color import no_style -from django.utils.importlib import import_module from optparse import make_option import sys +from django.conf import settings +from django.core.management.base import NoArgsCommand +from django.core.management.color import no_style +from django.core.management.sql import custom_sql_for_model, emit_post_sync_signal +from django.db import connections, transaction, models +from django.utils.importlib import import_module + try: set except NameError: @@ -13,13 +17,12 @@ class Command(NoArgsCommand): option_list = NoArgsCommand.option_list + ( make_option('--noinput', action='store_false', dest='interactive', default=True, help='Tells Django to NOT prompt the user for input of any kind.'), + make_option('--database', action='store', dest='database', + default='', help='Selects what database to flush.'), ) help = "Create the database tables for all apps in INSTALLED_APPS whose tables haven't already been created." def handle_noargs(self, **options): - from django.db import connection, transaction, models - from django.conf import settings - from django.core.management.sql import custom_sql_for_model, emit_post_sync_signal verbosity = int(options.get('verbosity', 1)) interactive = options.get('interactive') @@ -27,124 +30,130 @@ class Command(NoArgsCommand): self.style = no_style() - # Import the 'management' module within each installed app, to register - # dispatcher events. - for app_name in settings.INSTALLED_APPS: - try: - import_module('.management', app_name) - except ImportError, exc: - # This is slightly hackish. We want to ignore ImportErrors - # if the "management" module itself is missing -- but we don't - # want to ignore the exception if the management module exists - # but raises an ImportError for some reason. The only way we - # can do this is to check the text of the exception. Note that - # we're a bit broad in how we check the text, because different - # Python implementations may not use the same text. - # CPython uses the text "No module named management" - # PyPy uses "No module named myproject.myapp.management" - msg = exc.args[0] - if not msg.startswith('No module named') or 'management' not in msg: - raise + if not options['database']: + dbs = connections.all() + else: + dbs = [options['database']] + for connection in dbs: - cursor = connection.cursor() + # Import the 'management' module within each installed app, to register + # dispatcher events. + for app_name in settings.INSTALLED_APPS: + try: + import_module('.management', app_name) + except ImportError, exc: + # This is slightly hackish. We want to ignore ImportErrors + # if the "management" module itself is missing -- but we don't + # want to ignore the exception if the management module exists + # but raises an ImportError for some reason. The only way we + # can do this is to check the text of the exception. Note that + # we're a bit broad in how we check the text, because different + # Python implementations may not use the same text. + # CPython uses the text "No module named management" + # PyPy uses "No module named myproject.myapp.management" + msg = exc.args[0] + if not msg.startswith('No module named') or 'management' not in msg: + raise - # Get a list of already installed *models* so that references work right. - tables = connection.introspection.table_names() - seen_models = connection.introspection.installed_models(tables) - created_models = set() - pending_references = {} + cursor = connection.cursor() - # Create the tables for each model - for app in models.get_apps(): - app_name = app.__name__.split('.')[-2] - model_list = models.get_models(app) - for model in model_list: - # Create the model's database table, if it doesn't already exist. - if verbosity >= 2: - print "Processing %s.%s model" % (app_name, model._meta.object_name) - if connection.introspection.table_name_converter(model._meta.db_table) in tables: - continue - sql, references = connection.creation.sql_create_model(model, self.style, seen_models) - seen_models.add(model) - 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, self.style, pending_references)) - sql.extend(connection.creation.sql_for_pending_references(model, self.style, pending_references)) - if verbosity >= 1 and sql: - print "Creating table %s" % model._meta.db_table - for statement in sql: - cursor.execute(statement) - tables.append(connection.introspection.table_name_converter(model._meta.db_table)) + # Get a list of already installed *models* so that references work right. + tables = connection.introspection.table_names() + seen_models = connection.introspection.installed_models(tables) + created_models = set() + pending_references = {} - # Create the m2m tables. This must be done after all tables have been created - # to ensure that all referred tables will exist. - for app in models.get_apps(): - app_name = app.__name__.split('.')[-2] - model_list = models.get_models(app) - for model in model_list: - if model in created_models: - sql = connection.creation.sql_for_many_to_many(model, self.style) - if sql: - if verbosity >= 2: - print "Creating many-to-many tables for %s.%s model" % (app_name, model._meta.object_name) - for statement in sql: - cursor.execute(statement) + # Create the tables for each model + for app in models.get_apps(): + app_name = app.__name__.split('.')[-2] + model_list = models.get_models(app) + for model in model_list: + # Create the model's database table, if it doesn't already exist. + if verbosity >= 2: + print "Processing %s.%s model" % (app_name, model._meta.object_name) + if connection.introspection.table_name_converter(model._meta.db_table) in tables: + continue + sql, references = connection.creation.sql_create_model(model, self.style, seen_models) + seen_models.add(model) + 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, self.style, pending_references)) + sql.extend(connection.creation.sql_for_pending_references(model, self.style, pending_references)) + if verbosity >= 1 and sql: + print "Creating table %s" % model._meta.db_table + for statement in sql: + cursor.execute(statement) + tables.append(connection.introspection.table_name_converter(model._meta.db_table)) - transaction.commit_unless_managed() + # Create the m2m tables. This must be done after all tables have been created + # to ensure that all referred tables will exist. + for app in models.get_apps(): + app_name = app.__name__.split('.')[-2] + model_list = models.get_models(app) + for model in model_list: + if model in created_models: + sql = connection.creation.sql_for_many_to_many(model, self.style) + if sql: + if verbosity >= 2: + print "Creating many-to-many tables for %s.%s model" % (app_name, model._meta.object_name) + for statement in sql: + cursor.execute(statement) - # Send the post_syncdb signal, so individual apps can do whatever they need - # to do at this point. - emit_post_sync_signal(created_models, verbosity, interactive) + transaction.commit_unless_managed() - # The connection may have been closed by a syncdb handler. - cursor = connection.cursor() + # Send the post_syncdb signal, so individual apps can do whatever they need + # to do at this point. + emit_post_sync_signal(created_models, verbosity, interactive, connection) - # Install custom SQL for the app (but only if this - # is a model we've just created) - for app in models.get_apps(): - app_name = app.__name__.split('.')[-2] - for model in models.get_models(app): - if model in created_models: - custom_sql = custom_sql_for_model(model, self.style) - if custom_sql: - if verbosity >= 1: - print "Installing custom SQL for %s.%s model" % (app_name, model._meta.object_name) - try: - for sql in custom_sql: - cursor.execute(sql) - except Exception, e: - sys.stderr.write("Failed to install custom SQL for %s.%s model: %s\n" % \ - (app_name, model._meta.object_name, e)) - if show_traceback: - import traceback - traceback.print_exc() - transaction.rollback_unless_managed() + # The connection may have been closed by a syncdb handler. + cursor = connection.cursor() + + # Install custom SQL for the app (but only if this + # is a model we've just created) + for app in models.get_apps(): + app_name = app.__name__.split('.')[-2] + for model in models.get_models(app): + if model in created_models: + custom_sql = custom_sql_for_model(model, self.style, connection) + if custom_sql: + if verbosity >= 1: + print "Installing custom SQL for %s.%s model" % (app_name, model._meta.object_name) + try: + for sql in custom_sql: + cursor.execute(sql) + except Exception, e: + sys.stderr.write("Failed to install custom SQL for %s.%s model: %s\n" % \ + (app_name, model._meta.object_name, e)) + if show_traceback: + import traceback + traceback.print_exc() + transaction.rollback_unless_managed() + else: + transaction.commit_unless_managed() else: - transaction.commit_unless_managed() - else: - if verbosity >= 2: - print "No custom SQL for %s.%s model" % (app_name, model._meta.object_name) - # Install SQL indicies for all newly created models - for app in models.get_apps(): - app_name = app.__name__.split('.')[-2] - for model in models.get_models(app): - if model in created_models: - index_sql = connection.creation.sql_indexes_for_model(model, self.style) - if index_sql: - if verbosity >= 1: - print "Installing index for %s.%s model" % (app_name, model._meta.object_name) - try: - for sql in index_sql: - cursor.execute(sql) - except Exception, e: - sys.stderr.write("Failed to install index for %s.%s model: %s\n" % \ - (app_name, model._meta.object_name, e)) - transaction.rollback_unless_managed() - else: - transaction.commit_unless_managed() + if verbosity >= 2: + print "No custom SQL for %s.%s model" % (app_name, model._meta.object_name) + # Install SQL indicies for all newly created models + for app in models.get_apps(): + app_name = app.__name__.split('.')[-2] + for model in models.get_models(app): + if model in created_models: + index_sql = connection.creation.sql_indexes_for_model(model, self.style) + if index_sql: + if verbosity >= 1: + print "Installing index for %s.%s model" % (app_name, model._meta.object_name) + try: + for sql in index_sql: + cursor.execute(sql) + except Exception, e: + sys.stderr.write("Failed to install index for %s.%s model: %s\n" % \ + (app_name, model._meta.object_name, e)) + transaction.rollback_unless_managed() + else: + transaction.commit_unless_managed() - # Install the 'initial_data' fixture, using format discovery - from django.core.management import call_command - call_command('loaddata', 'initial_data', verbosity=verbosity) + # Install the 'initial_data' fixture, using format discovery + from django.core.management import call_command + call_command('loaddata', 'initial_data', verbosity=verbosity) diff --git a/django/core/management/sql.py b/django/core/management/sql.py index 14fd3f8214..9591ce000a 100644 --- a/django/core/management/sql.py +++ b/django/core/management/sql.py @@ -1,18 +1,23 @@ -from django.core.management.base import CommandError import os import re +from django.conf import settings +from django.contrib.contenttypes import generic +from django.core.management.base import CommandError +from django.dispatch import dispatcher +from django.db import models +from django.db.models import get_models +from django.db.backends.util import truncate_name + try: set except NameError: from sets import Set as set # Python 2.3 fallback -def sql_create(app, style): +def sql_create(app, style, connection): "Returns a list of the CREATE TABLE SQL statements for the given app." - from django.db import connection, models - from django.conf import settings - if settings.DATABASE_ENGINE == 'dummy': + if connection.settings_dict['DATABASE_ENGINE'] == 'dummy': # This must be the "dummy" database backend, which means the user # hasn't set DATABASE_ENGINE. raise CommandError("Django doesn't know which syntax to use for your SQL statements,\n" + @@ -58,11 +63,8 @@ def sql_create(app, style): return final_output -def sql_delete(app, style): +def sql_delete(app, style, connection): "Returns a list of the DROP TABLE SQL statements for the given app." - from django.db import connection, models - from django.db.backends.util import truncate_name - from django.contrib.contenttypes import generic # This should work even if a connection isn't available try: @@ -112,18 +114,17 @@ def sql_delete(app, style): return output[::-1] # Reverse it, to deal with table dependencies. -def sql_reset(app, style): +def sql_reset(app, style, connection): "Returns a list of the DROP TABLE SQL, then the CREATE TABLE SQL, for the given module." return sql_delete(app, style) + sql_all(app, style) -def sql_flush(style, only_django=False): +def sql_flush(style, connection, only_django=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. """ - from django.db import connection if only_django: tables = connection.introspection.django_table_names(only_existing=True) else: @@ -131,35 +132,30 @@ def sql_flush(style, only_django=False): statements = connection.ops.sql_flush(style, tables, connection.introspection.sequence_list()) return statements -def sql_custom(app, style): +def sql_custom(app, style, connection): "Returns a list of the custom table modifying SQL statements for the given app." - from django.db.models import get_models output = [] app_models = get_models(app) app_dir = os.path.normpath(os.path.join(os.path.dirname(app.__file__), 'sql')) for model in app_models: - output.extend(custom_sql_for_model(model, style)) + output.extend(custom_sql_for_model(model, style, connection)) return output -def sql_indexes(app, style): +def sql_indexes(app, style, connection): "Returns a list of the CREATE INDEX SQL statements for all models in the given app." - from django.db import connection, models output = [] for model in models.get_models(app): output.extend(connection.creation.sql_indexes_for_model(model, style)) return output -def sql_all(app, style): +def sql_all(app, style, connection): "Returns a list of CREATE TABLE SQL, initial-data inserts, and CREATE INDEX SQL for the given module." - return sql_create(app, style) + sql_custom(app, style) + sql_indexes(app, style) - -def custom_sql_for_model(model, style): - from django.db import models - from django.conf import settings + return sql_create(app, style, connection) + sql_custom(app, style, connection) + sql_indexes(app, style, connection) +def custom_sql_for_model(model, style, connection): opts = model._meta app_dir = os.path.normpath(os.path.join(os.path.dirname(models.get_app(model._meta.app_label).__file__), 'sql')) output = [] @@ -177,7 +173,7 @@ def custom_sql_for_model(model, style): statements = re.compile(r";[ \t]*$", re.M) # Find custom SQL, if it's available. - sql_files = [os.path.join(app_dir, "%s.%s.sql" % (opts.object_name.lower(), settings.DATABASE_ENGINE)), + sql_files = [os.path.join(app_dir, "%s.%s.sql" % (opts.object_name.lower(), connection.settings_dict['DATABASE_ENGINE'])), os.path.join(app_dir, "%s.sql" % opts.object_name.lower())] for sql_file in sql_files: if os.path.exists(sql_file): @@ -192,9 +188,7 @@ def custom_sql_for_model(model, style): return output -def emit_post_sync_signal(created_models, verbosity, interactive): - from django.db import models - from django.dispatch import dispatcher +def emit_post_sync_signal(created_models, verbosity, interactive, connection): # Emit the post_sync signal for every application. for app in models.get_apps(): app_name = app.__name__.split('.')[-2] @@ -202,4 +196,4 @@ def emit_post_sync_signal(created_models, verbosity, interactive): print "Running post-sync handlers for application", app_name models.signals.post_syncdb.send(sender=app, app=app, created_models=created_models, verbosity=verbosity, - interactive=interactive) + interactive=interactive, connection=connection) diff --git a/django/db/utils.py b/django/db/utils.py index b5e6f1e2a8..384f26e7df 100644 --- a/django/db/utils.py +++ b/django/db/utils.py @@ -53,3 +53,6 @@ class ConnectionHandler(object): conn = backend.DatabaseWrapper(db) self._connections[alias] = conn return conn + + def all(self): + return [self[alias] for alias in self.databases] diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt index 71804cf022..3bb6f1c86f 100644 --- a/docs/ref/django-admin.txt +++ b/docs/ref/django-admin.txt @@ -122,6 +122,12 @@ createcachetable Creates a cache table named ``tablename`` for use with the database cache backend. See :ref:`topics-cache` for more information. +--database +~~~~~~~~~~ + +The alias for the database to install the cachetable to. By default uses the +``'default'`` alias. + createsuperuser --------------- @@ -168,6 +174,13 @@ the program name (``psql``, ``mysql``, ``sqlite3``) will find the program in the right place. There's no way to specify the location of the program manually. +--database +~~~~~~~~~~ + +The alias for the database to open the shell for. By default uses the +``'default'`` alias. + + diffsettings ------------ @@ -252,6 +265,12 @@ fixture will be re-installed. you sure?" confirmation messages. This is useful if ``django-admin.py`` is being executed as an unattended, automated script. +--database +~~~~~~~~~~ + +The alias for the database to flush. By default flushes all databases. + + inspectdb --------- @@ -293,6 +312,13 @@ needed. ``inspectdb`` works with PostgreSQL, MySQL and SQLite. Foreign-key detection only works in PostgreSQL and with certain types of MySQL tables. +--database +~~~~~~~~~~ + +The alias for the database to introspect. By default uses the ``'default'`` +alias. + + loaddata ------------------------------ @@ -448,6 +474,11 @@ Use the ``--noinput`` option to suppress all user prompting, such as "Are you sure?" confirmation messages. This is useful if ``django-admin.py`` is being executed as an unattended, automated script. +--database +~~~~~~~~~~ + +The alias for the database to reset. By default resets all databases. + runfcgi [options] ----------------- @@ -559,6 +590,13 @@ sql Prints the CREATE TABLE SQL statements for the given app name(s). +--database +~~~~~~~~~~ + +The alias for the database to print the SQL for. By default uses the +``'default'`` alias. + + sqlall ---------------------------- @@ -567,11 +605,23 @@ Prints the CREATE TABLE and initial-data SQL statements for the given app name(s Refer to the description of ``sqlcustom`` for an explanation of how to specify initial data. +--database +~~~~~~~~~~ + +The alias for the database to print the SQL for. By default uses the +``'default'`` alias. + sqlclear ------------------------------ Prints the DROP TABLE SQL statements for the given app name(s). +--database +~~~~~~~~~~ + +The alias for the database to print the SQL for. By default uses the +``'default'`` alias. + sqlcustom ------------------------------- @@ -591,21 +641,45 @@ table modifications, or insert any SQL functions into the database. Note that the order in which the SQL files are processed is undefined. +--database +~~~~~~~~~~ + +The alias for the database to print the SQL for. By default uses the +``'default'`` alias. + sqlflush -------- Prints the SQL statements that would be executed for the `flush`_ command. +--database +~~~~~~~~~~ + +The alias for the database to print the SQL for. By default uses the +``'default'`` alias. + sqlindexes -------------------------------- Prints the CREATE INDEX SQL statements for the given app name(s). +--database +~~~~~~~~~~ + +The alias for the database to print the SQL for. By default uses the +``'default'`` alias. + sqlreset ------------------------------ Prints the DROP TABLE SQL, then the CREATE TABLE SQL, for the given app name(s). +--database +~~~~~~~~~~ + +The alias for the database to print the SQL for. By default uses the +``'default'`` alias. + sqlsequencereset -------------------------------------- @@ -613,6 +687,12 @@ Prints the SQL statements for resetting sequences for the given app name(s). See http://simon.incutio.com/archive/2004/04/21/postgres for more information. +--database +~~~~~~~~~~ + +The alias for the database to print the SQL for. By default uses the +``'default'`` alias. + startapp ------------------ @@ -665,6 +745,12 @@ with an appropriate extension (e.g. ``json`` or ``xml``). See the documentation for ``loaddata`` for details on the specification of fixture data files. +--database +~~~~~~~~~~ + +The alias for the database install the tables for. By default uses the +``'default'`` alias. + --noinput ~~~~~~~~~