Index: django/conf/project_template/settings.py
===================================================================
--- django/conf/project_template/settings.py	(revision 11871)
+++ django/conf/project_template/settings.py	(working copy)
@@ -15,6 +15,7 @@
 DATABASE_PASSWORD = ''         # Not used with sqlite3.
 DATABASE_HOST = ''             # Set to empty string for localhost. Not used with sqlite3.
 DATABASE_PORT = ''             # Set to empty string for default. Not used with sqlite3.
+DATABASE_SCHEMA = ''           # Set to empty string for default.
 
 # Local time zone for this installation. Choices can be found here:
 # http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
Index: django/conf/global_settings.py
===================================================================
--- django/conf/global_settings.py	(revision 11871)
+++ django/conf/global_settings.py	(working copy)
@@ -130,6 +130,7 @@
 DATABASE_HOST = ''             # Set to empty string for localhost. Not used with sqlite3.
 DATABASE_PORT = ''             # Set to empty string for default. Not used with sqlite3.
 DATABASE_OPTIONS = {}          # Set to empty dictionary for default.
+DATABASE_SCHEMA = ''           # Set to empty string for default.
 
 # The email backend to use. For possible shortcuts see django.core.mail.
 # The default is to use the SMTP backend.
Index: django/db/models/sql/query.py
===================================================================
--- django/db/models/sql/query.py	(revision 11871)
+++ django/db/models/sql/query.py	(working copy)
@@ -585,7 +585,7 @@
         might not have all the pieces in place at that time.
         """
         if not self.tables:
-            self.join((None, self.model._meta.db_table, None, None))
+            self.join((None, self.model._meta.qualified_name, None, None))
         if (not self.select and self.default_cols and not
                 self.included_inherited_models):
             self.setup_inherited_models()
@@ -685,7 +685,7 @@
         Callback used by deferred_to_columns(). The "target" parameter should
         be a set instance.
         """
-        table = model._meta.db_table
+        table = model._meta.qualified_name
         if table not in target:
             target[table] = set()
         for field in fields:
@@ -802,7 +802,8 @@
                         alias = start_alias
                     else:
                         link_field = opts.get_ancestor_link(model)
