Ticket #6148: 6148-r12948.diff

File 6148-r12948.diff, 76.2 KB (added by Ramiro Morales, 14 years ago)

Path updated to r12948, tested with postgresql 8.3 and sqlite3

  • django/conf/global_settings.py

    diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py
    a b  
    140140DATABASE_HOST = ''             # Set to empty string for localhost. Not used with sqlite3.
    141141DATABASE_PORT = ''             # Set to empty string for default. Not used with sqlite3.
    142142DATABASE_OPTIONS = {}          # Set to empty dictionary for default.
     143DATABASE_SCHEMA = ''           # Set to empty string for default.
    143144
    144145# New format
    145146DATABASES = {
  • django/conf/project_template/settings.py

    diff --git a/django/conf/project_template/settings.py b/django/conf/project_template/settings.py
    a b  
    1717        'PASSWORD': '',                  # Not used with sqlite3.
    1818        'HOST': '',                      # Set to empty string for localhost. Not used with sqlite3.
    1919        'PORT': '',                      # Set to empty string for default. Not used with sqlite3.
     20        'SCHEMA': '',                    # Set to empty string for default.
    2021    }
    2122}
    2223
  • django/contrib/contenttypes/generic.py

    diff --git a/django/contrib/contenttypes/generic.py b/django/contrib/contenttypes/generic.py
    a b  
    131131    def m2m_reverse_name(self):
    132132        return self.rel.to._meta.pk.column
    133133
     134    def m2m_db_schema(self):
     135        return self.rel.to._meta.db_schema
     136
     137    def m2m_qualified_name(self):
     138        schema = self.m2m_db_schema()
     139        table = self.m2m_db_table()
     140        return connection.ops.prep_db_table(schema, table)
     141
    134142    def contribute_to_class(self, cls, name):
    135143        super(GenericRelation, self).contribute_to_class(cls, name)
    136144
  • django/core/management/commands/syncdb.py

    diff --git a/django/core/management/commands/syncdb.py b/django/core/management/commands/syncdb.py
    a b  
    88from django.db import connections, router, transaction, models, DEFAULT_DB_ALIAS
    99from django.utils.importlib import import_module
    1010
     11def log(s):
     12    l = open('/home/ramiro/django/t6148/salida.sql', 'a')
     13    try:
     14        l.write(s)
     15    finally:
     16        l.close()
    1117
    1218class Command(NoArgsCommand):
    1319    option_list = NoArgsCommand.option_list + (
     
    5056        connection = connections[db]
    5157        cursor = connection.cursor()
    5258
     59        #log('-- ===== DB: %s\n' % connection.settings_dict['NAME'])
    5360        # Get a list of already installed *models* so that references work right.
    54         tables = connection.introspection.table_names()
     61        schemas = connection.introspection.schema_names()
     62        if schemas:
     63            tables = []
     64            default_schema_name = connection.features.default_schema_name
     65            for schema in connection.introspection.schema_names():
     66               if default_schema_name and schema == default_schema_name:
     67                   sn = ''
     68               else:
     69                   sn = schema
     70               for tn in connection.introspection.schema_table_names(schema):
     71                   tables.append((sn, tn))
     72        else:
     73            tables = [('', tn) for tn in connection.introspection.table_names()]
     74        #tablesx = [x for x in tables]
     75        #tablesx.sort()
     76        #log('-- Creating tables list (%d): %s\n' % (len(tables), tablesx))
    5577        seen_models = connection.introspection.installed_models(tables)
     78        seen_schemas = set()
    5679        created_models = set()
    5780        pending_references = {}
    5881
     
    6386                if router.allow_syncdb(db, m)])
    6487            for app in models.get_apps()
    6588        )
     89
     90        def model_schema(model):
     91            db_schema = model._meta.db_schema
     92            if db_schema:
     93                db_schema = connection.introspection.table_name_converter(db_schema)
     94            return db_schema
     95
    6696        def model_installed(model):
    6797            opts = model._meta
    6898            converter = connection.introspection.table_name_converter
    69             return not ((converter(opts.db_table) in tables) or
    70                 (opts.auto_created and converter(opts.auto_created._meta.db_table) in tables))
     99            db_schema = model_schema(model)
     100            schema_table = (db_schema, converter(opts.db_table))
     101            return not ((schema_table in tables) or
     102                (opts.auto_created and \
     103                 (db_schema, converter(opts.auto_created._meta.db_table)) in tables)
     104                 #(model_schema(opts.auto_created), converter(opts.auto_created._meta.db_table)) in tables)
     105             )
    71106
    72107        manifest = dict(
    73108            (app_name, filter(model_installed, model_list))
     
    76111
    77112        # Create the tables for each model
    78113        for app_name, model_list in manifest.items():
     114            #log('-- app: %s\n' % app_name)
    79115            for model in model_list:
     116                # Add model-defined schema tables if any.
     117                db_schema = model_schema(model)
     118                #log('-- (schemas) db_schema: %s\n' % db_schema)
     119                if db_schema and db_schema not in seen_schemas:
     120                    #log('-- (schemas) appending to tables list: %s\n' % [(db_schema, tn) for tn in connection.introspection.schema_table_names(db_schema)])
     121                    tables += [(db_schema, tn) for tn in
     122                               connection.introspection.schema_table_names(db_schema)]
     123                    seen_schemas.add(db_schema)
     124
    80125                # Create the model's database table, if it doesn't already exist.
    81126                if verbosity >= 2:
    82127                    print "Processing %s.%s model" % (app_name, model._meta.object_name)
     
    89134                        sql.extend(connection.creation.sql_for_pending_references(refto, self.style, pending_references))
    90135                sql.extend(connection.creation.sql_for_pending_references(model, self.style, pending_references))
    91136                if verbosity >= 1 and sql:
    92                     print "Creating table %s" % model._meta.db_table
     137                    if db_schema:
     138                        print "Creating table %s.%s" % (db_schema, model._meta.db_table)
     139                    else:
     140                        print "Creating table %s" % model._meta.db_table
     141                #log('%s\n' % ''.join(sql))
    93142                for statement in sql:
    94143                    cursor.execute(statement)
    95                 tables.append(connection.introspection.table_name_converter(model._meta.db_table))
     144                if sql:
     145                    #log('-- appending to tables list: %s\n' % str((db_schema, connection.introspection.table_name_converter(model._meta.db_table))))
     146                    tables.append((db_schema, connection.introspection.table_name_converter(model._meta.db_table)))
     147                #tablesx2 = [x for x in tables]
     148                #tablesx2.sort()
     149                #log('-- now (%d): %s\n\n' % (len(tables), tablesx2))
    96150
    97151
    98152        transaction.commit_unless_managed(using=db)
  • django/core/management/sql.py

    diff --git a/django/core/management/sql.py b/django/core/management/sql.py
    a b  
    7171
    7272    # Figure out which tables already exist
    7373    if cursor:
    74         table_names = connection.introspection.get_table_list(cursor)
     74        table_names = [('', tn) for tn in
     75                       connection.introspection.get_table_list(cursor)]
    7576    else:
    7677        table_names = []
    7778
     
    8283
    8384    references_to_delete = {}
    8485    app_models = models.get_models(app, include_auto_created=True)
     86    seen_schemas = set()
    8587    for model in app_models:
    86         if cursor and connection.introspection.table_name_converter(model._meta.db_table) in table_names:
     88        db_schema = model._meta.db_schema
     89        # Find additional tables in model-defined schemas.
     90        if db_schema:
     91            db_schema = connection.introspection.schema_name_converter(db_schema)
     92            if db_schema not in seen_schemas:
     93                table_names += [(db_schema, tn) for tn in connection.introspection.get_schema_table_list(cursor, db_schema)]
     94                seen_schemas.add(db_schema)
     95        schema_table = (db_schema,
     96                        connection.introspection.table_name_converter(model._meta.db_table))
     97
     98        if cursor and schema_table in table_names:
    8799            # The table exists, so it needs to be dropped
    88100            opts = model._meta
    89101            for f in opts.local_fields:
     
    93105            to_delete.add(model)
    94106
    95107    for model in app_models:
    96         if connection.introspection.table_name_converter(model._meta.db_table) in table_names:
     108        db_schema = model._meta.db_schema
     109        if db_schema:
     110            db_schema = connection.introspection.schema_name_converter(db_schema)
     111        schema_table = (db_schema,
     112                        connection.introspection.table_name_converter(model._meta.db_table))
     113        if schema_table in table_names:
    97114            output.extend(connection.creation.sql_destroy_model(model, references_to_delete, style))
    98115
    99116    # Close database connection explicitly, in case this output is being piped
     
    118135    if only_django:
    119136        tables = connection.introspection.django_table_names(only_existing=True)
    120137    else:
    121         tables = connection.introspection.table_names()
     138        tables = [('', tn) for tn in connection.introspection.table_names()]
    122139    statements = connection.ops.sql_flush(style, tables, connection.introspection.sequence_list())
    123140    return statements
    124141
  • django/db/backends/__init__.py

    diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py
    a b  
    9696    # integer primary keys.
    9797    related_fields_match_type = False
    9898    allow_sliced_subqueries = True
     99    default_schema_name = ''
    99100
    100101class BaseDatabaseOperations(object):
    101102    """
     
    108109    def __init__(self):
    109110        self._cache = {}
    110111
    111     def autoinc_sql(self, table, column):
     112    def autoinc_sql(self, schema, table, column):
    112113        """
    113114        Returns any SQL needed to support auto-incrementing primary keys, or
    114115        None if no SQL is necessary.
     
    154155        """
    155156        return "DROP CONSTRAINT"
    156157
    157     def drop_sequence_sql(self, table):
     158    def drop_sequence_sql(self, schema, table):
    158159        """
    159160        Returns any SQL necessary to drop the sequence for the given table.
    160161        Returns None if no SQL is necessary.
     
    215216
    216217        return smart_unicode(sql) % u_params
    217218
    218     def last_insert_id(self, cursor, table_name, pk_name):
     219    def last_insert_id(self, cursor, schema_name, table_name, pk_name):
    219220        """
    220221        Given a cursor object that has just performed an INSERT statement into
    221222        a table that has an auto-incrementing ID, returns the newly created ID.
     
    289290        """
    290291        raise NotImplementedError()
    291292
     293    def prep_db_table(self, db_schema, db_table):
     294        """
     295        Prepares and formats the table name if necessary.
     296        Just returns quoted db_table if not supported.
     297        """
     298        return self.quote_name(db_table)
     299
     300    def prep_db_index(self, db_schema, db_index):
     301        """
     302        Prepares and formats the table index name if necessary.
     303        Just returns quoted db_index if not supported.
     304        """
     305        return self.quote_name(db_index)
     306
    292307    def random_function_sql(self):
    293308        """
    294309        Returns a SQL expression that returns a random value.
     
    488503        return name
    489504
    490505    def table_names(self):
    491         "Returns a list of names of all tables that exist in the database."
     506        "Returns a list of names of all tables that exist in the default schema."
    492507        cursor = self.connection.cursor()
    493508        return self.get_table_list(cursor)
    494509
     510    def schema_name_converter(self, name):
     511        """Apply a conversion to the name for the purposes of comparison.
     512
     513        The default schema name converter is for case sensitive comparison.
     514        """
     515        return name
     516
     517    def get_schema_list(self, cursor):
     518        "Returns a list of schemas that exist in the database"
     519        return []
     520
     521    def get_schema_table_list(self, cursor, schema):
     522        "Returns a list of tables in a specific schema"
     523        return []
     524
     525    def schema_names(self):
     526        cursor = self.connection.cursor()
     527        return self.get_schema_list(cursor)
     528
     529    def schema_table_names(self, schema):
     530        "Returns a list of names of all tables that exist in the database schema."
     531        cursor = self.connection.cursor()
     532        return self.get_schema_table_list(cursor, schema)
     533
    495534    def django_table_names(self, only_existing=False):
    496535        """
    497         Returns a list of all table names that have associated Django models and
    498         are in INSTALLED_APPS.
     536        Returns a list of tuples containing all schema and table names that
     537        have associated Django models and are in INSTALLED_APPS.
    499538
    500         If only_existing is True, the resulting list will only include the tables
    501         that actually exist in the database.
     539        If only_existing is True, the resulting list will only include the
     540        tables that actually exist in the database.
    502541        """
    503542        from django.db import models, router
    504543        tables = set()
    505         for app in models.get_apps():
    506             for model in models.get_models(app):
    507                 if not model._meta.managed:
    508                     continue
    509                 if not router.allow_syncdb(self.connection.alias, model):
    510                     continue
    511                 tables.add(model._meta.db_table)
    512                 tables.update([f.m2m_db_table() for f in model._meta.local_many_to_many])
    513544        if only_existing:
    514             tables = [t for t in tables if self.table_name_converter(t) in self.table_names()]
     545            existing_tables = set([('', tn) for tn in self.table_names()])
     546            seen_schemas = set()
     547        for model in models.get_models():
     548            if not model._meta.managed:
     549                continue
     550            if not router.allow_syncdb(self.connection.alias, model):
     551                continue
     552            db_schema = model._meta.db_schema
     553            if only_existing and db_schema and db_schema not in seen_schemas:
     554                existing_tables.update([(db_schema, tn) for tn in
     555                                        self.schema_table_names(db_schema)])
     556                seen_schemas.add(db_schema)
     557            tables.add((db_schema, model._meta.db_table))
     558            m2m_tables = []
     559            for f in model._meta.local_many_to_many:
     560                m2m_schema = f.m2m_db_schema()
     561                m2m_table = f.m2m_db_table()
     562                if only_existing and m2m_schema and m2m_schema not in seen_schemas:
     563                    existing_tables.update([(m2m_schema, tn) for tn in
     564                                        self.schema_table_names(m2m_schema)])
     565                    seen_schemas.add(m2m_schema)
     566                m2m_tables.append((m2m_schema, m2m_table))
     567            tables.update(m2m_tables)
     568        if only_existing:
     569            tables = [(s, t) for (s, t) in tables
     570                      if (self.schema_name_converter(s),
     571                          self.table_name_converter(t)) in existing_tables]
    515572        return tables
    516573
    517574    def installed_models(self, tables):
     
    541598                    continue
    542599                for f in model._meta.local_fields:
    543600                    if isinstance(f, models.AutoField):
    544                         sequence_list.append({'table': model._meta.db_table, 'column': f.column})
     601                        sequence_list.append({'table': model._meta.db_table,
     602                                              'column': f.column,
     603                                              'schema': model._meta.db_schema})
    545604                        break # Only one AutoField is allowed per model, so don't bother continuing.
    546605
    547606                for f in model._meta.local_many_to_many:
     607                    schema = f.m2m_db_schema()
    548608                    # If this is an m2m using an intermediate table,
    549609                    # we don't need to reset the sequence.
    550610                    if f.rel.through is None:
    551                         sequence_list.append({'table': f.m2m_db_table(), 'column': None})
     611                        sequence_list.append({'table': f.m2m_db_table(),
     612                                              'column': None,
     613                                              'schema': schema})
    552614
    553615        return sequence_list
    554616
  • django/db/backends/creation.py

    diff --git a/django/db/backends/creation.py b/django/db/backends/creation.py
    a b  
    3232        """
    3333        return '%x' % (abs(hash(args)) % 4294967296L)  # 2**32
    3434
     35    def default_schema(self):
     36        return ""
     37
     38    def sql_create_schema(self, schema, style):
     39        """"
     40        Returns the SQL required to create a single schema
     41        """
     42        qn = self.connection.ops.quote_name
     43        output = "%s %s;" % (style.SQL_KEYWORD('CREATE SCHEMA'), qn(schema))
     44        return output
     45
    3546    def sql_create_model(self, model, style, known_models=set()):
    3647        """
    3748        Returns the SQL required to create a single model, as a tuple of:
     
    8091            table_output.append(style.SQL_KEYWORD('UNIQUE') + ' (%s)' % \
    8192                ", ".join([style.SQL_FIELD(qn(opts.get_field(f).column)) for f in field_constraints]))
    8293
    83         full_statement = [style.SQL_KEYWORD('CREATE TABLE') + ' ' + style.SQL_TABLE(qn(opts.db_table)) + ' (']
     94        full_statement = [style.SQL_KEYWORD('CREATE TABLE') + ' ' + style.SQL_TABLE(opts.qualified_name) + ' (']
    8495        for i, line in enumerate(table_output): # Combine and add commas.
    8596            full_statement.append('    %s%s' % (line, i < len(table_output)-1 and ',' or ''))
    8697        full_statement.append(')')
     
    92103        if opts.has_auto_field:
    93104            # Add any extra SQL needed to support auto-incrementing primary keys.
    94105            auto_column = opts.auto_field.db_column or opts.auto_field.name
    95             autoinc_sql = self.connection.ops.autoinc_sql(opts.db_table, auto_column)
     106            autoinc_sql = self.connection.ops.autoinc_sql(opts.db_schema,
     107                                                          opts.db_table,
     108                                                          auto_column)
    96109            if autoinc_sql:
    97110                for stmt in autoinc_sql:
    98111                    final_output.append(stmt)
     
    104117        qn = self.connection.ops.quote_name
    105118        if field.rel.to in known_models:
    106119            output = [style.SQL_KEYWORD('REFERENCES') + ' ' + \
    107                 style.SQL_TABLE(qn(field.rel.to._meta.db_table)) + ' (' + \
     120                style.SQL_TABLE(field.rel.to._meta.qualified_name) + ' (' + \
    108121                style.SQL_FIELD(qn(field.rel.to._meta.get_field(field.rel.field_name).column)) + ')' +
    109122                self.connection.ops.deferrable_sql()
    110123            ]
     
    130143            for rel_class, f in pending_references[model]:
    131144                rel_opts = rel_class._meta
    132145                r_table = rel_opts.db_table
     146                r_qname = rel_opts.qualified_name
    133147                r_col = f.column
    134148                table = opts.db_table
     149                qname = opts.qualified_name
    135150                col = opts.get_field(f.rel.field_name).column
    136151                # For MySQL, r_name must be unique in the first 64 characters.
    137152                # So we are careful with character usage here.
    138153                r_name = '%s_refs_%s_%s' % (r_col, col, self._digest(r_table, table))
    139154                final_output.append(style.SQL_KEYWORD('ALTER TABLE') + ' %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s)%s;' % \
    140                     (qn(r_table), qn(truncate_name(r_name, self.connection.ops.max_name_length())),
    141                     qn(r_col), qn(table), qn(col),
     155                    (r_qname, qn(truncate_name(r_name, self.connection.ops.max_name_length())),
     156                    qn(r_col), qname, qn(col),
    142157                    self.connection.ops.deferrable_sql()))
    143158            del pending_references[model]
    144159        return final_output
     
    169184        from django.db.backends.util import truncate_name
    170185
    171186        output = []
    172         if f.auto_created:
     187        if f.rel.through._meta.auto_created:
    173188            opts = model._meta
    174189            qn = self.connection.ops.quote_name
    175190            tablespace = f.db_tablespace or opts.db_tablespace
     
    182197            else:
    183198                tablespace_sql = ''
    184199            table_output = [style.SQL_KEYWORD('CREATE TABLE') + ' ' + \
    185                 style.SQL_TABLE(qn(f.m2m_db_table())) + ' (']
     200                style.SQL_TABLE(qn(f.m2m_qualified_name())) + ' (']
    186201            table_output.append('    %s %s %s%s,' %
    187202                (style.SQL_FIELD(qn('id')),
    188203                style.SQL_COLTYPE(models.AutoField(primary_key=True).db_type(connection=self.connection)),
     
    214229                self.connection.ops.deferrable_sql()))
    215230
    216231            # Add any extra SQL needed to support auto-incrementing PKs
    217             autoinc_sql = self.connection.ops.autoinc_sql(f.m2m_db_table(), 'id')
     232            autoinc_sql = self.connection.ops.autoinc_sql(f.m2m_db_schema(),
     233                                                          f.m2m_db_table(),
     234                                                          'id')
    218235            if autoinc_sql:
    219236                for stmt in autoinc_sql:
    220237                    output.append(stmt)
     
    237254                (style.SQL_FIELD(qn(field.m2m_column_name())),
    238255                style.SQL_COLTYPE(models.ForeignKey(model).db_type(connection=self.connection)),
    239256                style.SQL_KEYWORD('NOT NULL REFERENCES'),
    240                 style.SQL_TABLE(qn(opts.db_table)),
     257                style.SQL_TABLE(opts.qualified_name),
    241258                style.SQL_FIELD(qn(opts.pk.column)),
    242259                self.connection.ops.deferrable_sql()),
    243260            '    %s %s %s %s (%s)%s,' %
    244261                (style.SQL_FIELD(qn(field.m2m_reverse_name())),
    245262                style.SQL_COLTYPE(models.ForeignKey(field.rel.to).db_type(connection=self.connection)),
    246263                style.SQL_KEYWORD('NOT NULL REFERENCES'),
    247                 style.SQL_TABLE(qn(field.rel.to._meta.db_table)),
     264                style.SQL_TABLE(field.rel.to._meta.qualified_name),
    248265                style.SQL_FIELD(qn(field.rel.to._meta.pk.column)),
    249266                self.connection.ops.deferrable_sql())
    250267        ]
     
    274291                    tablespace_sql = ''
    275292            else:
    276293                tablespace_sql = ''
     294            index_name = '%s_%s' % (model._meta.db_table, f.column)
     295            index_name = self.connection.ops.prep_db_index(model._meta.db_schema, index_name)
    277296            output = [style.SQL_KEYWORD('CREATE INDEX') + ' ' +
    278                 style.SQL_TABLE(qn('%s_%s' % (model._meta.db_table, f.column))) + ' ' +
     297                style.SQL_TABLE(index_name) + ' ' +
    279298                style.SQL_KEYWORD('ON') + ' ' +
    280                 style.SQL_TABLE(qn(model._meta.db_table)) + ' ' +
     299                style.SQL_TABLE(model._meta.qualified_name) + ' ' +
    281300                "(%s)" % style.SQL_FIELD(qn(f.column)) +
    282301                "%s;" % tablespace_sql]
    283302        else:
    284303            output = []
    285304        return output
    286305
     306    def sql_destroy_schema(self, schema, style):
     307        """"
     308        Returns the SQL required to destroy a single schema.
     309        """
     310        qn = self.connection.ops.quote_name
     311        output = "%s %s CASCADE;" % (style.SQL_KEYWORD('DROP SCHEMA IF EXISTS'), qn(schema))
     312        return output
     313
    287314    def sql_destroy_model(self, model, references_to_delete, style):
    288315        "Return the DROP TABLE and restraint dropping statements for a single model"
    289316        if not model._meta.managed or model._meta.proxy:
     
    291318        # Drop the table now
    292319        qn = self.connection.ops.quote_name
    293320        output = ['%s %s;' % (style.SQL_KEYWORD('DROP TABLE'),
    294                               style.SQL_TABLE(qn(model._meta.db_table)))]
     321                              style.SQL_TABLE(model._meta.qualified_name))]
    295322        if model in references_to_delete:
    296323            output.extend(self.sql_remove_table_constraints(model, references_to_delete, style))
    297324
    298325        if model._meta.has_auto_field:
    299             ds = self.connection.ops.drop_sequence_sql(model._meta.db_table)
     326            ds = self.connection.ops.drop_sequence_sql(model._meta.db_schema,
     327                                                       model._meta.db_table)
    300328            if ds:
    301329                output.append(ds)
    302330        return output
     
    310338        qn = self.connection.ops.quote_name
    311339        for rel_class, f in references_to_delete[model]:
    312340            table = rel_class._meta.db_table
     341            qname = rel_class._meta.qualified_name
    313342            col = f.column
    314343            r_table = model._meta.db_table
    315344            r_col = model._meta.get_field(f.rel.field_name).column
    316345            r_name = '%s_refs_%s_%s' % (col, r_col, self._digest(table, r_table))
    317346            output.append('%s %s %s %s;' % \
    318347                (style.SQL_KEYWORD('ALTER TABLE'),
    319                 style.SQL_TABLE(qn(table)),
     348                style.SQL_TABLE(qname),
    320349                style.SQL_KEYWORD(self.connection.ops.drop_foreignkey_sql()),
    321350                style.SQL_FIELD(qn(truncate_name(r_name, self.connection.ops.max_name_length())))))
    322351        del references_to_delete[model]
     
    332361
    333362        qn = self.connection.ops.quote_name
    334363        output = []
    335         if f.auto_created:
     364        if f.rel.through._meta.auto_created:
    336365            output.append("%s %s;" % (style.SQL_KEYWORD('DROP TABLE'),
    337                 style.SQL_TABLE(qn(f.m2m_db_table()))))
    338             ds = self.connection.ops.drop_sequence_sql("%s_%s" % (model._meta.db_table, f.column))
     366                style.SQL_TABLE(f.m2m_qualified_name())))
     367            ds = self.connection.ops.drop_sequence_sql(model._meta.db_schema,
     368                               "%s_%s" % (model._meta.db_table, f.column))
    339369            if ds:
    340370                output.append(ds)
    341371        return output
     
    348378        if verbosity >= 1:
    349379            print "Creating test database '%s'..." % self.connection.alias
    350380
    351         test_database_name = self._create_test_db(verbosity, autoclobber)
     381        schema_apps = self._get_app_with_schemas()
     382        schemas = self._get_schemas(schema_apps)
     383        test_database_name = self._create_test_db(verbosity, autoclobber, schemas)
    352384
    353385        self.connection.close()
    354386        self.connection.settings_dict["NAME"] = test_database_name
    355387        can_rollback = self._rollback_works()
    356388        self.connection.settings_dict["SUPPORTS_TRANSACTIONS"] = can_rollback
    357389
     390        # Create the test schemas.
     391        cursor = self.connection.cursor()
     392        self._create_test_schemas(verbosity, schemas, cursor)
     393
    358394        call_command('syncdb', verbosity=verbosity, interactive=False, database=self.connection.alias)
    359395
    360396        if settings.CACHE_BACKEND.startswith('db://'):
     
    368404
    369405        return test_database_name
    370406
    371     def _create_test_db(self, verbosity, autoclobber):
     407    def _create_test_schemas(self, verbosity, schemas, cursor):
     408        from django.core.management.color import no_style
     409        style = no_style()
     410        for schema in schemas:
     411            if verbosity >= 1:
     412                print "Creating schema %s" % schema
     413            cursor.execute(self.sql_create_schema(schema, style))
     414
     415    def _destroy_test_schemas(self, verbosity, schemas, cursor):
     416        from django.core.management.color import no_style
     417        style = no_style()
     418        for schema in schemas:
     419            if verbosity >= 1:
     420                print "Destroying schema %s" % schema
     421            cursor.execute(self.sql_destroy_schema(schema, style))
     422            if verbosity >= 1:
     423                print "Schema %s destroyed" % schema
     424
     425    def _get_schemas(self, apps):
     426        from django.db import models
     427        schemas = set()
     428        for app in apps:
     429            app_models = models.get_models(app)
     430            for model in app_models:
     431                schema = model._meta.db_schema
     432                if not schema or schema in schemas:
     433                    continue
     434                schemas.add(schema)
     435        return schemas
     436
     437    def _get_app_with_schemas(self):
     438        from django.db import models
     439        apps = models.get_apps()
     440        schema_apps = set()
     441        for app in apps:
     442            app_models = models.get_models(app)
     443            for model in app_models:
     444                schema = model._meta.db_schema
     445                if not schema or app in schema_apps:
     446                    continue
     447                schema_apps.add(app)
     448        return schema_apps
     449
     450    def _create_test_db(self, verbosity, autoclobber, schemas):
    372451        "Internal implementation - creates the test db tables."
    373452        suffix = self.sql_table_creation_suffix()
    374453
     
    394473                try:
    395474                    if verbosity >= 1:
    396475                        print "Destroying old test database..."
     476                    self._destroy_test_schemas(verbosity, schemas, cursor)
    397477                    cursor.execute("DROP DATABASE %s" % qn(test_database_name))
    398478                    if verbosity >= 1:
    399479                        print "Creating test database..."
  • django/db/backends/mysql/base.py

    diff --git a/django/db/backends/mysql/base.py b/django/db/backends/mysql/base.py
    a b  
    173173            return name # Quoting once is enough.
    174174        return "`%s`" % name
    175175
     176    def prep_db_table(self, db_schema, db_table):
     177        qn = self.quote_name
     178        if db_schema:
     179            return "%s.%s" % (qn(db_schema), qn(db_table))
     180        else:
     181            return qn(db_table)
     182
     183    def prep_db_index(self, db_schema, db_index):
     184        return self.prep_db_table(db_schema, db_index)
     185
    176186    def random_function_sql(self):
    177187        return 'RAND()'
    178188
     
    182192        # to clear all tables of all data
    183193        if tables:
    184194            sql = ['SET FOREIGN_KEY_CHECKS = 0;']
    185             for table in tables:
    186                 sql.append('%s %s;' % (style.SQL_KEYWORD('TRUNCATE'), style.SQL_FIELD(self.quote_name(table))))
     195            for (schema, table) in tables:
     196                sql.append('%s %s;' % (style.SQL_KEYWORD('TRUNCATE'), style.SQL_FIELD(self.prep_db_table(schema, table))))
    187197            sql.append('SET FOREIGN_KEY_CHECKS = 1;')
    188198
    189199            # 'ALTER TABLE table AUTO_INCREMENT = 1;'... style SQL statements
    190200            # to reset sequence indices
    191             sql.extend(["%s %s %s %s %s;" % \
    192                 (style.SQL_KEYWORD('ALTER'),
    193                  style.SQL_KEYWORD('TABLE'),
    194                  style.SQL_TABLE(self.quote_name(sequence['table'])),
    195                  style.SQL_KEYWORD('AUTO_INCREMENT'),
    196                  style.SQL_FIELD('= 1'),
    197                 ) for sequence in sequences])
     201            for sequence_info in sequences:
     202                schema_name = sequence_info['schema']
     203                table_name = self.prep_db_table(schema_name, sequence_info['table'])
     204                sql.append("%s %s %s %s %s;" % \
     205                           (style.SQL_KEYWORD('ALTER'),
     206                            style.SQL_KEYWORD('TABLE'),
     207                            style.SQL_TABLE(table_name),
     208                            style.SQL_KEYWORD('AUTO_INCREMENT'),
     209                            style.SQL_FIELD('= 1'),
     210                            ))
    198211            return sql
    199212        else:
    200213            return []
  • django/db/backends/mysql/creation.py

    diff --git a/django/db/backends/mysql/creation.py b/django/db/backends/mysql/creation.py
    a b  
    6363                field.rel.to._meta.db_table, field.rel.to._meta.pk.column)
    6464            ]
    6565        return table_output, deferred
     66
     67    def default_schema(self):
     68        return settings.DATABASE_NAME
     69
     70    def sql_create_schema(self, schema, style):
     71        """
     72        Returns the SQL required to create a single schema.
     73        In MySQL schemas are synonymous to databases
     74        """
     75        qn = self.connection.ops.quote_name
     76        output = "%s %s;" % (style.SQL_KEYWORD('CREATE DATABASE'), qn(schema))
     77        return output
     78
     79    def sql_destroy_schema(self, schema, style):
     80        """"
     81        Returns the SQL required to destroy a single schema.
     82        """
     83        qn = self.connection.ops.quote_name
     84        output = "%s %s;" % (style.SQL_KEYWORD('DROP DATABASE IF EXISTS'), qn(schema))
     85        return output
  • django/db/backends/mysql/introspection.py

    diff --git a/django/db/backends/mysql/introspection.py b/django/db/backends/mysql/introspection.py
    a b  
    3333        cursor.execute("SHOW TABLES")
    3434        return [row[0] for row in cursor.fetchall()]
    3535
     36    def get_schema_list(self, cursor):
     37        cursor.execute("SHOW SCHEMAS")
     38        return [row[0] for row in cursor.fetchall()]
     39
     40    def get_schema_table_list(self, cursor, schema):
     41        cursor.execute("SHOW TABLES FROM %s" % self.connection.ops.quote_name(schema))
     42        return [schema + "." + row[0] for row in cursor.fetchall()]
     43
    3644    def get_table_description(self, cursor, table_name):
    3745        "Returns a description of the table, with the DB-API cursor.description interface."
    3846        cursor.execute("SELECT * FROM %s LIMIT 1" % self.connection.ops.quote_name(table_name))
  • django/db/backends/oracle/base.py

    diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py
    a b  
    5858class DatabaseOperations(BaseDatabaseOperations):
    5959    compiler_module = "django.db.backends.oracle.compiler"
    6060
    61     def autoinc_sql(self, table, column):
     61    def autoinc_sql(self, schema, table, column):
    6262        # To simulate auto-incrementing primary keys in Oracle, we have to
    6363        # create a sequence and a trigger.
    6464        sq_name = get_sequence_name(table)
    6565        tr_name = get_trigger_name(table)
    66         tbl_name = self.quote_name(table)
     66        tbl_name = self.prep_db_table(schema, table)
     67        sq_qname = self.prep_db_table(schema, sq_name)
     68        tr_qname = self.prep_db_table(schema, tr_name)
    6769        col_name = self.quote_name(column)
    6870        sequence_sql = """
    6971DECLARE
     
    7274    SELECT COUNT(*) INTO i FROM USER_CATALOG
    7375        WHERE TABLE_NAME = '%(sq_name)s' AND TABLE_TYPE = 'SEQUENCE';
    7476    IF i = 0 THEN
    75         EXECUTE IMMEDIATE 'CREATE SEQUENCE "%(sq_name)s"';
     77        EXECUTE IMMEDIATE 'CREATE SEQUENCE %(sq_qname)s';
    7678    END IF;
    7779END;
    7880/""" % locals()
    7981        trigger_sql = """
    80 CREATE OR REPLACE TRIGGER "%(tr_name)s"
     82CREATE OR REPLACE TRIGGER %(tr_qname)s
    8183BEFORE INSERT ON %(tbl_name)s
    8284FOR EACH ROW
    8385WHEN (new.%(col_name)s IS NULL)
    8486    BEGIN
    85         SELECT "%(sq_name)s".nextval
     87        SELECT %(sq_qname)s.nextval
    8688        INTO :new.%(col_name)s FROM dual;
    8789    END;
    8890/""" % locals()
     
    159161    def deferrable_sql(self):
    160162        return " DEFERRABLE INITIALLY DEFERRED"
    161163
    162     def drop_sequence_sql(self, table):
    163         return "DROP SEQUENCE %s;" % self.quote_name(get_sequence_name(table))
     164    def drop_sequence_sql(self, schema, table):
     165        sequence_name = self.prep_db_table(schema, get_sequence_name(table))
     166        return "DROP SEQUENCE %s;" % sequence_name
    164167
    165168    def fetch_returned_insert_id(self, cursor):
    166169        return long(cursor._insert_id_var.getvalue())
     
    171174        else:
    172175            return "%s"
    173176
    174     def last_insert_id(self, cursor, table_name, pk_name):
    175         sq_name = get_sequence_name(table_name)
    176         cursor.execute('SELECT "%s".currval FROM dual' % sq_name)
     177    def last_insert_id(self, cursor, schema_name, table_name, pk_name):
     178        sq_name = self.prep_db_table(schema_name, get_sequence_name(table_name))
     179        cursor.execute('SELECT %s.currval FROM dual' % sq_name)
    177180        return cursor.fetchone()[0]
    178181
    179182    def lookup_cast(self, lookup_type):
     
    184187    def max_name_length(self):
    185188        return 30
    186189
     190    def prep_db_table(self, db_schema, db_table):
     191        qn = self.quote_name
     192        if db_schema:
     193            return "%s.%s" % (qn(db_schema), qn(db_table))
     194        else:
     195            return qn(db_table)
     196
     197    def prep_db_index(self, db_schema, db_index):
     198        return self.prep_db_table(db_schema, db_index)
     199
    187200    def prep_for_iexact_query(self, x):
    188201        return x
    189202
     
    234247    def sql_flush(self, style, tables, sequences):
    235248        # Return a list of 'TRUNCATE x;', 'TRUNCATE y;',
    236249        # 'TRUNCATE z;'... style SQL statements
     250        sql = []
    237251        if tables:
    238252            # Oracle does support TRUNCATE, but it seems to get us into
    239253            # FK referential trouble, whereas DELETE FROM table works.
    240             sql = ['%s %s %s;' % \
    241                     (style.SQL_KEYWORD('DELETE'),
    242                      style.SQL_KEYWORD('FROM'),
    243                      style.SQL_FIELD(self.quote_name(table)))
    244                     for table in tables]
     254            for schema, table in tables:
     255                table = self.prep_db_table(schema, table)
     256                sql.append('%s %s %s;' % \
     257                           (style.SQL_KEYWORD('DELETE'),
     258                            style.SQL_KEYWORD('FROM'),
     259                            style.SQL_FIELD(table)))
    245260            # Since we've just deleted all the rows, running our sequence
    246261            # ALTER code will reset the sequence to 0.
    247262            for sequence_info in sequences:
    248                 sequence_name = get_sequence_name(sequence_info['table'])
    249                 table_name = self.quote_name(sequence_info['table'])
     263                schema_name = sequence_info['schema']
     264                sequence_name = self.prep_db_table(schema_name,
     265                                    get_sequence_name(sequence_info['table']))
     266                table_name = self.prep_db_table(schema_name,
     267                                                sequence_info['table'])
    250268                column_name = self.quote_name(sequence_info['column'] or 'id')
    251269                query = _get_sequence_reset_sql() % {'sequence': sequence_name,
    252270                                                     'table': table_name,
    253271                                                     'column': column_name}
    254272                sql.append(query)
    255             return sql
    256         else:
    257             return []
     273        return sql
    258274
    259275    def sequence_reset_sql(self, style, model_list):
    260276        from django.db import models
     
    263279        for model in model_list:
    264280            for f in model._meta.local_fields:
    265281                if isinstance(f, models.AutoField):
    266                     table_name = self.quote_name(model._meta.db_table)
    267                     sequence_name = get_sequence_name(model._meta.db_table)
     282                    table_name = model._meta.qualified_name
     283                    sequence_name = self.prep_db_table(model._meta.db_schema,
     284                                       get_sequence_name(model._meta.db_table))
    268285                    column_name = self.quote_name(f.column)
    269286                    output.append(query % {'sequence': sequence_name,
    270287                                           'table': table_name,
     
    274291                    break
    275292            for f in model._meta.many_to_many:
    276293                if not f.rel.through:
    277                     table_name = self.quote_name(f.m2m_db_table())
    278                     sequence_name = get_sequence_name(f.m2m_db_table())
     294                    table_name = self.quote_name(f.m2m_qualified_name())
     295                    sequence_name = self.prep_db_table(f.m2m_db_schema(),
     296                                           get_sequence_name(f.m2m_db_table()))
    279297                    column_name = self.quote_name('id')
    280298                    output.append(query % {'sequence': sequence_name,
    281299                                           'table': table_name,
     
    619637BEGIN
    620638    LOCK TABLE %(table)s IN SHARE MODE;
    621639    SELECT NVL(MAX(%(column)s), 0) INTO startvalue FROM %(table)s;
    622     SELECT "%(sequence)s".nextval INTO cval FROM dual;
     640    SELECT %(sequence)s.nextval INTO cval FROM dual;
    623641    cval := startvalue - cval;
    624642    IF cval != 0 THEN
    625         EXECUTE IMMEDIATE 'ALTER SEQUENCE "%(sequence)s" MINVALUE 0 INCREMENT BY '||cval;
    626         SELECT "%(sequence)s".nextval INTO cval FROM dual;
    627         EXECUTE IMMEDIATE 'ALTER SEQUENCE "%(sequence)s" INCREMENT BY 1';
     643        EXECUTE IMMEDIATE 'ALTER SEQUENCE %(sequence)s MINVALUE 0 INCREMENT BY '||cval;
     644        SELECT %(sequence)s.nextval INTO cval FROM dual;
     645        EXECUTE IMMEDIATE 'ALTER SEQUENCE %(sequence)s INCREMENT BY 1';
    628646    END IF;
    629647    COMMIT;
    630648END;
  • django/db/backends/oracle/creation.py

    diff --git a/django/db/backends/oracle/creation.py b/django/db/backends/oracle/creation.py
    a b  
    3939        'URLField':                     'VARCHAR2(%(max_length)s)',
    4040    }
    4141
     42    def sql_create_schema(self, schema, style, password=None,
     43                          tablespace=None, temp_tablespace=None):
     44        qn = self.connection.ops.quote_name
     45        lock_account = (password is None)
     46        if lock_account:
     47            password = schema
     48        output = []
     49        output.append("%s %s %s %s" % (style.SQL_KEYWORD('CREATE USER'),
     50                                       qn(schema),
     51                                       style.SQL_KEYWORD('IDENTIFIED BY'),
     52                                       qn(password)))
     53        if tablespace:
     54            output.append("%s %s" % (style.SQL_KEYWORD('DEFAULT TABLESPACE'),
     55                                     qn(tablespace)))
     56        if temp_tablespace:
     57            output.append("%s %s" % (style.SQL_KEYWORD('TEMPORARY TABLESPACE'),
     58                                     qn(temp_tablespace)))
     59        if lock_account:
     60            output.append(style.SQL_KEYWORD('ACCOUNT LOCK'))
     61        return '\n'.join(output)
     62
     63    def sql_destroy_schema(self, schema, style):
     64        qn = self.connection.ops.quote_name
     65        return "%s %s %s" % (style.SQL_KEYWORD('DROP USER'), qn(schema),
     66                             style.SQL_KEYWORD('CASCADE'))
     67
    4268    remember = {}
    4369
    44     def _create_test_db(self, verbosity=1, autoclobber=False):
     70    def _create_test_db(self, verbosity=1, autoclobber=False, schema):
    4571        TEST_NAME = self._test_database_name()
    4672        TEST_USER = self._test_database_user()
    4773        TEST_PASSWD = self._test_database_passwd()
     
    174200               DEFAULT TABLESPACE %(tblspace)s
    175201               TEMPORARY TABLESPACE %(tblspace_temp)s
    176202            """,
    177             """GRANT CONNECT, RESOURCE TO %(user)s""",
     203            """GRANT CONNECT, RESOURCE, CREATE USER, DROP USER, CREATE ANY TABLE, ALTER ANY TABLE, CREATE ANY INDEX, CREATE ANY SEQUENCE, CREATE ANY TRIGGER, SELECT ANY TABLE, INSERT ANY TABLE, UPDATE ANY TABLE, DELETE ANY TABLE TO %(user)s""",
    178204        ]
    179205        self._execute_statements(cursor, statements, parameters, verbosity)
    180206
  • django/db/backends/oracle/introspection.py

    diff --git a/django/db/backends/oracle/introspection.py b/django/db/backends/oracle/introspection.py
    a b  
    119119        for row in cursor.fetchall():
    120120            indexes[row[0]] = {'primary_key': row[1], 'unique': row[2]}
    121121        return indexes
     122
     123    def schema_name_converter(self, name):
     124        """Convert to lowercase for case-sensitive schema name comparison."""
     125        return name.lower()
     126
     127    def get_schema_list(self, cursor):
     128        "Returns a list of schemas that exist in the database."
     129        sql = """
     130        select distinct username
     131        from all_users, all_objects
     132        where username = owner
     133        """
     134        cursor.execute(sql)
     135        return [schema.lower() for (schema,) in cursor]
     136
     137    def get_schema_table_list(self, cursor, schema):
     138        "Returns a list of tables in a specific schema."
     139        sql = """
     140        select table_name from all_tables
     141        where owner = upper(%s)
     142        """
     143        cursor.execute(sql, [schema])
     144        return [table.lower() for (table,) in cursor]
  • django/db/backends/postgresql/base.py

    diff --git a/django/db/backends/postgresql/base.py b/django/db/backends/postgresql/base.py
    a b  
    8080
    8181class DatabaseFeatures(BaseDatabaseFeatures):
    8282    uses_savepoints = True
     83    default_schema_name = u'public'
    8384
    8485class DatabaseWrapper(BaseDatabaseWrapper):
    8586    operators = {
  • django/db/backends/postgresql/creation.py

    diff --git a/django/db/backends/postgresql/creation.py b/django/db/backends/postgresql/creation.py
    a b  
    5050                tablespace_sql = ''
    5151
    5252            def get_index_sql(index_name, opclass=''):
     53                index_name = self.connection.ops.prep_db_index(model._meta.db_schema, index_name)
    5354                return (style.SQL_KEYWORD('CREATE INDEX') + ' ' +
    54                         style.SQL_TABLE(qn(index_name)) + ' ' +
     55                        style.SQL_TABLE(index_name) + ' ' +
    5556                        style.SQL_KEYWORD('ON') + ' ' +
    56                         style.SQL_TABLE(qn(db_table)) + ' ' +
     57                        style.SQL_TABLE(model._meta.qualified_name) + ' ' +
    5758                        "(%s%s)" % (style.SQL_FIELD(qn(f.column)), opclass) +
    5859                        "%s;" % tablespace_sql)
    5960
  • django/db/backends/postgresql/introspection.py

    diff --git a/django/db/backends/postgresql/introspection.py b/django/db/backends/postgresql/introspection.py
    a b  
    3131                AND pg_catalog.pg_table_is_visible(c.oid)""")
    3232        return [row[0] for row in cursor.fetchall()]
    3333
     34    def get_schema_list(self, cursor):
     35        cursor.execute("""
     36            SELECT DISTINCT n.nspname
     37            FROM pg_catalog.pg_class c
     38            LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
     39            WHERE c.relkind IN ('r', 'v', '')
     40            AND n.nspname NOT IN ('pg_catalog', 'pg_toast', 'information_schema')""")
     41        return [row[0] for row in cursor.fetchall()]
     42
     43    def get_schema_table_list(self, cursor, schema):
     44        cursor.execute("""
     45            SELECT c.relname
     46            FROM pg_catalog.pg_class c
     47            LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
     48            WHERE c.relkind IN ('r', 'v', '')
     49                AND n.nspname = '%s'""" % schema)
     50        return [row[0] for row in cursor.fetchall()]
     51
    3452    def get_table_description(self, cursor, table_name):
    3553        "Returns a description of the table, with the DB-API cursor.description interface."
    3654        cursor.execute("SELECT * FROM %s LIMIT 1" % self.connection.ops.quote_name(table_name))
  • django/db/backends/postgresql/operations.py

    diff --git a/django/db/backends/postgresql/operations.py b/django/db/backends/postgresql/operations.py
    a b  
    5353            return 'HOST(%s)'
    5454        return '%s'
    5555
    56     def last_insert_id(self, cursor, table_name, pk_name):
    57         cursor.execute("SELECT CURRVAL('\"%s_%s_seq\"')" % (table_name, pk_name))
     56    def last_insert_id(self, cursor, schema_name, table_name, pk_name):
     57        sequence_name = '%s_%s_seq' % (table_name, pk_name)
     58        sequence_name = self.prep_db_table(schema_name, sequence_name)
     59        cursor.execute("SELECT CURRVAL('%s')" % sequence_name)
    5860        return cursor.fetchone()[0]
    5961
    6062    def no_limit_value(self):
     
    6567            return name # Quoting once is enough.
    6668        return '"%s"' % name
    6769
     70    def prep_db_table(self, db_schema, db_table):
     71        qn = self.quote_name
     72        if db_schema:
     73            return "%s.%s" % (qn(db_schema), qn(db_table))
     74        else:
     75            return qn(db_table)
     76
    6877    def sql_flush(self, style, tables, sequences):
    6978        if tables:
     79            qnames = [self.prep_db_table(schema, table)
     80                      for (schema, table) in tables]
    7081            if self.postgres_version[0:2] >= (8,1):
    7182                # Postgres 8.1+ can do 'TRUNCATE x, y, z...;'. In fact, it *has to*
    7283                # in order to be able to truncate tables referenced by a foreign
     
    7485                # statement.
    7586                sql = ['%s %s;' % \
    7687                    (style.SQL_KEYWORD('TRUNCATE'),
    77                      style.SQL_FIELD(', '.join([self.quote_name(table) for table in tables]))
     88                     style.SQL_FIELD(', '.join(qnames))
    7889                )]
    7990            else:
    8091                # Older versions of Postgres can't do TRUNCATE in a single call, so
     
    8293                sql = ['%s %s %s;' % \
    8394                        (style.SQL_KEYWORD('DELETE'),
    8495                         style.SQL_KEYWORD('FROM'),
    85                          style.SQL_FIELD(self.quote_name(table))
    86                          ) for table in tables]
     96                         style.SQL_FIELD(qname)
     97                         ) for qname in qnames]
    8798
    8899            # 'ALTER SEQUENCE sequence_name RESTART WITH 1;'... style SQL statements
    89100            # to reset sequence indices
    90101            for sequence_info in sequences:
     102                schema_name = sequence_info['schema']
    91103                table_name = sequence_info['table']
    92104                column_name = sequence_info['column']
    93105                if column_name and len(column_name) > 0:
    94106                    sequence_name = '%s_%s_seq' % (table_name, column_name)
    95107                else:
    96108                    sequence_name = '%s_id_seq' % table_name
     109                sequence_name = self.prep_db_table(schema_name, sequence_name)
    97110                sql.append("%s setval('%s', 1, false);" % \
    98111                    (style.SQL_KEYWORD('SELECT'),
    99                     style.SQL_FIELD(self.quote_name(sequence_name)))
     112                    style.SQL_FIELD(sequence_name))
    100113                )
    101114            return sql
    102115        else:
     
    112125            # if there are records (as the max pk value is already in use), otherwise set it to false.
    113126            for f in model._meta.local_fields:
    114127                if isinstance(f, models.AutoField):
     128                    sequence_name = qn('%s_%s_seq' % (model._meta.db_table,
     129                                                      f.column)) # XXX: generic schemas support
     130                    sequence_name = self.prep_db_table(model._meta.db_schema,
     131                                                       sequence_name)
    115132                    output.append("%s setval('%s', coalesce(max(%s), 1), max(%s) %s null) %s %s;" % \
    116133                        (style.SQL_KEYWORD('SELECT'),
    117                         style.SQL_FIELD(qn('%s_%s_seq' % (model._meta.db_table, f.column))),
     134                        style.SQL_FIELD(sequence_name),
    118135                        style.SQL_FIELD(qn(f.column)),
    119136                        style.SQL_FIELD(qn(f.column)),
    120137                        style.SQL_KEYWORD('IS NOT'),
    121138                        style.SQL_KEYWORD('FROM'),
    122                         style.SQL_TABLE(qn(model._meta.db_table))))
     139                        style.SQL_TABLE(model._meta.qualified_name)))
    123140                    break # Only one AutoField is allowed per model, so don't bother continuing.
    124141            for f in model._meta.many_to_many:
    125142                if not f.rel.through:
     143                    sequence_name = qn('%s_id_seq' % f.m2m_db_table()) # XXX: generic schemas support
     144                    sequence_name = self.prep_db_table(f.m2m_db_schema(),
     145                                                       sequence_name)
    126146                    output.append("%s setval('%s', coalesce(max(%s), 1), max(%s) %s null) %s %s;" % \
    127147                        (style.SQL_KEYWORD('SELECT'),
    128                         style.SQL_FIELD(qn('%s_id_seq' % f.m2m_db_table())),
     148                        style.SQL_FIELD(sequence_name),
    129149                        style.SQL_FIELD(qn('id')),
    130150                        style.SQL_FIELD(qn('id')),
    131151                        style.SQL_KEYWORD('IS NOT'),
    132152                        style.SQL_KEYWORD('FROM'),
    133                         style.SQL_TABLE(qn(f.m2m_db_table()))))
     153                        style.SQL_TABLE(qn(f.m2m_qualified_name()))))
    134154        return output
    135155
    136156    def savepoint_create_sql(self, sid):
  • django/db/backends/postgresql_psycopg2/base.py

    diff --git a/django/db/backends/postgresql_psycopg2/base.py b/django/db/backends/postgresql_psycopg2/base.py
    a b  
    6767class DatabaseFeatures(BaseDatabaseFeatures):
    6868    needs_datetime_string_cast = False
    6969    can_return_id_from_insert = False
     70    default_schema_name = u'public'
    7071
    7172class DatabaseOperations(PostgresqlDatabaseOperations):
    7273    def last_executed_query(self, cursor, sql, params):
  • django/db/backends/sqlite3/base.py

    diff --git a/django/db/backends/sqlite3/base.py b/django/db/backends/sqlite3/base.py
    a b  
    9898                (style.SQL_KEYWORD('DELETE'),
    9999                 style.SQL_KEYWORD('FROM'),
    100100                 style.SQL_FIELD(self.quote_name(table))
    101                  ) for table in tables]
     101                 ) for (_, table) in tables]
    102102        # Note: No requirement for reset of auto-incremented indices (cf. other
    103103        # sql_flush() implementations). Just return SQL at this point
    104104        return sql
  • django/db/backends/sqlite3/creation.py

    diff --git a/django/db/backends/sqlite3/creation.py b/django/db/backends/sqlite3/creation.py
    a b  
    3838        "SQLite3 doesn't support constraints"
    3939        return []
    4040
    41     def _create_test_db(self, verbosity, autoclobber):
     41    def _create_test_db(self, verbosity, autoclobber, schemas):
    4242        test_database_name = self.connection.settings_dict['TEST_NAME']
    4343        if test_database_name and test_database_name != ":memory:":
    4444            # Erase the old test database
  • django/db/models/fields/related.py

    diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
    a b  
    877877        if isinstance(self.rel.to, basestring):
    878878            target = self.rel.to
    879879        else:
    880             target = self.rel.to._meta.db_table
     880            target = self.rel.to._meta.qualified_name
    881881        cls._meta.duplicate_targets[self.column] = (target, "o2m")
    882882
    883883    def contribute_to_related_class(self, cls, related):
     
    966966        to = to.lower()
    967967    meta = type('Meta', (object,), {
    968968        'db_table': field._get_m2m_db_table(klass._meta),
     969        'db_schema': field._get_m2m_db_schema(klass._meta),
    969970        'managed': managed,
    970971        'auto_created': klass,
    971972        'app_label': klass._meta.app_label,
     
    995996            through=kwargs.pop('through', None))
    996997
    997998        self.db_table = kwargs.pop('db_table', None)
     999        self.db_schema = kwargs.pop('db_schema', '')
    9981000        if kwargs['rel'].through is not None:
    9991001            assert self.db_table is None, "Cannot specify a db_table if an intermediary model is used."
    10001002
     
    10161018            return util.truncate_name('%s_%s' % (opts.db_table, self.name),
    10171019                                      connection.ops.max_name_length())
    10181020
     1021    def _get_m2m_db_schema(self, opts):
     1022        "Function that can be curried to provide the m2m schema name for this relation"
     1023        if self.rel.through is not None and self.rel.through._meta.db_schema:
     1024            return self.rel.through._meta.db_schema
     1025        return self.db_schema
     1026
     1027    def _get_m2m_qualified_name(self, opts):
     1028        "Function that can be curried to provide the qualified m2m table name for this relation"
     1029        schema = self._get_m2m_db_schema(opts)
     1030        table = self._get_m2m_db_table(opts)
     1031        return connection.ops.prep_db_table(schema, table)
     1032
    10191033    def _get_m2m_attr(self, related, attr):
    10201034        "Function that can be curried to provide the source accessor or DB column name for the m2m table"
    10211035        cache_attr = '_m2m_%s_cache' % attr
     
    11051119
    11061120        # Set up the accessor for the m2m table name for the relation
    11071121        self.m2m_db_table = curry(self._get_m2m_db_table, cls._meta)
     1122        self.m2m_db_schema = curry(self._get_m2m_db_schema, cls._meta)
     1123        self.m2m_qualified_name = curry(self._get_m2m_qualified_name,
     1124                                        cls._meta)
    11081125
    11091126        # Populate some necessary rel arguments so that cross-app relations
    11101127        # work correctly.
     
    11161133        if isinstance(self.rel.to, basestring):
    11171134            target = self.rel.to
    11181135        else:
    1119             target = self.rel.to._meta.db_table
     1136            target = self.rel.to._meta.qualified_name
    11201137        cls._meta.duplicate_targets[self.column] = (target, "m2m")
    11211138
    11221139    def contribute_to_related_class(self, cls, related):
  • django/db/models/options.py

    diff --git a/django/db/models/options.py b/django/db/models/options.py
    a b  
    2121DEFAULT_NAMES = ('verbose_name', 'db_table', 'ordering',
    2222                 'unique_together', 'permissions', 'get_latest_by',
    2323                 'order_with_respect_to', 'app_label', 'db_tablespace',
    24                  'abstract', 'managed', 'proxy', 'auto_created')
     24                 'abstract', 'managed', 'proxy', 'auto_created', 'db_schema')
    2525
    2626class Options(object):
    2727    def __init__(self, meta, app_label=None):
     
    3030        self.module_name, self.verbose_name = None, None
    3131        self.verbose_name_plural = None
    3232        self.db_table = ''
     33        self.db_schema = settings.DATABASE_SCHEMA
     34        self.qualified_name = ''
    3335        self.ordering = []
    3436        self.unique_together =  []
    3537        self.permissions =  []
     
    104106            self.db_table = "%s_%s" % (self.app_label, self.module_name)
    105107            self.db_table = truncate_name(self.db_table, connection.ops.max_name_length())
    106108
     109        # Construct qualified table name.
     110        self.qualified_name = connection.ops.prep_db_table(self.db_schema,
     111                                                           self.db_table)
     112        if self.qualified_name == connection.ops.quote_name(self.db_table):
     113            # If unchanged, the backend doesn't support schemas.
     114            self.db_schema = ''
    107115
    108116    def _prepare(self, model):
    109117        if self.order_with_respect_to:
     
    177185        self.pk = target._meta.pk
    178186        self.proxy_for_model = target
    179187        self.db_table = target._meta.db_table
     188        self.db_schema = target._meta.db_schema
     189        self.qualified_name = target._meta.qualified_name
    180190
    181191    def __repr__(self):
    182192        return '<Options for %s>' % self.object_name
  • django/db/models/sql/compiler.py

    diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py
    a b  
    2121        might not have all the pieces in place at that time.
    2222        """
    2323        if not self.query.tables:
    24             self.query.join((None, self.query.model._meta.db_table, None, None))
     24            self.query.join((None, self.query.model._meta.qualified_name, None, None))
    2525        if (not self.query.select and self.query.default_cols and not
    2626                self.query.included_inherited_models):
    2727            self.query.setup_inherited_models()
     
    248248                        alias = start_alias
    249249                    else:
    250250                        link_field = opts.get_ancestor_link(model)
    251                         alias = self.query.join((start_alias, model._meta.db_table,
     251                        alias = self.query.join((start_alias, model._meta.qualified_name,
    252252                                link_field.column, model._meta.pk.column))
    253253                    seen[model] = alias
    254254            else:
     
    449449                result.append('%s%s%s' % (connector, qn(name), alias_str))
    450450            first = False
    451451        for t in self.query.extra_tables:
    452             alias, unused = self.query.table_alias(t)
     452            alias, unused = self.query.table_alias(qn(t))
    453453            # Only add the alias if it's not already present (the table_alias()
    454454            # calls increments the refcount, so an alias refcount of one means
    455455            # this is the only reference.
     
    529529            # what "used" specifies).
    530530            avoid = avoid_set.copy()
    531531            dupe_set = orig_dupe_set.copy()
    532             table = f.rel.to._meta.db_table
     532            table = f.rel.to._meta.qualified_name
    533533            promote = nullable or f.null
    534534            if model:
    535535                int_opts = opts
     
    550550                                ())
    551551                        dupe_set.add((opts, lhs_col))
    552552                    int_opts = int_model._meta
    553                     alias = self.query.join((alias, int_opts.db_table, lhs_col,
     553                    alias = self.query.join((alias, int_opts.qualified_name, lhs_col,
    554554                            int_opts.pk.column), exclusions=used,
    555555                            promote=promote)
    556556                    alias_chain.append(alias)
     
    602602                # what "used" specifies).
    603603                avoid = avoid_set.copy()
    604604                dupe_set = orig_dupe_set.copy()
    605                 table = model._meta.db_table
     605                table = model._meta.qualified_name
    606606
    607607                int_opts = opts
    608608                alias = root_alias
     
    625625                            dupe_set.add((opts, lhs_col))
    626626                        int_opts = int_model._meta
    627627                        alias = self.query.join(
    628                             (alias, int_opts.db_table, lhs_col, int_opts.pk.column),
     628                            (alias, int_opts.qualified_name, lhs_col, int_opts.pk.column),
    629629                            exclusions=used, promote=True, reuse=used
    630630                        )
    631631                        alias_chain.append(alias)
     
    766766        # going to be column names (so we can avoid the extra overhead).
    767767        qn = self.connection.ops.quote_name
    768768        opts = self.query.model._meta
    769         result = ['INSERT INTO %s' % qn(opts.db_table)]
     769        result = ['INSERT INTO %s' % opts.qualified_name]
    770770        result.append('(%s)' % ', '.join([qn(c) for c in self.query.columns]))
    771771        values = [self.placeholder(*v) for v in self.query.values]
    772772        result.append('VALUES (%s)' % ', '.join(values))
    773773        params = self.query.params
    774774        if self.return_id and self.connection.features.can_return_id_from_insert:
    775             col = "%s.%s" % (qn(opts.db_table), qn(opts.pk.column))
     775            col = "%s.%s" % (opts.qualified_name, qn(opts.pk.column))
    776776            r_fmt, r_params = self.connection.ops.return_insert_id()
    777777            result.append(r_fmt % col)
    778778            params = params + r_params
     
    786786        if self.connection.features.can_return_id_from_insert:
    787787            return self.connection.ops.fetch_returned_insert_id(cursor)
    788788        return self.connection.ops.last_insert_id(cursor,
    789                 self.query.model._meta.db_table, self.query.model._meta.pk.column)
     789                self.query.model._meta.db_schema, self.query.model._meta.db_table,
     790                self.query.model._meta.pk.column)
    790791
    791792
    792793class SQLDeleteCompiler(SQLCompiler):
  • django/db/models/sql/query.py

    diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py
    a b  
    587587        Callback used by deferred_to_columns(). The "target" parameter should
    588588        be a set instance.
    589589        """
    590         table = model._meta.db_table
     590        table = model._meta.qualified_name
    591591        if table not in target:
    592592            target[table] = set()
    593593        for field in fields:
    594594            target[table].add(field.column)
    595595
    596596
     597
    597598    def table_alias(self, table_name, create=False):
    598599        """
    599600        Returns a table alias for the given table_name and whether this is a
     
    769770            alias = self.tables[0]
    770771            self.ref_alias(alias)
    771772        else:
    772             alias = self.join((None, self.model._meta.db_table, None, None))
     773            alias = self.join((None, self.model._meta.qualified_name,
     774                               None, None))
    773775        return alias
    774776
    775777    def count_active_tables(self):
     
    882884                    seen[model] = root_alias
    883885                else:
    884886                    link_field = opts.get_ancestor_link(model)
    885                     seen[model] = self.join((root_alias, model._meta.db_table,
     887                    seen[model] = self.join((root_alias,
     888                            model._meta.qualified_name,
    886889                            link_field.column, model._meta.pk.column))
    887890        self.included_inherited_models = seen
    888891
     
    12071210                                    (id(opts), lhs_col), ()))
    12081211                            dupe_set.add((opts, lhs_col))
    12091212                        opts = int_model._meta
    1210                         alias = self.join((alias, opts.db_table, lhs_col,
    1211                                 opts.pk.column), exclusions=exclusions)
     1213                        alias = self.join((alias, opts.qualified_name,
     1214                                           lhs_col, opts.pk.column),
     1215                                          exclusions=exclusions)
    12121216                        joins.append(alias)
    12131217                        exclusions.add(alias)
    12141218                        for (dupe_opts, dupe_col) in dupe_set:
     
    12331237                        (table1, from_col1, to_col1, table2, from_col2,
    12341238                                to_col2, opts, target) = cached_data
    12351239                    else:
    1236                         table1 = field.m2m_db_table()
     1240                        table1 = field.m2m_qualified_name()
    12371241                        from_col1 = opts.pk.column
    12381242                        to_col1 = field.m2m_column_name()
    12391243                        opts = field.rel.to._meta
    1240                         table2 = opts.db_table
     1244                        table2 = opts.qualified_name
    12411245                        from_col2 = field.m2m_reverse_name()
    12421246                        to_col2 = opts.pk.column
    12431247                        target = opts.pk
     
    12641268                    else:
    12651269                        opts = field.rel.to._meta
    12661270                        target = field.rel.get_related_field()
    1267                         table = opts.db_table
     1271                        table = opts.qualified_name
    12681272                        from_col = field.column
    12691273                        to_col = target.column
    12701274                        orig_opts._join_cache[name] = (table, from_col, to_col,
     
    12861290                        (table1, from_col1, to_col1, table2, from_col2,
    12871291                                to_col2, opts, target) = cached_data
    12881292                    else:
    1289                         table1 = field.m2m_db_table()
     1293                        table1 = field.m2m_qualified_name()
    12901294                        from_col1 = opts.pk.column
    12911295                        to_col1 = field.m2m_reverse_name()
    12921296                        opts = orig_field.opts
    1293                         table2 = opts.db_table
     1297                        table2 = opts.qualified_name
    12941298                        from_col2 = field.m2m_column_name()
    12951299                        to_col2 = opts.pk.column
    12961300                        target = opts.pk
     
    13131317                        local_field = opts.get_field_by_name(
    13141318                                field.rel.field_name)[0]
    13151319                        opts = orig_field.opts
    1316                         table = opts.db_table
     1320                        table = opts.qualified_name
    13171321                        from_col = local_field.column
    13181322                        to_col = field.column
    13191323                        target = opts.pk
     
    15691573        else:
    15701574            opts = self.model._meta
    15711575            if not self.select:
    1572                 count = self.aggregates_module.Count((self.join((None, opts.db_table, None, None)), opts.pk.column),
     1576                count = self.aggregates_module.Count((self.join((None,
     1577                           opts.qualified_name, None, None)), opts.pk.column),
    15731578                                         is_summary=True, distinct=True)
    15741579            else:
    15751580                # Because of SQL portability issues, multi-column, distinct
  • django/db/models/sql/subqueries.py

    diff --git a/django/db/models/sql/subqueries.py b/django/db/models/sql/subqueries.py
    a b  
    3838            field = self.model._meta.pk
    3939            where.add((Constraint(None, field.column, field), 'in',
    4040                    pk_list[offset : offset + GET_ITERATOR_CHUNK_SIZE]), AND)
    41             self.do_query(self.model._meta.db_table, where, using=using)
     41            self.do_query(self.model._meta.qualified_name, where, using=using)
    4242
    4343class UpdateQuery(Query):
    4444    """
     
    154154        extras.update(kwargs)
    155155        return super(InsertQuery, self).clone(klass, **extras)
    156156
     157
    157158    def insert_values(self, insert_values, raw_values=False):
    158159        """
    159160        Set up the insert query from the 'insert_values' dictionary. The
  • docs/ref/models/options.txt

    diff --git a/docs/ref/models/options.txt b/docs/ref/models/options.txt
    a b  
    6262aren't allowed in Python variable names -- notably, the hyphen -- that's OK.
    6363Django quotes column and table names behind the scenes.
    6464
     65.. _db_schema:
     66
     67``db_schema``
     68-------------
     69
     70.. attribute:: Options.db_schema
     71
     72.. versionadded:: 1.3
     73
     74The name of the database schema to use for the model. If the backend
     75doesn't support multiple schemas, this option is ignored.
     76
     77If this is used then Django will prefix the model table names with the schema
     78name. For example with MySQL Django would use ``db_schema + '.' + db_table``.
     79Be aware that PostgreSQL supports different schemas within the database.
     80MySQL solves the same thing by treating it as just another database.
     81
    6582``db_tablespace``
    6683-----------------
    6784
  • docs/ref/settings.txt

    diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt
    a b  
    317317The port to use when connecting to the database. An empty string means the
    318318default port. Not used with SQLite.
    319319
     320.. setting:: SCHEMA
     321
     322SCHEMA
     323~~~~~~
     324
     325.. versionadded:: 1.3
     326
     327Default: ``''`` (Empty string)
     328
     329The name of the database schema to use for models. If the backend
     330doesn't support multiple schemas, this option is ignored. An empty
     331string means the default schema.
     332
     333If this is used then Django will prefix any table names with the schema name.
     334The schema can be overriden on a per-model basis, for details see
     335:ref:`db_schema`.
     336
    320337.. setting:: USER
    321338
    322339USER
  • docs/topics/db/models.txt

    diff --git a/docs/topics/db/models.txt b/docs/topics/db/models.txt
    a b  
    629629            verbose_name_plural = "oxen"
    630630
    631631Model metadata is "anything that's not a field", such as ordering options
    632 (:attr:`~Options.ordering`), database table name (:attr:`~Options.db_table`), or
     632(:attr:`~Options.ordering`), database table name (:attr:`~Options.db_table`),
     633custom schema for the tables (:attr:`~Options.db_schema`), or
    633634human-readable singular and plural names (:attr:`~Options.verbose_name` and
    634635:attr:`~Options.verbose_name_plural`). None are required, and adding ``class
    635636Meta`` to a model is completely optional.
  • new file tests/modeltests/schemas/__init__.py

    diff --git a/tests/modeltests/schemas/__init__.py b/tests/modeltests/schemas/__init__.py
    new file mode 100644
    - +  
     1#
  • new file tests/modeltests/schemas/fixtures/testschema.json

    diff --git a/tests/modeltests/schemas/fixtures/testschema.json b/tests/modeltests/schemas/fixtures/testschema.json
    new file mode 100644
    - +  
     1[
     2    {
     3        "pk": 1,
     4        "model": "schemas.tag",
     5        "fields": {
     6            "name": "Test"
     7        }
     8    },
     9    {
     10        "pk": 1,
     11        "model": "schemas.blog",
     12        "fields": {
     13            "name":"Test"
     14        }
     15    },
     16    {
     17        "pk": 1,
     18        "model": "schemas.entry",
     19        "fields": {
     20            "blog": 1,
     21            "title": "Test entry",
     22            "tags": [1]
     23        }
     24    },
     25    {
     26        "pk": 1,
     27        "model": "schemas.comment",
     28        "fields": {
     29            "entry": 1,
     30            "text": "nice entry"
     31        }
     32    },
     33    {
     34        "pk": 2,
     35        "model": "schemas.comment",
     36        "fields": {
     37            "entry": 1,
     38            "text": "typical spam comment"
     39        }
     40    }
     41
     42
     43]
  • new file tests/modeltests/schemas/models.py

    diff --git a/tests/modeltests/schemas/models.py b/tests/modeltests/schemas/models.py
    new file mode 100644
    - +  
     1# coding: utf-8
     2
     3from django.db import models
     4
     5class Category(models.Model):
     6    name = models.CharField(max_length=50)
     7
     8
     9class Blog(models.Model):
     10    "Model in default schema"
     11    name = models.CharField(max_length=50)
     12
     13
     14class Entry(models.Model):
     15    "Model in custom schema that references the default"
     16    blog = models.ForeignKey(Blog)
     17    title = models.CharField(max_length=50)
     18    categories = models.ManyToManyField(Category)
     19
     20    class Meta:
     21        "Using custom db_table as well"
     22        db_table='schema_blog_entries'
     23        db_schema = 'test_schema'
     24
     25
     26class Comment(models.Model):
     27    "Model in the custom schema that references Entry in the same schema"
     28    entry = models.ForeignKey(Entry)
     29    text = models.CharField(max_length=50)
     30
     31    class Meta:
     32        db_schema = 'test_schema'
     33
     34
     35class Tag(models.Model):
     36    "Used for m2m relations"
     37    name=models.CharField(max_length=20)
     38    entries=models.ManyToManyField(Entry)
     39
     40
     41
     42__test__ = {'API_TESTS': """
     43
     44# Test with actual data
     45# Nothing in there yet
     46>>> Blog.objects.all()
     47[]
     48
     49# Create a blog
     50>>> b = Blog(name='Test')
     51>>> b.save()
     52
     53# Verify that we got an ID
     54>>> b.id
     551
     56
     57# Create entry
     58>>> e = Entry(blog=b, title='Test entry')
     59>>> e.save()
     60>>> e.id
     611
     62
     63# Create Comments
     64>>> c1 = Comment(entry=e, text='nice entry')
     65>>> c1.save()
     66>>> c2 = Comment(entry=e, text='really like it')
     67>>> c2.save()
     68
     69# Retrieve the stuff again.
     70>>> b2 = Blog.objects.get(id=b.id)
     71>>> b==b2
     72True
     73
     74>>> b2.entry_set.all()
     75[<Entry: Entry object>]
     76
     77# Make sure we don't break many to many relations
     78>>> t = Tag(name="test")
     79>>> t.save()
     80>>> t.entries = [e]
     81>>> t.entries.all()
     82[<Entry: Entry object>]
     83
     84
     85>>> from django.conf import settings
     86>>> from django.db import connection, models
     87
     88# Test if we support schemas and can find the table if so
     89>>> if e._meta.db_schema:
     90...     tables = connection.introspection.schema_table_names(e._meta.db_schema)
     91... else:
     92...     tables = connection.introspection.table_names()
     93>>> if connection.introspection.table_name_converter(e._meta.db_table) in tables:
     94...     print "ok"
     95... else:
     96...     print "schema=" + e._meta.db_schema
     97...     print "tables=%s" % tables
     98ok
     99
     100
     101"""
     102}
  • new file tests/modeltests/schemas/tests.py

    diff --git a/tests/modeltests/schemas/tests.py b/tests/modeltests/schemas/tests.py
    new file mode 100644
    - +  
     1# Validate that you can override the default test suite
     2
     3import unittest
     4from models import *
     5
     6from django.conf import settings
     7from django.db import connection, models, DEFAULT_DB_ALIAS
     8
     9class SampleTests(unittest.TestCase):
     10    fixtures = ['testschema',]
     11    VALID_ENGINES = ['postgresql_psycopg2', 'postgresql', 'mysql','oracle']
     12    VALID_ENGINES = ['django.db.backends.%s' %b for b in VALID_ENGINES]
     13
     14    def test_meta_information(self):
     15        e = Entry.objects.get(id=1)
     16        b = Blog.objects.get(id=1)
     17        if settings.DATABASES[DEFAULT_DB_ALIAS]['ENGINE'] in self.VALID_ENGINES:
     18            self.assertEqual('test_schema', e._meta.db_schema)
     19            if settings.DATABASE_SCHEMA:
     20                self.assertEqual(settings.DATABASE_SCHEMA, b._meta.db_schema)
     21            else:
     22                self.assertFalse(b._meta.db_schema)
     23        else:
     24            self.assertFalse(e._meta.db_schema) # No model schema
     25            self.assertFalse(b._meta.db_schema) # No global schema
     26
     27    def test_schema_in_m2m_declaring_model(self):
     28        e = Entry.objects.get(id=1)
     29        c = Category(name='Test')
     30        c.save()
     31        e.categories = [c]
     32
     33        categories= e.categories.filter(name='Test')
     34
     35        a = len(categories)
     36        self.assertEqual(1, len(categories))
  • tests/regressiontests/introspection/tests.py

    diff --git a/tests/regressiontests/introspection/tests.py b/tests/regressiontests/introspection/tests.py
    a b  
    6363
    6464    def test_sequence_list(self):
    6565        sequences = connection.introspection.sequence_list()
    66         expected = {'table': Reporter._meta.db_table, 'column': 'id'}
     66        expected = {'table': Reporter._meta.db_table, 'column': 'id', 'schema': ''}
    6767        self.assert_(expected in sequences,
    6868                     'Reporter sequence not found in sequence_list()')
    6969
Back to Top