-                        alias = self.join((start_alias, model._meta.db_table,
+                        alias = self.join((start_alias,
+                                model._meta.qualified_name,
                                 link_field.column, model._meta.pk.column))
                     seen[model] = alias
             else:
@@ -1210,7 +1211,8 @@
             alias = self.tables[0]
             self.ref_alias(alias)
         else:
-            alias = self.join((None, self.model._meta.db_table, None, None))
+            alias = self.join((None, self.model._meta.qualified_name,
+                               None, None))
         return alias
 
     def count_active_tables(self):
@@ -1323,7 +1325,8 @@
                     seen[model] = root_alias
                 else:
                     link_field = opts.get_ancestor_link(model)
-                    seen[model] = self.join((root_alias, model._meta.db_table,
+                    seen[model] = self.join((root_alias,
+                            model._meta.qualified_name,
                             link_field.column, model._meta.pk.column))
         self.included_inherited_models = seen
 
@@ -1381,7 +1384,7 @@
             # what "used" specifies).
             avoid = avoid_set.copy()
             dupe_set = orig_dupe_set.copy()
-            table = f.rel.to._meta.db_table
+            table = f.rel.to._meta.qualified_name
             if nullable or f.null:
                 promote = True
             else:
@@ -1405,8 +1408,8 @@
                                 ())
                         dupe_set.add((opts, lhs_col))
                     int_opts = int_model._meta
-                    alias = self.join((alias, int_opts.db_table, lhs_col,
-                            int_opts.pk.column), exclusions=used,
+                    alias = self.join((alias, int_opts.qualified_name,
+                            lhs_col, int_opts.pk.column), exclusions=used,
                             promote=promote)
                     alias_chain.append(alias)
                     for (dupe_opts, dupe_col) in dupe_set:
@@ -1760,8 +1763,9 @@
                                     (id(opts), lhs_col), ()))
                             dupe_set.add((opts, lhs_col))
                         opts = int_model._meta
-                        alias = self.join((alias, opts.db_table, lhs_col,
-                                opts.pk.column), exclusions=exclusions)
+                        alias = self.join((alias, opts.qualified_name,
+                                           lhs_col, opts.pk.column),
+                                          exclusions=exclusions)
                         joins.append(alias)
                         exclusions.add(alias)
                         for (dupe_opts, dupe_col) in dupe_set:
@@ -1786,11 +1790,11 @@
                         (table1, from_col1, to_col1, table2, from_col2,
                                 to_col2, opts, target) = cached_data
                     else:
-                        table1 = field.m2m_db_table()
+                        table1 = field.m2m_qualified_name()
                         from_col1 = opts.pk.column
                         to_col1 = field.m2m_column_name()
                         opts = field.rel.to._meta
-                        table2 = opts.db_table
+                        table2 = opts.qualified_name
                         from_col2 = field.m2m_reverse_name()
                         to_col2 = opts.pk.column
                         target = opts.pk
@@ -1817,7 +1821,7 @@
                     else:
                         opts = field.rel.to._meta
                         target = field.rel.get_related_field()
-                        table = opts.db_table
+                        table = opts.qualified_name
                         from_col = field.column
                         to_col = target.column
                         orig_opts._join_cache[name] = (table, from_col, to_col,
@@ -1839,11 +1843,11 @@
                         (table1, from_col1, to_col1, table2, from_col2,
                                 to_col2, opts, target) = cached_data
                     else:
-                        table1 = field.m2m_db_table()
+                        table1 = field.m2m_qualified_name()
                         from_col1 = opts.pk.column
                         to_col1 = field.m2m_reverse_name()
                         opts = orig_field.opts
-                        table2 = opts.db_table
+                        table2 = opts.qualified_name
                         from_col2 = field.m2m_column_name()
                         to_col2 = opts.pk.column
                         target = opts.pk
@@ -1866,7 +1870,7 @@
                         local_field = opts.get_field_by_name(
                                 field.rel.field_name)[0]
                         opts = orig_field.opts
-                        table = opts.db_table
+                        table = opts.qualified_name
                         from_col = local_field.column
                         to_col = field.column
                         target = opts.pk
@@ -2105,7 +2109,7 @@
         self.group_by = []
         if self.connection.features.allows_group_by_pk:
             if len(self.select) == len(self.model._meta.fields):
-                self.group_by.append((self.model._meta.db_table,
+                self.group_by.append((self.model._meta.qualified_name,
                                       self.model._meta.pk.column))
                 return
 
@@ -2127,7 +2131,8 @@
         else:
             opts = self.model._meta
             if not self.select:
-                count = self.aggregates_module.Count((self.join((None, opts.db_table, None, None)), opts.pk.column),
+                count = self.aggregates_module.Count((self.join((None,
+                           opts.qualified_name, None, None)), opts.pk.column),
                                          is_summary=True, distinct=True)
             else:
                 # Because of SQL portability issues, multi-column, distinct
Index: django/db/models/sql/subqueries.py
===================================================================
--- django/db/models/sql/subqueries.py	(revision 11871)
+++ django/db/models/sql/subqueries.py	(working copy)
@@ -54,7 +54,7 @@
                             'in',
                             pk_list[offset : offset+GET_ITERATOR_CHUNK_SIZE]),
                             AND)
-                    self.do_query(related.field.m2m_db_table(), where)
+                    self.do_query(related.field.m2m_qualified_name(), where)
 
         for f in cls._meta.many_to_many:
             w1 = self.where_class()
@@ -70,7 +70,7 @@
                         AND)
                 if w1:
                     where.add(w1, AND)
-                self.do_query(f.m2m_db_table(), where)
+                self.do_query(f.m2m_qualified_name(), where)
 
     def delete_batch(self, pk_list):
         """
@@ -85,7 +85,7 @@
             field = self.model._meta.pk
             where.add((Constraint(None, field.column, field), 'in',
                     pk_list[offset : offset + GET_ITERATOR_CHUNK_SIZE]), AND)
-            self.do_query(self.model._meta.db_table, where)
+            self.do_query(self.model._meta.qualified_name, where)
 
 class UpdateQuery(Query):
     """
@@ -304,12 +304,12 @@
         # going to be column names (so we can avoid the extra overhead).
         qn = self.connection.ops.quote_name
         opts = self.model._meta
-        result = ['INSERT INTO %s' % qn(opts.db_table)]
+        result = ['INSERT INTO %s' % opts.qualified_name]
         result.append('(%s)' % ', '.join([qn(c) for c in self.columns]))
         result.append('VALUES (%s)' % ', '.join(self.values))
         params = self.params
         if self.return_id and self.connection.features.can_return_id_from_insert:
-            col = "%s.%s" % (qn(opts.db_table), qn(opts.pk.column))
+            col = "%s.%s" % (opts.qualified_name, qn(opts.pk.column))
             r_fmt, r_params = self.connection.ops.return_insert_id()
             result.append(r_fmt % col)
             params = params + r_params
@@ -323,7 +323,8 @@
         if self.connection.features.can_return_id_from_insert:
             return self.connection.ops.fetch_returned_insert_id(cursor)
         return self.connection.ops.last_insert_id(cursor,
-                self.model._meta.db_table, self.model._meta.pk.column)
+                self.model._meta.db_schema, self.model._meta.db_table,
+                self.model._meta.pk.column)
 
     def insert_values(self, insert_values, raw_values=False):
         """
Index: django/db/models/base.py
===================================================================
--- django/db/models/base.py	(revision 11871)
+++ django/db/models/base.py	(working copy)
@@ -611,7 +611,7 @@
             # into a pure queryset operation.
             where = ['%s %s (SELECT %s FROM %s WHERE %s=%%s)' % \
                 (qn('_order'), op, qn('_order'),
-                qn(self._meta.db_table), qn(self._meta.pk.column))]
+                self._meta.qualified_name, qn(self._meta.pk.column))]
             params = [self.pk]
             obj = self._default_manager.filter(**{order_field.name: getattr(self, order_field.attname)}).extra(where=where, params=params).order_by(order)[:1].get()
             setattr(self, cachename, obj)
Index: django/db/models/options.py
===================================================================
--- django/db/models/options.py	(revision 11871)
+++ django/db/models/options.py	(working copy)
@@ -21,7 +21,7 @@
 DEFAULT_NAMES = ('verbose_name', 'db_table', 'ordering',
                  'unique_together', 'permissions', 'get_latest_by',
                  'order_with_respect_to', 'app_label', 'db_tablespace',
-                 'abstract', 'managed', 'proxy', 'auto_created')
+                 'abstract', 'managed', 'proxy', 'auto_created', 'db_schema')
 
 class Options(object):
     def __init__(self, meta, app_label=None):
@@ -30,6 +30,8 @@
         self.module_name, self.verbose_name = None, None
         self.verbose_name_plural = None
         self.db_table = ''
+        self.db_schema = settings.DATABASE_SCHEMA
+        self.qualified_name = ''
         self.ordering = []
         self.unique_together =  []
         self.permissions =  []
@@ -104,6 +106,12 @@
             self.db_table = "%s_%s" % (self.app_label, self.module_name)
             self.db_table = truncate_name(self.db_table, connection.ops.max_name_length())
 
+        # Construct qualified table name.
+        self.qualified_name = connection.ops.prep_db_table(self.db_schema,
+                                                           self.db_table)
+        if self.qualified_name == connection.ops.quote_name(self.db_table):
+            # If unchanged, the backend doesn't support schemas.
+            self.db_schema = ''
 
     def _prepare(self, model):
         if self.order_with_respect_to:
@@ -177,6 +185,7 @@
         self.pk = target._meta.pk
         self.proxy_for_model = target
         self.db_table = target._meta.db_table
+        self.db_schema = target._meta.db_schema
 
     def __repr__(self):
         return '<Options for %s>' % self.object_name
Index: django/db/models/fields/related.py
===================================================================
--- django/db/models/fields/related.py	(revision 11871)
+++ django/db/models/fields/related.py	(working copy)
@@ -559,8 +559,9 @@
             core_filters={'%s__pk' % self.related.field.name: instance._get_pk_val()},
             instance=instance,
             symmetrical=False,
-            source_field_name=self.related.field.m2m_reverse_field_name(),
-            target_field_name=self.related.field.m2m_field_name()
+            join_table=self.related.field.m2m_qualified_name(),
+            source_field_name=qn(self.related.field.m2m_reverse_field_name()),
+            target_field_name=qn(self.related.field.m2m_field_name())
         )
 
         return manager
@@ -609,6 +610,7 @@
             core_filters={'%s__pk' % self.field.related_query_name(): instance._get_pk_val()},
             instance=instance,
             symmetrical=(self.field.rel.symmetrical and isinstance(instance, rel_model)),
+            join_table=self.field.m2m_qualified_name(),
             source_field_name=self.field.m2m_field_name(),
             target_field_name=self.field.m2m_reverse_field_name()
         )
@@ -753,7 +755,7 @@
         if isinstance(self.rel.to, basestring):
             target = self.rel.to
         else:
-            target = self.rel.to._meta.db_table
+            target = self.rel.to._meta.qualified_name
         cls._meta.duplicate_targets[self.column] = (target, "o2m")
 
     def contribute_to_related_class(self, cls, related):
@@ -866,6 +868,7 @@
             through=kwargs.pop('through', None))
 
         self.db_table = kwargs.pop('db_table', None)
+        self.db_schema = kwargs.pop('db_schema', '')
         if kwargs['rel'].through is not None:
             assert self.db_table is None, "Cannot specify a db_table if an intermediary model is used."
 
@@ -887,6 +890,19 @@
             return util.truncate_name('%s_%s' % (opts.db_table, self.name),
                                       connection.ops.max_name_length())
 
+    def _get_m2m_db_schema(self, opts):
+        "Function that can be curried to provide the m2m schema name for this relation"
+        if self.rel.through is not None and self.rel.through._meta.db_schema:
+            return self.rel.through._meta.db_schema
+        elif self.db_schema:
+            return self.db_schema
+
+    def _get_m2m_qualified_name(self, opts):
+        "Function that can be curried to provide the qualified m2m table name for this relation"
+        schema = self._get_m2m_db_schema(opts)
+        table = self._get_m2m_db_table(opts)
+        return connection.ops.prep_db_table(schema, table)
+
     def _get_m2m_attr(self, related, attr):
         "Function that can be curried to provide the source column name for the m2m table"
         cache_attr = '_m2m_%s_cache' % attr
@@ -976,6 +992,9 @@
 
         # Set up the accessor for the m2m table name for the relation
         self.m2m_db_table = curry(self._get_m2m_db_table, cls._meta)
+        self.m2m_db_schema = curry(self._get_m2m_db_schema, cls._meta)
+        self.m2m_qualified_name = curry(self._get_m2m_qualified_name,
+                                        cls._meta)
 
         # Populate some necessary rel arguments so that cross-app relations
         # work correctly.
@@ -987,7 +1006,7 @@
         if isinstance(self.rel.to, basestring):
             target = self.rel.to
         else:
-            target = self.rel.to._meta.db_table
+            target = self.rel.to._meta.qualified_name
         cls._meta.duplicate_targets[self.column] = (target, "m2m")
 
     def contribute_to_related_class(self, cls, related):
Index: django/db/backends/postgresql/introspection.py
===================================================================
--- django/db/backends/postgresql/introspection.py	(revision 11871)
+++ django/db/backends/postgresql/introspection.py	(working copy)
@@ -30,6 +30,24 @@
                 AND pg_catalog.pg_table_is_visible(c.oid)""")
         return [row[0] for row in cursor.fetchall()]
 
+    def get_schema_list(self, cursor):
+        cursor.execute("""
+            SELECT DISTINCT n.nspname
+            FROM pg_catalog.pg_class c
+            LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
+            WHERE c.relkind IN ('r', 'v', '')
+            AND n.nspname NOT IN ('pg_catalog', 'pg_toast', 'information_schema')""")
+        return [row[0] for row in cursor.fetchall()]
+
+    def get_schema_table_list(self, cursor, schema):
+        cursor.execute("""
+            SELECT c.relname
+            FROM pg_catalog.pg_class c
+            LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
+            WHERE c.relkind IN ('r', 'v', '')
+                AND n.nspname = '%s'""" % schema)
+        return [row[0] for row in cursor.fetchall()]
+
     def get_table_description(self, cursor, table_name):
         "Returns a description of the table, with the DB-API cursor.description interface."
         cursor.execute("SELECT * FROM %s LIMIT 1" % self.connection.ops.quote_name(table_name))
Index: django/db/backends/postgresql/operations.py
===================================================================
--- django/db/backends/postgresql/operations.py	(revision 11871)
+++ django/db/backends/postgresql/operations.py	(working copy)
@@ -52,8 +52,10 @@
             return 'HOST(%s)'
         return '%s'
 
-    def last_insert_id(self, cursor, table_name, pk_name):
-        cursor.execute("SELECT CURRVAL('\"%s_%s_seq\"')" % (table_name, pk_name))
+    def last_insert_id(self, cursor, schema_name, table_name, pk_name):
+        sequence_name = '%s_%s_seq' % (table_name, pk_name)
+        sequence_name = self.prep_db_table(schema_name, sequence_name)
+        cursor.execute("SELECT CURRVAL('%s')" % sequence_name)
         return cursor.fetchone()[0]
 
     def no_limit_value(self):
@@ -64,8 +66,17 @@
             return name # Quoting once is enough.
         return '"%s"' % name
 
+    def prep_db_table(self, db_schema, db_table):
+        qn = self.quote_name
+        if db_schema:
+            return "%s.%s" % (qn(db_schema), qn(db_table))
+        else:
+            return qn(db_table)
+
     def sql_flush(self, style, tables, sequences):
         if tables:
+            qnames = [self.prep_db_name(schema, table)
+                      for (schema, table) in tables]
             if self.postgres_version[0:2] >= (8,1):
                 # Postgres 8.1+ can do 'TRUNCATE x, y, z...;'. In fact, it *has to*
                 # in order to be able to truncate tables referenced by a foreign
@@ -73,7 +84,7 @@
                 # statement.
                 sql = ['%s %s;' % \
                     (style.SQL_KEYWORD('TRUNCATE'),
-                     style.SQL_FIELD(', '.join([self.quote_name(table) for table in tables]))
+                     style.SQL_FIELD(', '.join(qnames))
                 )]
             else:
                 # Older versions of Postgres can't do TRUNCATE in a single call, so
@@ -81,21 +92,23 @@
                 sql = ['%s %s %s;' % \
                         (style.SQL_KEYWORD('DELETE'),
                          style.SQL_KEYWORD('FROM'),
-                         style.SQL_FIELD(self.quote_name(table))
-                         ) for table in tables]
+                         style.SQL_FIELD(qname)
+                         ) for qname in qnames]
 
             # 'ALTER SEQUENCE sequence_name RESTART WITH 1;'... style SQL statements
             # to reset sequence indices
             for sequence_info in sequences:
+                schema_name = sequence_info['schema']
                 table_name = sequence_info['table']
                 column_name = sequence_info['column']
                 if column_name and len(column_name) > 0:
                     sequence_name = '%s_%s_seq' % (table_name, column_name)
                 else:
                     sequence_name = '%s_id_seq' % table_name
+                sequence_name = self.prep_db_table(schema_name, sequence_name)
                 sql.append("%s setval('%s', 1, false);" % \
                     (style.SQL_KEYWORD('SELECT'),
-                    style.SQL_FIELD(self.quote_name(sequence_name)))
+                    style.SQL_FIELD(sequence_name))
                 )
             return sql
         else:
@@ -111,25 +124,32 @@
             # if there are records (as the max pk value is already in use), otherwise set it to false.
             for f in model._meta.local_fields:
                 if isinstance(f, models.AutoField):
+                    sequence_name = qn('%s_%s_seq' % (model._meta.db_table,
+                                                      f.column))
+                    sequence_name = self.prep_db_table(model._meta.db_schema,
+                                                       sequence_name)
                     output.append("%s setval('%s', coalesce(max(%s), 1), max(%s) %s null) %s %s;" % \
                         (style.SQL_KEYWORD('SELECT'),
-                        style.SQL_FIELD(qn('%s_%s_seq' % (model._meta.db_table, f.column))),
+                        style.SQL_FIELD(sequence_name),
                         style.SQL_FIELD(qn(f.column)),
                         style.SQL_FIELD(qn(f.column)),
                         style.SQL_KEYWORD('IS NOT'),
                         style.SQL_KEYWORD('FROM'),
-                        style.SQL_TABLE(qn(model._meta.db_table))))
+                        style.SQL_TABLE(model._meta.qualified_name)))
                     break # Only one AutoField is allowed per model, so don't bother continuing.
             for f in model._meta.many_to_many:
                 if not f.rel.through:
+                    sequence_name = qn('%s_id_seq' % f.m2m_db_table())
+                    sequence_name = self.prep_db_table(f.m2m_db_schema(),
+                                                       sequence_name)
                     output.append("%s setval('%s', coalesce(max(%s), 1), max(%s) %s null) %s %s;" % \
                         (style.SQL_KEYWORD('SELECT'),
-                        style.SQL_FIELD(qn('%s_id_seq' % f.m2m_db_table())),
+                        style.SQL_FIELD(sequence_name),
                         style.SQL_FIELD(qn('id')),
                         style.SQL_FIELD(qn('id')),
                         style.SQL_KEYWORD('IS NOT'),
                         style.SQL_KEYWORD('FROM'),
-                        style.SQL_TABLE(qn(f.m2m_db_table()))))
+                        style.SQL_TABLE(qn(f.m2m_qualified_name()))))
         return output
 
     def savepoint_create_sql(self, sid):
Index: django/db/backends/sqlite3/base.py
===================================================================
--- django/db/backends/sqlite3/base.py	(revision 11871)
+++ django/db/backends/sqlite3/base.py	(working copy)
@@ -94,7 +94,7 @@
                 (style.SQL_KEYWORD('DELETE'),
                  style.SQL_KEYWORD('FROM'),
                  style.SQL_FIELD(self.quote_name(table))
-                 ) for table in tables]
+                 ) for (_, table) in tables]
         # Note: No requirement for reset of auto-incremented indices (cf. other
         # sql_flush() implementations). Just return SQL at this point
         return sql
Index: django/db/backends/mysql/base.py
===================================================================
--- django/db/backends/mysql/base.py	(revision 11871)
+++ django/db/backends/mysql/base.py	(working copy)
@@ -160,6 +160,16 @@
             return name # Quoting once is enough.
         return "`%s`" % name
 
+    def prep_db_table(self, db_schema, db_table):
+        qn = self.quote_name
+        if db_schema:
+            return "%s.%s" % (qn(db_schema), qn(db_table))
+        else:
+            return qn(db_table)
+
+    def prep_db_index(self, db_schema, db_index):
+        return self.prep_db_table(db_schema, db_index)
+
     def random_function_sql(self):
         return 'RAND()'
 
@@ -169,19 +179,21 @@
         # to clear all tables of all data
         if tables:
             sql = ['SET FOREIGN_KEY_CHECKS = 0;']
-            for table in tables:
-                sql.append('%s %s;' % (style.SQL_KEYWORD('TRUNCATE'), style.SQL_FIELD(self.quote_name(table))))
+            sql.extend(['%s %s;' % (style.SQL_KEYWORD('TRUNCATE'), style.SQL_FIELD(self.prep_db_table(schema, table))) for (schema, table) in tables])
             sql.append('SET FOREIGN_KEY_CHECKS = 1;')
 
             # 'ALTER TABLE table AUTO_INCREMENT = 1;'... style SQL statements
             # to reset sequence indices
-            sql.extend(["%s %s %s %s %s;" % \
-                (style.SQL_KEYWORD('ALTER'),
-                 style.SQL_KEYWORD('TABLE'),
-                 style.SQL_TABLE(self.quote_name(sequence['table'])),
-                 style.SQL_KEYWORD('AUTO_INCREMENT'),
-                 style.SQL_FIELD('= 1'),
-                ) for sequence in sequences])
+            for sequence_info in sequences:
+                schema_name = sequence_info['schema']
+                table_name = self.prep_db_table(schema_name, sequence_info['table'])
+                sql.append("%s %s %s %s %s;" % \
+                           (style.SQL_KEYWORD('ALTER'),
+                            style.SQL_KEYWORD('TABLE'),
+                            style.SQL_TABLE(table_name),
+                            style.SQL_KEYWORD('AUTO_INCREMENT'),
+                            style.SQL_FIELD('= 1'),
+                            ))
             return sql
         else:
             return []
Index: django/db/backends/mysql/introspection.py
===================================================================
--- django/db/backends/mysql/introspection.py	(revision 11871)
+++ django/db/backends/mysql/introspection.py	(working copy)
@@ -33,6 +33,14 @@
         cursor.execute("SHOW TABLES")
         return [row[0] for row in cursor.fetchall()]
 
+    def get_schema_list(self, cursor):
+        cursor.execute("SHOW SCHEMAS")
+        return [row[0] for row in cursor.fetchall()]
+
+    def get_schema_table_list(self, cursor, schema):
+        cursor.execute("SHOW TABLES FROM %s" % self.connection.ops.quote_name(schema))
+        return [schema + "." + row[0] for row in cursor.fetchall()]
+
     def get_table_description(self, cursor, table_name):
         "Returns a description of the table, with the DB-API cursor.description interface."
         cursor.execute("SELECT * FROM %s LIMIT 1" % self.connection.ops.quote_name(table_name))
Index: django/db/backends/mysql/creation.py
===================================================================
--- django/db/backends/mysql/creation.py	(revision 11871)
+++ django/db/backends/mysql/creation.py	(working copy)
@@ -63,4 +63,23 @@
                 field.rel.to._meta.db_table, field.rel.to._meta.pk.column)
             ]
         return table_output, deferred
-        
\ No newline at end of file
+
+    def default_schema(self):
+        return settings.DATABASE_NAME
+
+    def sql_create_schema(self, schema, style):
+        """
+        Returns the SQL required to create a single schema.
+        In MySQL schemas are synonymous to databases
+        """
+        qn = self.connection.ops.quote_name
+        output = "%s %s;" % (style.SQL_KEYWORD('CREATE DATABASE'), qn(schema))
+        return output
+
+    def sql_destroy_schema(self, schema, style):
+        """"
+        Returns the SQL required to create a single schema
+        """
+        qn = self.connection.ops.quote_name
+        output = "%s %s;" % (style.SQL_KEYWORD('DROP DATABASE IF EXISTS'), qn(schema))
+        return output
\ No newline at end of file
Index: django/db/backends/oracle/base.py
===================================================================
--- django/db/backends/oracle/base.py	(revision 11871)
+++ django/db/backends/oracle/base.py	(working copy)
@@ -55,12 +55,14 @@
 
 class DatabaseOperations(BaseDatabaseOperations):
 
-    def autoinc_sql(self, table, column):
+    def autoinc_sql(self, schema, table, column):
         # To simulate auto-incrementing primary keys in Oracle, we have to
         # create a sequence and a trigger.
         sq_name = get_sequence_name(table)
         tr_name = get_trigger_name(table)
-        tbl_name = self.quote_name(table)
+        tbl_name = self.prep_db_table(schema, table)
+        sq_qname = self.prep_db_table(schema, sq_name)
+        tr_qname = self.prep_db_table(schema, tr_name)
         col_name = self.quote_name(column)
         sequence_sql = """
 DECLARE
@@ -69,17 +71,17 @@
     SELECT COUNT(*) INTO i FROM USER_CATALOG
         WHERE TABLE_NAME = '%(sq_name)s' AND TABLE_TYPE = 'SEQUENCE';
     IF i = 0 THEN
-        EXECUTE IMMEDIATE 'CREATE SEQUENCE "%(sq_name)s"';
+        EXECUTE IMMEDIATE 'CREATE SEQUENCE %(sq_qname)s';
     END IF;
 END;
 /""" % locals()
         trigger_sql = """
-CREATE OR REPLACE TRIGGER "%(tr_name)s"
+CREATE OR REPLACE TRIGGER %(tr_qname)s
 BEFORE INSERT ON %(tbl_name)s
 FOR EACH ROW
 WHEN (new.%(col_name)s IS NULL)
     BEGIN
-        SELECT "%(sq_name)s".nextval
+        SELECT %(sq_qname)s.nextval
         INTO :new.%(col_name)s FROM dual;
     END;
 /""" % locals()
@@ -108,8 +110,9 @@
     def deferrable_sql(self):
         return " DEFERRABLE INITIALLY DEFERRED"
 
-    def drop_sequence_sql(self, table):
-        return "DROP SEQUENCE %s;" % self.quote_name(get_sequence_name(table))
+    def drop_sequence_sql(self, schema, table):
+        sequence_name = self.prep_db_table(schema, get_sequence_name(table))
+        return "DROP SEQUENCE %s;" % sequence_name
 
     def fetch_returned_insert_id(self, cursor):
         return long(cursor._insert_id_var.getvalue())
@@ -120,9 +123,9 @@
         else:
             return "%s"
 
-    def last_insert_id(self, cursor, table_name, pk_name):
-        sq_name = get_sequence_name(table_name)
-        cursor.execute('SELECT "%s".currval FROM dual' % sq_name)
+    def last_insert_id(self, cursor, schema_name, table_name, pk_name):
+        sq_name = self.prep_db_table(schema_name, get_sequence_name(table_name))
+        cursor.execute('SELECT %s.currval FROM dual' % sq_name)
         return cursor.fetchone()[0]
 
     def lookup_cast(self, lookup_type):
@@ -133,6 +136,16 @@
     def max_name_length(self):
         return 30
 
+    def prep_db_table(self, db_schema, db_table):
+        qn = self.quote_name
+        if db_schema:
+            return "%s.%s" % (qn(db_schema), qn(db_table))
+        else:
+            return qn(db_table)
+
+    def prep_db_index(self, db_schema, db_index):
+        return self.prep_db_table(db_schema, db_index)
+
     def prep_for_iexact_query(self, x):
         return x
 
@@ -186,27 +199,30 @@
     def sql_flush(self, style, tables, sequences):
         # Return a list of 'TRUNCATE x;', 'TRUNCATE y;',
         # 'TRUNCATE z;'... style SQL statements
+        sql = []
         if tables:
             # Oracle does support TRUNCATE, but it seems to get us into
             # FK referential trouble, whereas DELETE FROM table works.
-            sql = ['%s %s %s;' % \
-                    (style.SQL_KEYWORD('DELETE'),
-                     style.SQL_KEYWORD('FROM'),
-                     style.SQL_FIELD(self.quote_name(table)))
-                    for table in tables]
+            for schema, table in tables:
+                table = self.prep_db_table(schema, table)
+                sql.append('%s %s %s;' % \
+                           (style.SQL_KEYWORD('DELETE'),
+                            style.SQL_KEYWORD('FROM'),
+                            style.SQL_FIELD(table)))
             # Since we've just deleted all the rows, running our sequence
             # ALTER code will reset the sequence to 0.
             for sequence_info in sequences:
-                sequence_name = get_sequence_name(sequence_info['table'])
-                table_name = self.quote_name(sequence_info['table'])
+                schema_name = sequence_info['schema']
+                sequence_name = self.prep_db_table(schema_name,
+                                    get_sequence_name(sequence_info['table']))
+                table_name = self.prep_db_table(schema_name,
+                                                sequence_info['table'])
                 column_name = self.quote_name(sequence_info['column'] or 'id')
                 query = _get_sequence_reset_sql() % {'sequence': sequence_name,
                                                      'table': table_name,
                                                      'column': column_name}
                 sql.append(query)
-            return sql
-        else:
-            return []
+        return sql
 
     def sequence_reset_sql(self, style, model_list):
         from django.db import models
@@ -215,8 +231,9 @@
         for model in model_list:
             for f in model._meta.local_fields:
                 if isinstance(f, models.AutoField):
-                    table_name = self.quote_name(model._meta.db_table)
-                    sequence_name = get_sequence_name(model._meta.db_table)
+                    table_name = model._meta.qualified_name
+                    sequence_name = self.prep_db_table(model._meta.db_schema,
+                                       get_sequence_name(model._meta.db_table))
                     column_name = self.quote_name(f.column)
                     output.append(query % {'sequence': sequence_name,
                                            'table': table_name,
@@ -226,8 +243,9 @@
                     break
             for f in model._meta.many_to_many:
                 if not f.rel.through:
-                    table_name = self.quote_name(f.m2m_db_table())
-                    sequence_name = get_sequence_name(f.m2m_db_table())
+                    table_name = self.quote_name(f.m2m_qualified_name())
+                    sequence_name = self.prep_db_table(f.m2m_db_schema(),
+                                           get_sequence_name(f.m2m_db_table()))
                     column_name = self.quote_name('id')
                     output.append(query % {'sequence': sequence_name,
                                            'table': table_name,
@@ -434,6 +452,7 @@
             query = query[:-1]
         query = convert_unicode(query % tuple(args), self.charset)
         self._guess_input_sizes([params])
+        print >>file('/home/ikelly/django-log.sql', 'w'), query, self._param_generator(params)
         try:
             return self.cursor.execute(query, self._param_generator(params))
         except DatabaseError, e:
@@ -551,12 +570,12 @@
 BEGIN
     LOCK TABLE %(table)s IN SHARE MODE;
     SELECT NVL(MAX(%(column)s), 0) INTO startvalue FROM %(table)s;
-    SELECT "%(sequence)s".nextval INTO cval FROM dual;
+    SELECT %(sequence)s.nextval INTO cval FROM dual;
     cval := startvalue - cval;
     IF cval != 0 THEN
-        EXECUTE IMMEDIATE 'ALTER SEQUENCE "%(sequence)s" MINVALUE 0 INCREMENT BY '||cval;
-        SELECT "%(sequence)s".nextval INTO cval FROM dual;
-        EXECUTE IMMEDIATE 'ALTER SEQUENCE "%(sequence)s" INCREMENT BY 1';
+        EXECUTE IMMEDIATE 'ALTER SEQUENCE %(sequence)s MINVALUE 0 INCREMENT BY '||cval;
+        SELECT %(sequence)s.nextval INTO cval FROM dual;
+        EXECUTE IMMEDIATE 'ALTER SEQUENCE %(sequence)s INCREMENT BY 1';
     END IF;
     COMMIT;
 END;
Index: django/db/backends/oracle/introspection.py
===================================================================
--- django/db/backends/oracle/introspection.py	(revision 11871)
+++ django/db/backends/oracle/introspection.py	(working copy)
@@ -116,3 +116,26 @@
         for row in cursor.fetchall():
             indexes[row[0]] = {'primary_key': row[1], 'unique': row[2]}
         return indexes
+
+    def schema_name_converter(self, name):
+        """Convert to lowercase for case-sensitive schema name comparison."""
+        return name.lower()
+
+    def get_schema_list(self, cursor):
+        "Returns a list of schemas that exist in the database."
+        sql = """
+        select distinct username
+        from all_users, all_objects
+        where username = owner
+        """
+        cursor.execute(sql)
+        return [schema.lower() for (schema,) in cursor]
+
+    def get_schema_table_list(self, cursor, schema):
+        "Returns a list of tables in a specific schema."
+        sql = """
+        select table_name from all_tables
+        where owner = upper(%s)
+        """
+        cursor.execute(sql, [schema])
+        return [table.lower() for (table,) in cursor]
Index: django/db/backends/oracle/creation.py
===================================================================
--- django/db/backends/oracle/creation.py	(revision 11871)
+++ django/db/backends/oracle/creation.py	(working copy)
@@ -39,6 +39,32 @@
         'URLField':                     'VARCHAR2(%(max_length)s)',
     }
 
+    def sql_create_schema(self, schema, style, password=None,
+                          tablespace=None, temp_tablespace=None):
+        qn = self.connection.ops.quote_name
+        lock_account = (password is None)
+        if lock_account:
+            password = schema
+        output = []
+        output.append("%s %s %s %s" % (style.SQL_KEYWORD('CREATE USER'),
+                                       qn(schema),
+                                       style.SQL_KEYWORD('IDENTIFIED BY'),
+                                       qn(password)))
+        if tablespace:
+            output.append("%s %s" % (style.SQL_KEYWORD('DEFAULT TABLESPACE'),
+                                     qn(tablespace)))
+        if temp_tablespace:
+            output.append("%s %s" % (style.SQL_KEYWORD('TEMPORARY TABLESPACE'),
+                                     qn(temp_tablespace)))
+        if lock_account:
+            output.append(style.SQL_KEYWORD('ACCOUNT LOCK'))
+        return '\n'.join(output)
+
+    def sql_destroy_schema(self, schema, style):
+        qn = self.connection.ops.quote_name
+        return "%s %s %s" % (style.SQL_KEYWORD('DROP USER'), qn(schema),
+                             style.SQL_KEYWORD('CASCADE'))
+
     remember = {}
 
     def _create_test_db(self, verbosity=1, autoclobber=False):
@@ -174,7 +200,7 @@
                DEFAULT TABLESPACE %(tblspace)s
                TEMPORARY TABLESPACE %(tblspace_temp)s
             """,
-            """GRANT CONNECT, RESOURCE TO %(user)s""",
+            """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""",
         ]
         self._execute_statements(cursor, statements, parameters, verbosity)
 
Index: django/db/backends/__init__.py
===================================================================
--- django/db/backends/__init__.py	(revision 11871)
+++ django/db/backends/__init__.py	(working copy)
@@ -109,7 +109,7 @@
     a backend performs ordering or calculates the ID of a recently-inserted
     row.
     """
-    def autoinc_sql(self, table, column):
+    def autoinc_sql(self, schema, table, column):
         """
         Returns any SQL needed to support auto-incrementing primary keys, or
         None if no SQL is necessary.
@@ -155,7 +155,7 @@
         """
         return "DROP CONSTRAINT"
 
-    def drop_sequence_sql(self, table):
+    def drop_sequence_sql(self, schema, table):
         """
         Returns any SQL necessary to drop the sequence for the given table.
         Returns None if no SQL is necessary.
@@ -216,7 +216,7 @@
 
         return smart_unicode(sql) % u_params
 
-    def last_insert_id(self, cursor, table_name, pk_name):
+    def last_insert_id(self, cursor, schema_name, table_name, pk_name):
         """
         Given a cursor object that has just performed an INSERT statement into
         a table that has an auto-incrementing ID, returns the newly created ID.
@@ -287,6 +287,20 @@
         """
         raise NotImplementedError()
 
+    def prep_db_table(self, db_schema, db_table):
+        """
+        Prepares and formats the table name if necessary.
+        Just returns quoted db_table if not supported.
+        """
+        return self.quote_name(db_table)
+
+    def prep_db_index(self, db_schema, db_index):
+        """
+        Prepares and formats the table index name if necessary.
+        Just returns quoted db_index if not supported.
+        """
+        return self.quote_name(db_index)
+
     def random_function_sql(self):
         """
         Returns a SQL expression that returns a random value.
@@ -486,28 +500,70 @@
         return name
 
     def table_names(self):
-        "Returns a list of names of all tables that exist in the database."
+        "Returns a list of names of all tables that exist in the default schema."
         cursor = self.connection.cursor()
         return self.get_table_list(cursor)
 
+    def schema_name_converter(self, name):
+        """Apply a conversion to the name for the purposes of comparison.
+
+        The default schema name converter is for case sensitive comparison.
+        """
+        return name
+
+    def get_schema_list(self, cursor):
+        "Returns a list of schemas that exist in the database"
+        return []
+
+    def get_schema_table_list(self, cursor, schema):
+        "Returns a list of tables in a specific schema"
+        return []
+
+    def schema_names(self):
+        cursor = self.connection.cursor()
+        return self.get_schema_list(cursor)
+
+    def schema_table_names(self, schema):
+        "Returns a list of names of all tables that exist in the database schema."
+        cursor = self.connection.cursor()
+        return self.get_schema_table_list(cursor, schema)
+
     def django_table_names(self, only_existing=False):
         """
-        Returns a list of all table names that have associated Django models and
-        are in INSTALLED_APPS.
+        Returns a list of tuples containing all schema and table names that
+        have associated Django models and are in INSTALLED_APPS.
 
-        If only_existing is True, the resulting list will only include the tables
-        that actually exist in the database.
+        If only_existing is True, the resulting list will only include the
+        tables that actually exist in the database.
         """
         from django.db import models
         tables = set()
+        if only_existing:
+            existing_tables = set([('', tn) for tn in self.table_names()])
+            seen_schemas = set()
         for app in models.get_apps():
             for model in models.get_models(app):
                 if not model._meta.managed:
                     continue
-                tables.add(model._meta.db_table)
-                tables.update([f.m2m_db_table() for f in model._meta.local_many_to_many])
+                db_schema = model._meta.db_schema
+                db_table = model._meta.db_table
+                if only_existing and db_schema and db_schema not in seen_schemas:
+                    existing_tables.update([(db_schema, tn) for tn in
+                                            self.schema_table_names(db_schema)])
+                    seen_schemas.add(db_schema)
+                tables.add((model._meta.db_schema, model._meta.db_table))
+                for f in model._meta.local_many_to_many:
+                    m2m_schema = f.m2m_db_schema()
+                    m2m_table = f.m2m_db_table()
+                    if only_existing and m2m_schema and m2m_schema not in seen_schemas:
+                        existing_tables.update([(m2m_schema, tn) for tn in
+                                            self.schema_table_names(m2m_schema)])
+                        seen_schemas.add(m2m_schema)
+                    tables.add((m2m_schema, m2m_table))
         if only_existing:
-            tables = [t for t in tables if self.table_name_converter(t) in self.table_names()]
+            tables = [(s, t) for (s, t) in tables
+                      if (self.schema_name_converter(s),
+                          self.table_name_converter(t)) in existing_tables]
         return tables
 
     def installed_models(self, tables):
@@ -534,14 +590,18 @@
                     continue
                 for f in model._meta.local_fields:
                     if isinstance(f, models.AutoField):
-                        sequence_list.append({'table': model._meta.db_table, 'column': f.column})
+                        sequence_list.append({'table': model._meta.db_table,
+                                              'column': f.column,
+                                              'schema': model._meta.db_schema})
                         break # Only one AutoField is allowed per model, so don't bother continuing.
 
                 for f in model._meta.local_many_to_many:
                     # If this is an m2m using an intermediate table,
                     # we don't need to reset the sequence.
                     if f.rel.through is None:
-                        sequence_list.append({'table': f.m2m_db_table(), 'column': None})
+                        sequence_list.append({'table': f.m2m_db_table(),
+                                              'column': None,
+                                              'schema': f.m2m_db_schema()})
 
         return sequence_list
 
Index: django/db/backends/creation.py
===================================================================
--- django/db/backends/creation.py	(revision 11871)
+++ django/db/backends/creation.py	(working copy)
@@ -32,6 +32,17 @@
         """
         return '%x' % (abs(hash(args)) % 4294967296L)  # 2**32
 
+    def default_schema(self):
+        return ""
+
+    def sql_create_schema(self, schema, style):
+        """"
+        Returns the SQL required to create a single schema
+        """
+        qn = self.connection.ops.quote_name
+        output = "%s %s;" % (style.SQL_KEYWORD('CREATE SCHEMA'), qn(schema))
+        return output
+
     def sql_create_model(self, model, style, known_models=set()):
         """
         Returns the SQL required to create a single model, as a tuple of:
@@ -80,7 +91,7 @@
             table_output.append(style.SQL_KEYWORD('UNIQUE') + ' (%s)' % \
                 ", ".join([style.SQL_FIELD(qn(opts.get_field(f).column)) for f in field_constraints]))
 
-        full_statement = [style.SQL_KEYWORD('CREATE TABLE') + ' ' + style.SQL_TABLE(qn(opts.db_table)) + ' (']
+        full_statement = [style.SQL_KEYWORD('CREATE TABLE') + ' ' + style.SQL_TABLE(opts.qualified_name) + ' (']
         for i, line in enumerate(table_output): # Combine and add commas.
             full_statement.append('    %s%s' % (line, i < len(table_output)-1 and ',' or ''))
         full_statement.append(')')
@@ -92,7 +103,9 @@
         if opts.has_auto_field:
             # Add any extra SQL needed to support auto-incrementing primary keys.
             auto_column = opts.auto_field.db_column or opts.auto_field.name
-            autoinc_sql = self.connection.ops.autoinc_sql(opts.db_table, auto_column)
+            autoinc_sql = self.connection.ops.autoinc_sql(opts.db_schema,
+                                                          opts.db_table,
+                                                          auto_column)
             if autoinc_sql:
                 for stmt in autoinc_sql:
                     final_output.append(stmt)
@@ -104,7 +117,7 @@
         qn = self.connection.ops.quote_name
         if field.rel.to in known_models:
             output = [style.SQL_KEYWORD('REFERENCES') + ' ' + \
-                style.SQL_TABLE(qn(field.rel.to._meta.db_table)) + ' (' + \
+                style.SQL_TABLE(field.rel.to._meta.qualified_name) + ' (' + \
                 style.SQL_FIELD(qn(field.rel.to._meta.get_field(field.rel.field_name).column)) + ')' +
                 self.connection.ops.deferrable_sql()
             ]
@@ -130,15 +143,17 @@
             for rel_class, f in pending_references[model]:
                 rel_opts = rel_class._meta
                 r_table = rel_opts.db_table
+                r_qname = rel_opts.qualified_name
                 r_col = f.column
                 table = opts.db_table
+                qname = opts.qualified_name
                 col = opts.get_field(f.rel.field_name).column
                 # For MySQL, r_name must be unique in the first 64 characters.
                 # So we are careful with character usage here.
                 r_name = '%s_refs_%s_%s' % (r_col, col, self._digest(r_table, table))
                 final_output.append(style.SQL_KEYWORD('ALTER TABLE') + ' %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s)%s;' % \
-                    (qn(r_table), qn(truncate_name(r_name, self.connection.ops.max_name_length())),
-                    qn(r_col), qn(table), qn(col),
+                    (r_qname, qn(truncate_name(r_name, self.connection.ops.max_name_length())),
+                    qn(r_col), qname, qn(col),
                     self.connection.ops.deferrable_sql()))
             del pending_references[model]
         return final_output
@@ -170,7 +185,7 @@
             else:
                 tablespace_sql = ''
             table_output = [style.SQL_KEYWORD('CREATE TABLE') + ' ' + \
-                style.SQL_TABLE(qn(f.m2m_db_table())) + ' (']
+                style.SQL_TABLE(qn(f.m2m_qualified_name())) + ' (']
             table_output.append('    %s %s %s%s,' %
                 (style.SQL_FIELD(qn('id')),
                 style.SQL_COLTYPE(models.AutoField(primary_key=True).db_type()),
@@ -202,7 +217,9 @@
                 self.connection.ops.deferrable_sql()))
 
             # Add any extra SQL needed to support auto-incrementing PKs
-            autoinc_sql = self.connection.ops.autoinc_sql(f.m2m_db_table(), 'id')
+            autoinc_sql = self.connection.ops.autoinc_sql(f.m2m_db_schema(),
+                                                          f.m2m_db_table(),
+                                                          'id')
             if autoinc_sql:
                 for stmt in autoinc_sql:
                     output.append(stmt)
@@ -219,14 +236,14 @@
                 (style.SQL_FIELD(qn(field.m2m_column_name())),
                 style.SQL_COLTYPE(models.ForeignKey(model).db_type()),
                 style.SQL_KEYWORD('NOT NULL REFERENCES'),
-                style.SQL_TABLE(qn(opts.db_table)),
+                style.SQL_TABLE(opts.qualified_name),
                 style.SQL_FIELD(qn(opts.pk.column)),
                 self.connection.ops.deferrable_sql()),
             '    %s %s %s %s (%s)%s,' %
                 (style.SQL_FIELD(qn(field.m2m_reverse_name())),
                 style.SQL_COLTYPE(models.ForeignKey(field.rel.to).db_type()),
                 style.SQL_KEYWORD('NOT NULL REFERENCES'),
-                style.SQL_TABLE(qn(field.rel.to._meta.db_table)),
+                style.SQL_TABLE(opts.qualified_name),
                 style.SQL_FIELD(qn(field.rel.to._meta.pk.column)),
                 self.connection.ops.deferrable_sql())
         ]
@@ -256,16 +273,26 @@
                     tablespace_sql = ''
             else:
                 tablespace_sql = ''
+            index_name = '%s_%s' % (model._meta.db_table, f.column)
+            index_name = self.connection.ops.prep_db_index(model._meta.db_schema, index_name)
             output = [style.SQL_KEYWORD('CREATE INDEX') + ' ' +
-                style.SQL_TABLE(qn('%s_%s' % (model._meta.db_table, f.column))) + ' ' +
+                style.SQL_TABLE(index_name) + ' ' +
                 style.SQL_KEYWORD('ON') + ' ' +
-                style.SQL_TABLE(qn(model._meta.db_table)) + ' ' +
+                style.SQL_TABLE(model._meta.qualified_name) + ' ' +
                 "(%s)" % style.SQL_FIELD(qn(f.column)) +
                 "%s;" % tablespace_sql]
         else:
             output = []
         return output
 
+    def sql_destroy_schema(self, schema, style):
+        """"
+        Returns the SQL required to create a single schema
+        """
+        qn = self.connection.ops.quote_name
+        output = "%s %s CASCADE;" % (style.SQL_KEYWORD('DROP SCHEMA IF EXISTS'), qn(schema))
+        return output
+
     def sql_destroy_model(self, model, references_to_delete, style):
         "Return the DROP TABLE and restraint dropping statements for a single model"
         if not model._meta.managed or model._meta.proxy:
@@ -273,12 +300,13 @@
         # Drop the table now
         qn = self.connection.ops.quote_name
         output = ['%s %s;' % (style.SQL_KEYWORD('DROP TABLE'),
-                              style.SQL_TABLE(qn(model._meta.db_table)))]
+                              style.SQL_TABLE(model._meta.qualified_name))]
         if model in references_to_delete:
             output.extend(self.sql_remove_table_constraints(model, references_to_delete, style))
 
         if model._meta.has_auto_field:
-            ds = self.connection.ops.drop_sequence_sql(model._meta.db_table)
+            ds = self.connection.ops.drop_sequence_sql(model._meta.db_schema,
+                                                       model._meta.db_table)
             if ds:
                 output.append(ds)
         return output
@@ -292,13 +320,14 @@
         qn = self.connection.ops.quote_name
         for rel_class, f in references_to_delete[model]:
             table = rel_class._meta.db_table
+            qname = rel_class._meta.qualified_name
             col = f.column
             r_table = model._meta.db_table
             r_col = model._meta.get_field(f.rel.field_name).column
             r_name = '%s_refs_%s_%s' % (col, r_col, self._digest(table, r_table))
             output.append('%s %s %s %s;' % \
                 (style.SQL_KEYWORD('ALTER TABLE'),
-                style.SQL_TABLE(qn(table)),
+                style.SQL_TABLE(qname),
                 style.SQL_KEYWORD(self.connection.ops.drop_foreignkey_sql()),
                 style.SQL_FIELD(truncate_name(r_name, self.connection.ops.max_name_length()))))
         del references_to_delete[model]
@@ -310,8 +339,9 @@
         output = []
         if f.creates_table:
             output.append("%s %s;" % (style.SQL_KEYWORD('DROP TABLE'),
-                style.SQL_TABLE(qn(f.m2m_db_table()))))
-            ds = self.connection.ops.drop_sequence_sql("%s_%s" % (model._meta.db_table, f.column))
+                style.SQL_TABLE(f.m2m_qualified_name())))
+            ds = self.connection.ops.drop_sequence_sql(model._meta.db_schema,
+                               "%s_%s" % (model._meta.db_table, f.column))
             if ds:
                 output.append(ds)
         return output
@@ -333,6 +363,12 @@
         settings.DATABASE_SUPPORTS_TRANSACTIONS = can_rollback
         self.connection.settings_dict["DATABASE_SUPPORTS_TRANSACTIONS"] = can_rollback
 
+        # Create the test schemas.
+        schema_apps = self._get_app_with_schemas()
+        schemas = self._get_schemas(schema_apps)
+        cursor = self.connection.cursor()
+        self._create_test_schemas(verbosity, schemas, cursor)
+
         call_command('syncdb', verbosity=verbosity, interactive=False)
 
         if settings.CACHE_BACKEND.startswith('db://'):
@@ -346,6 +382,51 @@
 
         return test_database_name
 
+    def _create_test_schemas(self, verbosity, schemas, cursor):
+        from django.core.management.color import no_style
+        style = no_style()
+        for schema in schemas:
+            if verbosity >= 1:
+                print "Creating schema %s" % schema
+            cursor.execute(self.sql_create_schema(schema, style))
+
+    def _destroy_test_schemas(self, verbosity, schemas, cursor):
+        from django.core.management.color import no_style
+        style = no_style()
+        for schema in schemas:
+            if verbosity >= 1:
+                print "Destroying schema %s" % schema
+                cursor.execute(self.sql_destroy_schema(schema, style))
+            if verbosity >= 1:
+                print "Schema %s destroyed" % schema
+
+    def _get_schemas(self, apps):
+        from django.db import models
+        schemas = set()
+        for app in apps:
+            app_models = models.get_models(app)
+            for model in app_models:
+                schema = model._meta.db_schema
+                if not schema or schema in schemas:
+                    continue
+                schemas.add(schema)
+        return schemas
+
+    def _get_app_with_schemas(self):
+        from django.db import models
+        apps = models.get_apps()
+        schema_apps = set()
+        for app in apps:
+            app_models = models.get_models(app)
+            for model in app_models:
+                schema = model._meta.db_schema
+                if not schema or app in schema_apps:
+                    continue
+                schema_apps.add(app)
+                continue
+
+        return schema_apps
+
     def _create_test_db(self, verbosity, autoclobber):
         "Internal implementation - creates the test db tables."
         suffix = self.sql_table_creation_suffix()
@@ -372,6 +453,7 @@
                 try:
                     if verbosity >= 1:
                         print "Destroying old test database..."
+                    self._destroy_test_schemas(verbosity, schemas, cursor)
                     cursor.execute("DROP DATABASE %s" % qn(test_database_name))
                     if verbosity >= 1:
                         print "Creating test database..."
Index: django/core/management/commands/syncdb.py
===================================================================
--- django/core/management/commands/syncdb.py	(revision 11871)
+++ django/core/management/commands/syncdb.py	(working copy)
@@ -49,8 +49,9 @@
         cursor = connection.cursor()
 
         # Get a list of already installed *models* so that references work right.
-        tables = connection.introspection.table_names()
+        tables = [('', tn) for tn in connection.introspection.table_names()]
         seen_models = connection.introspection.installed_models(tables)
+        seen_schemas = set()
         created_models = set()
         pending_references = {}
 
@@ -59,11 +60,22 @@
             app_name = app.__name__.split('.')[-2]
             model_list = models.get_models(app, include_auto_created=True)
             for model in model_list:
-                # Create the model's database table, if it doesn't already exist.
+                # Add model-defined schema tables if any.
+                db_schema = model._meta.db_schema
+                if db_schema:
+                    db_schema = connection.introspection.schema_name_converter(db_schema)
+                    if db_schema not in seen_schemas:
+                        tables += [(db_schema, tn) for tn in
+                                   connection.introspection.schema_table_names(db_schema)]
+                        seen_schemas.add(db_schema)
+                
+                # 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)
                 opts = model._meta
-                if (connection.introspection.table_name_converter(opts.db_table) in tables or
+                schema_table = (db_schema, connection.introspection.table_name_converter(opts.db_table))
+                if (schema_table in tables or
                     (opts.auto_created and
                     connection.introspection.table_name_converter(opts.auto_created._meta.db_table) in tables)):
                     continue
@@ -79,7 +91,7 @@
                     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))
+                tables.append(schema_table)
 
 
         transaction.commit_unless_managed()
Index: django/core/management/sql.py
===================================================================
--- django/core/management/sql.py	(revision 11871)
+++ django/core/management/sql.py	(working copy)
@@ -68,7 +68,8 @@
 
     # Figure out which tables already exist
     if cursor:
-        table_names = connection.introspection.get_table_list(cursor)
+        table_names = [('', tn) for tn in
+                       connection.introspection.get_table_list(cursor)]
     else:
         table_names = []
 
@@ -79,8 +80,19 @@
 
     references_to_delete = {}
     app_models = models.get_models(app, include_auto_created=True)
+    seen_schemas = set()
     for model in app_models:
-        if cursor and connection.introspection.table_name_converter(model._meta.db_table) in table_names:
+        db_schema = model._meta.db_schema
+        # Find additional tables in model-defined schemas.
+        if db_schema:
+            db_schema = connection.introspection.schema_name_converter(db_schema)
+            if db_schema not in seen_schemas:
+                table_names += ((db_schema, tn) for tn in connection.introspection.get_schema_table_list(cursor, db_schema))
+                seen_schemas.add(db_schema)
+        schema_table = (db_schema,
+                        connection.introspection.table_name_converter(model._meta.db_table))
+
+        if cursor and schema_table in table_names:
             # The table exists, so it needs to be dropped
             opts = model._meta
             for f in opts.local_fields:
@@ -90,9 +102,26 @@
             to_delete.add(model)
 
     for model in app_models:
-        if connection.introspection.table_name_converter(model._meta.db_table) in table_names:
+        db_schema = model._meta.db_schema
+        if db_schema:
+            db_schema = connection.introspection.schema_name_converter(db_schema)
+        schema_table = (db_schema,
+                        connection.introspection.table_name_converter(model._meta.db_table))
+        if schema_table in table_names:
             output.extend(connection.creation.sql_destroy_model(model, references_to_delete, style))
 
+    # Output DROP TABLE statements for many-to-many tables.
+    for model in app_models:
+        opts = model._meta
+        db_schema = opts.db_schema
+        if db_schema:
+            db_schema = connection.introspection.schema_name_converter(db_schema)
+        for f in opts.local_many_to_many:
+            schema_table = (db_schema,
+                            connection.introspection.table_name_converter(f.m2m_db_table()))
+            if cursor and schema_table in table_names:
+                output.extend(connection.creation.sql_destroy_many_to_many(model, f, style))
+
     # Close database connection explicitly, in case this output is being piped
     # directly into a database client, to avoid locking issues.
     if cursor:
@@ -116,7 +145,7 @@
     if only_django:
         tables = connection.introspection.django_table_names(only_existing=True)
     else:
-        tables = connection.introspection.table_names()
+        tables = [('', tn) for tn in connection.introspection.table_names()]
     statements = connection.ops.sql_flush(style, tables, connection.introspection.sequence_list())
     return statements
 
Index: tests/modeltests/schemas/__init__.py
===================================================================
--- tests/modeltests/schemas/__init__.py	(revision 0)
+++ tests/modeltests/schemas/__init__.py	(revision 0)
@@ -0,0 +1 @@
+#
Index: tests/modeltests/schemas/tests.py
===================================================================
--- tests/modeltests/schemas/tests.py	(revision 0)
+++ tests/modeltests/schemas/tests.py	(revision 0)
@@ -0,0 +1,35 @@
+# Validate that you can override the default test suite
+
+import unittest
+from models import *
+    
+from django.conf import settings
+from django.db import connection, models
+
+class SampleTests(unittest.TestCase):
+    fixtures = ['testschema',]
+    VALID_ENGINES=['postgresql_psycopg2', 'postgresql', 'mysql','oracle']
+
+    def test_meta_information(self):
+        e=Entry.objects.get(id=1)
+        b = Blog.objects.get(id=1)
+        if settings.DATABASE_ENGINE in self.VALID_ENGINES:
+            self.assertEqual('test_schema', e._meta.db_schema)
+            if settings.DATABASE_SCHEMA:
+                self.assertEqual(settings.DATABASE_SCHEMA, b._meta.db_schema)
+            else:
+                self.assertFalse(b._meta.db_schema)
+        else:
+            self.assertFalse(e._meta.db_schema) #no model schema
+            self.assertFalse(b._meta.db_schema) #no global schema
+    
+    def test_schema_in_m2m_declaring_model(self):
+        e = Entry.objects.get(id=1)
+        c = Category(name='Test')
+        c.save()
+        e.categories = [c]
+        
+        categories= e.categories.filter(name='Test')
+        
+        a = len(categories)
+        self.assertEqual(1, len(categories))
\ No newline at end of file
Index: tests/modeltests/schemas/fixtures/testschema.json
===================================================================
--- tests/modeltests/schemas/fixtures/testschema.json	(revision 0)
+++ tests/modeltests/schemas/fixtures/testschema.json	(revision 0)
@@ -0,0 +1,43 @@
+[
+    {
+        "pk": 1,
+        "model": "schemas.tag",
+        "fields": {
+            "name": "Test"
+        }
+    },
+    {
+        "pk": 1,
+        "model": "schemas.blog",
+        "fields": {
+            "name":"Test"
+        }
+    },
+    {
+        "pk": 1,
+        "model": "schemas.entry",
+        "fields": {
+            "blog": 1,
+            "title": "Test entry",
+            "tags": [1]
+        }
+    },
+    {
+        "pk": 1,
+        "model": "schemas.comment",
+        "fields": {
+            "entry": 1,
+            "text": "nice entry"
+        }
+    },
+    {
+        "pk": 2,
+        "model": "schemas.comment",
+        "fields": {
+            "entry": 1,
+            "text": "typical spam comment"
+        }
+    }
+
+
+]
\ No newline at end of file
Index: tests/modeltests/schemas/models.py
===================================================================
--- tests/modeltests/schemas/models.py	(revision 0)
+++ tests/modeltests/schemas/models.py	(revision 0)
@@ -0,0 +1,102 @@
+# coding: utf-8
+
+from django.db import models
+
+class Category(models.Model):
+    name = models.CharField(max_length=50)
+    
+
+class Blog(models.Model):
+    "Model in default schema"
+    name = models.CharField(max_length=50)
+
+
+class Entry(models.Model):
+    "Model in custom schema that references the default"
+    blog = models.ForeignKey(Blog)
+    title = models.CharField(max_length=50)
+    categories = models.ManyToManyField(Category)
+    
+    class Meta:
+        "using custom db_table as well"
+        db_table='schema_blog_entries'
+        db_schema = 'test_schema'
+
+
+class Comment(models.Model):
+    "Model in the custom schema that references Entry in the same schema"
+    entry = models.ForeignKey(Entry)
+    text = models.CharField(max_length=50)
+
+    class Meta:
+        db_schema = 'test_schema'
+
+
+class Tag(models.Model):
+    "Used for m2m relations"
+    name=models.CharField(max_length=20)
+    entries=models.ManyToManyField(Entry)
+
+    
+    
+__test__ = {'API_TESTS': """
+
+#Test with actual data
+# Nothing in there yet
+>>> Blog.objects.all()
+[]
+
+# Create a blog
+>>> b = Blog(name='Test')
+>>> b.save()
+
+# Verify that we got an ID
+>>> b.id
+1
+
+# Create entry
+>>> e = Entry(blog=b, title='Test entry')
+>>> e.save()
+>>> e.id
+1
+
+# Create Comments
+>>> c1 = Comment(entry=e, text='nice entry')
+>>> c1.save()
+>>> c2 = Comment(entry=e, text='really like it')
+>>> c2.save()
+
+#Retrieve the stuff again.
+>>> b2 = Blog.objects.get(id=b.id)
+>>> b==b2
+True
+
+>>> b2.entry_set.all()
+[<Entry: Entry object>]
+
+#make sure we don't break many to many relations
+>>> t = Tag(name="test")
+>>> t.save()
+>>> t.entries = [e]
+>>> t.entries.all()
+[<Entry: Entry object>]
+
+
+>>> from django.conf import settings
+>>> from django.db import connection, models
+
+# Test if we support schemas and can find the table if so
+>>> if e._meta.db_schema:
+...     tables = connection.introspection.schema_table_names(e._meta.db_schema)
+... else:
+...     tables = connection.introspection.table_names()
+>>> if connection.introspection.table_name_converter(e._meta.db_table) in tables:
+...     print "ok"
+... else:
+...     print "schema=" + e._meta.db_schema
+...     print "tables=%s" % tables
+ok
+
+
+"""
+}
Index: docs/topics/db/models.txt
===================================================================
--- docs/topics/db/models.txt	(revision 11871)
+++ docs/topics/db/models.txt	(working copy)
@@ -629,7 +629,8 @@
             verbose_name_plural = "oxen"
 
 Model metadata is "anything that's not a field", such as ordering options
-(:attr:`~Options.ordering`), database table name (:attr:`~Options.db_table`), or
+(:attr:`~Options.ordering`), database table name (:attr:`~Options.db_table`),
+(:attr:`~Options.db_schema`) custom schema for the tables, or
 human-readable singular and plural names (:attr:`~Options.verbose_name` and
 :attr:`~Options.verbose_name_plural`). None are required, and adding ``class
 Meta`` to a model is completely optional.
Index: docs/ref/models/options.txt
===================================================================
--- docs/ref/models/options.txt	(revision 11871)
+++ docs/ref/models/options.txt	(working copy)
@@ -61,6 +61,25 @@
 aren't allowed in Python variable names -- notably, the hyphen -- that's OK.
 Django quotes column and table names behind the scenes.
 
+.. _db_schema:
+
+``db_schema``
+-----------------
+
+.. attribute:: Options.db_schema
+
+**New in Django development version**
+
+The name of the database schema to use for the model. If the backend
+doesn't support multiple schemas, this options is ignored.
+
+If this is used the Django will prefix the model table names with the schema
+name. For example MySQL Django would use ``db_schema + '.' + db_table``.
+Be aware that PostgreSQL supports different schemas within the database. 
+MySQL solves the same thing by treating it as just another database.
+
+
+
 ``db_tablespace``
 -----------------
 
Index: docs/ref/settings.txt
===================================================================
--- docs/ref/settings.txt	(revision 11871)
+++ docs/ref/settings.txt	(working copy)
@@ -272,6 +272,22 @@
 The port to use when connecting to the database. An empty string means the
 default port. Not used with SQLite.
 
+.. setting:: DATABASE_SCHEMA
+
+DATABASE_SCHEMA
+---------------
+
+**New in Django development version**
+
+Default: ``''`` (Empty string)
+
+The name of the database schema to use for models. If the backend
+doesn't support multiple schemas, this options is ignored. An empty
+string means the default schema.
+
+If this is used the Django will prefix any table names with the schema name.
+The schema can be overriden in model, for details see :ref:`db_schema`.
+
 .. setting:: DATABASE_USER
 
 DATABASE_USER
