Index: docs/topics/db/models.txt
===================================================================
--- docs/topics/db/models.txt	(revision 16443)
+++ docs/topics/db/models.txt	(working copy)
@@ -626,7 +626,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`),
+custom schema for the tables (:attr:`~Options.db_schema`), 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 16443)
+++ docs/ref/models/options.txt	(working copy)
@@ -61,6 +61,23 @@
 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
+
+.. versionadded:: 1.3
+
+The name of the database schema to use for the model. If the backend
+doesn't support multiple schemas, this option is ignored.
+
+If this is used then Django will prefix the model table names with the schema
+name. For example with 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 16443)
+++ docs/ref/settings.txt	(working copy)
@@ -510,6 +510,23 @@
 The port to use when connecting to the database. An empty string means the
 default port. Not used with SQLite.
 
+.. setting:: SCHEMA
+
+SCHEMA
+~~~~~~
+
+.. versionadded:: 1.3
+
+Default: ``''`` (Empty string)
+
+The name of the database schema to use for models. If the backend
+doesn't support multiple schemas, this option is ignored. An empty
+string means the default schema.
+
+If this is used then Django will prefix any table names with the schema name.
+The schema can be overriden on a per-model basis, for details see
+:ref:`db_schema`.
+
 .. setting:: USER
 
 USER
Index: django/db/models/sql/compiler.py
===================================================================
--- django/db/models/sql/compiler.py	(revision 16443)
+++ django/db/models/sql/compiler.py	(working copy)
@@ -23,7 +23,7 @@
         might not have all the pieces in place at that time.
         """
         if not self.query.tables:
-            self.query.join((None, self.query.model._meta.db_table, None, None))
+            self.query.join((None, self.query.model._meta.qualified_name, None, None))
         if (not self.query.select and self.query.default_cols and not
                 self.query.included_inherited_models):
             self.query.setup_inherited_models()
@@ -261,7 +261,7 @@
                         alias = start_alias
                     else:
                         link_field = opts.get_ancestor_link(model)
-                        alias = self.query.join((start_alias, model._meta.db_table,
+                        alias = self.query.join((start_alias, model._meta.qualified_name,
                                 link_field.column, model._meta.pk.column))
                     seen[model] = alias
             else:
@@ -462,7 +462,7 @@
                 result.append('%s%s%s' % (connector, qn(name), alias_str))
             first = False
         for t in self.query.extra_tables:
-            alias, unused = self.query.table_alias(t)
+            alias, unused = self.query.table_alias(qn(t))
             # Only add the alias if it's not already present (the table_alias()
             # calls increments the refcount, so an alias refcount of one means
             # this is the only reference.
@@ -550,7 +550,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
             promote = nullable or f.null
             if model:
                 int_opts = opts
@@ -571,7 +571,7 @@
                                 ()))
                         dupe_set.add((opts, lhs_col))
                     int_opts = int_model._meta
-                    alias = self.query.join((alias, int_opts.db_table, lhs_col,
+                    alias = self.query.join((alias, int_opts.qualified_name, lhs_col,
                             int_opts.pk.column), exclusions=used,
                             promote=promote)
                     alias_chain.append(alias)
@@ -623,7 +623,7 @@
                 # what "used" specifies).
                 avoid = avoid_set.copy()
                 dupe_set = orig_dupe_set.copy()
-                table = model._meta.db_table
+                table = model._meta.qualified_name
 
                 int_opts = opts
                 alias = root_alias
@@ -646,7 +646,7 @@
                             dupe_set.add((opts, lhs_col))
                         int_opts = int_model._meta
                         alias = self.query.join(
-                            (alias, int_opts.db_table, lhs_col, int_opts.pk.column),
+                            (alias, int_opts.qualified_name, lhs_col, int_opts.pk.column),
                             exclusions=used, promote=True, reuse=used
                         )
                         alias_chain.append(alias)
@@ -793,13 +793,13 @@
         # going to be column names (so we can avoid the extra overhead).
         qn = self.connection.ops.quote_name
         opts = self.query.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.query.columns]))
         values = [self.placeholder(*v) for v in self.query.values]
         result.append('VALUES (%s)' % ', '.join(values))
         params = self.query.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
@@ -813,7 +813,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.query.model._meta.db_table, self.query.model._meta.pk.column)
+                self.query.model._meta.db_schema, self.query.model._meta.db_table,
+                self.query.model._meta.pk.column)
 
 
 class SQLDeleteCompiler(SQLCompiler):
Index: django/db/models/sql/query.py
===================================================================
--- django/db/models/sql/query.py	(revision 16443)
+++ django/db/models/sql/query.py	(working copy)
@@ -620,7 +620,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:
@@ -809,7 +809,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):
@@ -922,7 +923,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
 
@@ -1269,8 +1271,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:
@@ -1295,12 +1298,12 @@
                         (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.get_field_by_name(
                             field.m2m_target_field_name())[0].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.get_field_by_name(
                             field.m2m_reverse_target_field_name())[0].column
@@ -1328,7 +1331,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,
@@ -1350,12 +1353,12 @@
                         (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.get_field_by_name(
                             field.m2m_reverse_target_field_name())[0].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.get_field_by_name(
                             field.m2m_target_field_name())[0].column
@@ -1379,7 +1382,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
                         # In case of a recursive FK, use the to_field for
@@ -1649,7 +1652,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 16443)
+++ django/db/models/sql/subqueries.py	(working copy)
@@ -41,7 +41,7 @@
             where = self.where_class()
             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, using=using)
+            self.do_query(self.model._meta.qualified_name, where, using=using)
 
 class UpdateQuery(Query):
     """
Index: django/db/models/options.py
===================================================================
--- django/db/models/options.py	(revision 16443)
+++ django/db/models/options.py	(working copy)
@@ -17,7 +17,7 @@
 DEFAULT_NAMES = ('verbose_name', 'verbose_name_plural', '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):
@@ -26,6 +26,9 @@
         self.module_name, self.verbose_name = None, None
         self.verbose_name_plural = None
         self.db_table = ''
+        #self.db_schema = settings.DATABASE_SCHEMA
+        self.db_schema = None
+        self.qualified_name = ''
         self.ordering = []
         self.unique_together =  []
         self.permissions =  []
@@ -55,8 +58,9 @@
         self.related_fkey_lookups = []
 
     def contribute_to_class(self, cls, name):
-        from django.db import connection
+        from django.db import connections, router
         from django.db.backends.util import truncate_name
+        conn = connections[router.db_for_read(cls)]
 
         cls._meta = self
         self.installed = re.sub('\.models$', '', cls.__module__) in settings.INSTALLED_APPS
@@ -64,6 +68,7 @@
         self.object_name = cls.__name__
         self.module_name = self.object_name.lower()
         self.verbose_name = get_verbose_name(self.object_name)
+        self.db_schema = conn.settings_dict['SCHEMA']
 
         # Next, apply any overridden values from 'class Meta'.
         if self.meta:
@@ -103,8 +108,14 @@
         # If the db_table wasn't provided, use the app_label + module_name.
         if not self.db_table:
             self.db_table = "%s_%s" % (self.app_label, self.module_name)
-            self.db_table = truncate_name(self.db_table, connection.ops.max_name_length())
+            self.db_table = truncate_name(self.db_table, conn.ops.max_name_length())
 
+        # Construct qualified table name.
+        self.qualified_name = conn.ops.prep_db_table(self.db_schema,
+                                                           self.db_table)
+        if self.qualified_name == conn.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:
             self.order_with_respect_to = self.get_field(self.order_with_respect_to)
@@ -184,6 +195,8 @@
         self.pk = target._meta.pk
         self.proxy_for_model = target
         self.db_table = target._meta.db_table
+        self.db_schema = target._meta.db_schema
+        self.qualified_name = target._meta.qualified_name
 
     def __repr__(self):
         return '<Options for %s>' % self.object_name
Index: django/db/models/fields/related.py
===================================================================
--- django/db/models/fields/related.py	(revision 16443)
+++ django/db/models/fields/related.py	(working copy)
@@ -892,7 +892,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):
@@ -983,6 +983,7 @@
         to = to.lower()
     meta = type('Meta', (object,), {
         'db_table': field._get_m2m_db_table(klass._meta),
+        'db_schema': field._get_m2m_db_schema(klass._meta),
         'managed': managed,
         'auto_created': klass,
         'app_label': klass._meta.app_label,
@@ -1014,6 +1015,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."
 
@@ -1035,6 +1037,18 @@
             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
+        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 accessor or DB column name for the m2m table"
         cache_attr = '_m2m_%s_cache' % attr
@@ -1105,6 +1119,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.
@@ -1116,7 +1133,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/__init__.py
===================================================================
--- django/db/__init__.py	(revision 16443)
+++ django/db/__init__.py	(working copy)
@@ -7,7 +7,7 @@
 __all__ = ('backend', 'connection', 'connections', 'router', 'DatabaseError',
     'IntegrityError', 'DEFAULT_DB_ALIAS')
 
-
+	
 if DEFAULT_DB_ALIAS not in settings.DATABASES:
     raise ImproperlyConfigured("You must define a '%s' database" % DEFAULT_DB_ALIAS)
 
Index: django/db/utils.py
===================================================================
--- django/db/utils.py	(revision 16443)
+++ django/db/utils.py	(working copy)
@@ -67,7 +67,8 @@
             conn['ENGINE'] = 'django.db.backends.dummy'
         conn.setdefault('OPTIONS', {})
         conn.setdefault('TIME_ZONE', settings.TIME_ZONE)
-        for setting in ['NAME', 'USER', 'PASSWORD', 'HOST', 'PORT']:
+
+        for setting in ('NAME', 'USER', 'PASSWORD', 'HOST', 'PORT', 'SCHEMA'):
             conn.setdefault(setting, '')
         for setting in ['TEST_CHARSET', 'TEST_COLLATION', 'TEST_NAME', 'TEST_MIRROR']:
             conn.setdefault(setting, None)
Index: django/db/backends/postgresql/base.py
===================================================================
--- django/db/backends/postgresql/base.py	(revision 13366)
+++ django/db/backends/postgresql/base.py	(working copy)
@@ -80,6 +80,7 @@
 
 class DatabaseFeatures(BaseDatabaseFeatures):
     uses_savepoints = True
+    default_schema_name = u'public'
 
 class DatabaseWrapper(BaseDatabaseWrapper):
     operators = {
Index: django/db/backends/postgresql/introspection.py
===================================================================
--- django/db/backends/postgresql/introspection.py	(revision 13366)
+++ django/db/backends/postgresql/introspection.py	(working copy)
@@ -31,6 +31,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 13366)
+++ django/db/backends/postgresql/operations.py	(working copy)
@@ -53,9 +53,10 @@
             return 'HOST(%s)'
         return '%s'
 
-    def last_insert_id(self, cursor, table_name, pk_name):
+    def last_insert_id(self, cursor, schema_name, table_name, pk_name):
         # Use pg_get_serial_sequence to get the underlying sequence name
         # from the table name and column name (available since PostgreSQL 8)
+        table_name = self.prep_db_table(schema_name, table_name)
         cursor.execute("SELECT CURRVAL(pg_get_serial_sequence('%s','%s'))" % (table_name, pk_name))
         return cursor.fetchone()[0]
 
@@ -67,8 +68,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_table(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
@@ -76,7 +86,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
@@ -84,18 +94,20 @@
                 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 not (column_name and len(column_name) > 0):
                     # This will be the case if it's an m2m using an autogenerated
                     # intermediate table (see BaseDatabaseIntrospection.sequence_list)
                     column_name = 'id'
+                table_name = self.prep_db_table(schema_name, table_name)
                 sql.append("%s setval(pg_get_serial_sequence('%s','%s'), 1, false);" % \
                     (style.SQL_KEYWORD('SELECT'),
                     style.SQL_TABLE(table_name),
@@ -118,27 +130,29 @@
 
             for f in model._meta.local_fields:
                 if isinstance(f, models.AutoField):
+                    table_name = self.prep_db_table(model._meta.db_schema, model._meta.db_table) # XXX: generic schemas support.
                     output.append("%s setval(pg_get_serial_sequence('%s','%s'), coalesce(max(%s), 1), max(%s) %s null) %s %s;" % \
                         (style.SQL_KEYWORD('SELECT'),
-                        style.SQL_TABLE(model._meta.db_table),
-                        style.SQL_FIELD(f.column),
+                        style.SQL_TABLE(table_name),
+                        style.SQL_FIELD(f.column), # XXX: Do we need qn() here?
                         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:
+                    table_name = self.prep_db_table(f.m2m_db_schema(), f.m2m_db_table()) # XXX: Do we need qn(f.m2m_db_table()) here? / XXX: generic schemas support.
                     output.append("%s setval(pg_get_serial_sequence('%s','%s'), coalesce(max(%s), 1), max(%s) %s null) %s %s;" % \
                         (style.SQL_KEYWORD('SELECT'),
-                        style.SQL_TABLE(model._meta.db_table),
-                        style.SQL_FIELD('id'),
+                        style.SQL_TABLE(table_name),
+                        style.SQL_FIELD('id'), # XXX: Do we need qn() here?
                         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/postgresql/creation.py
===================================================================
--- django/db/backends/postgresql/creation.py	(revision 13366)
+++ django/db/backends/postgresql/creation.py	(working copy)
@@ -51,10 +51,12 @@
                 tablespace_sql = ''
 
             def get_index_sql(index_name, opclass=''):
+                index_name = truncate_name(index_name, self.connection.ops.max_name_length())
+                index_name = self.connection.ops.prep_db_index(model._meta.db_schema, index_name)
                 return (style.SQL_KEYWORD('CREATE INDEX') + ' ' +
-                        style.SQL_TABLE(qn(truncate_name(index_name,self.connection.ops.max_name_length()))) + ' ' +
+                        style.SQL_TABLE(index_name) + ' ' +
                         style.SQL_KEYWORD('ON') + ' ' +
-                        style.SQL_TABLE(qn(db_table)) + ' ' +
+                        style.SQL_TABLE(model._meta.qualified_name) + ' ' +
                         "(%s%s)" % (style.SQL_FIELD(qn(f.column)), opclass) +
                         "%s;" % tablespace_sql)
 
Index: django/db/backends/sqlite3/base.py
===================================================================
--- django/db/backends/sqlite3/base.py	(revision 16443)
+++ django/db/backends/sqlite3/base.py	(working copy)
@@ -123,7 +123,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/sqlite3/creation.py
===================================================================
--- django/db/backends/sqlite3/creation.py	(revision 16443)
+++ django/db/backends/sqlite3/creation.py	(working copy)
@@ -45,7 +45,7 @@
             return test_database_name
         return ':memory:'
 
-    def _create_test_db(self, verbosity, autoclobber):
+    def _create_test_db(self, verbosity, autoclobber, schemas):
         test_database_name = self._get_test_db_name()
         if test_database_name != ':memory:':
             # Erase the old test database
Index: django/db/backends/mysql/base.py
===================================================================
--- django/db/backends/mysql/base.py	(revision 16443)
+++ django/db/backends/mysql/base.py	(working copy)
@@ -206,6 +206,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()'
 
@@ -215,19 +225,22 @@
         # 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))))
+            for (schema, table) in tables:
+                sql.append('%s %s;' % (style.SQL_KEYWORD('TRUNCATE'), style.SQL_FIELD(self.prep_db_table(schema, table))))
             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 16443)
+++ 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 16443)
+++ django/db/backends/mysql/creation.py	(working copy)
@@ -64,3 +64,23 @@
                 field.rel.to._meta.db_table, field.rel.to._meta.pk.column)
             ]
         return table_output, deferred
+
+    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 destroy a single schema.
+        """
+        qn = self.connection.ops.quote_name
+        output = "%s %s;" % (style.SQL_KEYWORD('DROP DATABASE IF EXISTS'), qn(schema))
+        return output
Index: django/db/backends/oracle/base.py
===================================================================
--- django/db/backends/oracle/base.py	(revision 16443)
+++ django/db/backends/oracle/base.py	(working copy)
@@ -82,12 +82,14 @@
 class DatabaseOperations(BaseDatabaseOperations):
     compiler_module = "django.db.backends.oracle.compiler"
 
-    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 = self._get_sequence_name(table)
         tr_name = self._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
@@ -96,17 +98,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()
@@ -191,8 +193,9 @@
     def deferrable_sql(self):
         return " DEFERRABLE INITIALLY DEFERRED"
 
-    def drop_sequence_sql(self, table):
-        return "DROP SEQUENCE %s;" % self.quote_name(self._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())
@@ -203,16 +206,15 @@
         else:
             return "%s"
 
+    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)
+
     def last_executed_query(self, cursor, sql, params):
         # http://cx-oracle.sourceforge.net/html/cursor.html#Cursor.statement
         # The DB API definition does not define this attribute.
         return cursor.statement
 
-    def last_insert_id(self, cursor, table_name, pk_name):
-        sq_name = self._get_sequence_name(table_name)
-        cursor.execute('SELECT "%s".currval FROM dual' % sq_name)
-        return cursor.fetchone()[0]
-
     def lookup_cast(self, lookup_type):
         if lookup_type in ('iexact', 'icontains', 'istartswith', 'iendswith'):
             return "UPPER(%s)"
@@ -224,6 +226,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
 
@@ -273,27 +285,31 @@
     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 = self._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
@@ -302,8 +318,10 @@
         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 = self._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,
@@ -313,8 +331,10 @@
                     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 = self._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,
Index: django/db/backends/oracle/introspection.py
===================================================================
--- django/db/backends/oracle/introspection.py	(revision 16443)
+++ django/db/backends/oracle/introspection.py	(working copy)
@@ -120,3 +120,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 16443)
+++ django/db/backends/oracle/creation.py	(working copy)
@@ -38,12 +38,38 @@
         'TimeField':                    'TIMESTAMP',
         'URLField':                     'VARCHAR2(%(max_length)s)',
     }
-
-    def __init__(self, connection):
+	
+	def __init__(self, connection):
         self.remember = {}
         super(DatabaseCreation, self).__init__(connection)
+	
+    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 _create_test_db(self, verbosity=1, autoclobber=False):
+    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'))
+
+    def _create_test_db(self, verbosity=1, autoclobber=False, schema=None):
         TEST_NAME = self._test_database_name()
         TEST_USER = self._test_database_user()
         TEST_PASSWD = self._test_database_passwd()
@@ -169,7 +195,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 16443)
+++ django/db/backends/__init__.py	(working copy)
@@ -279,6 +279,9 @@
     # integer primary keys.
     related_fields_match_type = False
     allow_sliced_subqueries = True
+	
+    default_schema_name = ''
+	
     has_select_for_update = False
     has_select_for_update_nowait = False
 
@@ -394,7 +397,7 @@
         self.connection = connection
         self._cache = None
 
-    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.
@@ -446,7 +449,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.
@@ -516,7 +519,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.
@@ -595,6 +598,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.
@@ -799,35 +816,72 @@
         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, router
         tables = set()
-        for app in models.get_apps():
-            for model in models.get_models(app):
-                if not model._meta.managed:
-                    continue
-                if not router.allow_syncdb(self.connection.alias, model):
-                    continue
-                tables.add(model._meta.db_table)
-                tables.update([f.m2m_db_table() for f in model._meta.local_many_to_many])
         if only_existing:
-            existing_tables = self.table_names()
-            tables = [
-                t
-                for t in tables
-                if self.table_name_converter(t) in existing_tables
-            ]
+            existing_tables = set([('', tn) for tn in self.table_names()])
+            seen_schemas = set()
+        for model in models.get_models():
+            if not model._meta.managed:
+                continue
+            if not router.allow_syncdb(self.connection.alias, model):
+                continue
+            db_schema = model._meta.db_schema
+            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((db_schema, model._meta.db_table))
+            m2m_tables = []
+            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)
+                m2m_tables.append((m2m_schema, m2m_table))
+            tables.update(m2m_tables)
+        if only_existing:
+            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):
@@ -859,14 +913,19 @@
                     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:
+                    schema = f.m2m_db_schema()
                     # 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': schema})
 
         return sequence_list
 
Index: django/db/backends/postgresql_psycopg2/base.py
===================================================================
--- django/db/backends/postgresql_psycopg2/base.py	(revision 16443)
+++ django/db/backends/postgresql_psycopg2/base.py	(working copy)
@@ -72,6 +72,7 @@
     can_defer_constraint_checks = True
     has_select_for_update = True
     has_select_for_update_nowait = True
+    default_schema_name = u'public'
 
 
 class DatabaseWrapper(BaseDatabaseWrapper):
Index: django/db/backends/creation.py
===================================================================
--- django/db/backends/creation.py	(revision 16443)
+++ django/db/backends/creation.py	(working copy)
@@ -26,6 +26,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:
@@ -69,7 +80,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(')')
@@ -81,7 +92,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)
@@ -93,7 +106,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()
             ]
@@ -119,19 +132,132 @@
             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
 
+    def sql_for_many_to_many(self, model, style):
+        "Return the CREATE TABLE statments for all the many-to-many tables defined on a model"
+        import warnings
+        warnings.warn(
+            'Database creation API for m2m tables has been deprecated. M2M models are now automatically generated',
+            PendingDeprecationWarning
+        )
+
+        output = []
+        for f in model._meta.local_many_to_many:
+            if model._meta.managed or f.rel.to._meta.managed:
+                output.extend(self.sql_for_many_to_many_field(model, f, style))
+        return output
+
+    def sql_for_many_to_many_field(self, model, f, style):
+        "Return the CREATE TABLE statements for a single m2m field"
+        import warnings
+        warnings.warn(
+            'Database creation API for m2m tables has been deprecated. M2M models are now automatically generated',
+            PendingDeprecationWarning
+        )
+
+        from django.db import models
+        from django.db.backends.util import truncate_name
+
+        output = []
+        if f.rel.through._meta.auto_created:
+            opts = model._meta
+            qn = self.connection.ops.quote_name
+            tablespace = f.db_tablespace or opts.db_tablespace
+            if tablespace:
+                sql = self.connection.ops.tablespace_sql(tablespace, inline=True)
+                if sql:
+                    tablespace_sql = ' ' + sql
+                else:
+                    tablespace_sql = ''
+            else:
+                tablespace_sql = ''
+            table_output = [style.SQL_KEYWORD('CREATE 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(connection=self.connection)),
+                style.SQL_KEYWORD('NOT NULL PRIMARY KEY'),
+                tablespace_sql))
+
+            deferred = []
+            inline_output, deferred = self.sql_for_inline_many_to_many_references(model, f, style)
+            table_output.extend(inline_output)
+
+            table_output.append('    %s (%s, %s)%s' %
+                (style.SQL_KEYWORD('UNIQUE'),
+                style.SQL_FIELD(qn(f.m2m_column_name())),
+                style.SQL_FIELD(qn(f.m2m_reverse_name())),
+                tablespace_sql))
+            table_output.append(')')
+            if opts.db_tablespace:
+                # f.db_tablespace is only for indices, so ignore its value here.
+                table_output.append(self.connection.ops.tablespace_sql(opts.db_tablespace))
+            table_output.append(';')
+            output.append('\n'.join(table_output))
+
+            for r_table, r_col, table, col in deferred:
+                r_name = '%s_refs_%s_%s' % (r_col, col, self._digest(r_table, table))
+                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),
+                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_schema(),
+                                                          f.m2m_db_table(),
+                                                          'id')
+            if autoinc_sql:
+                for stmt in autoinc_sql:
+                    output.append(stmt)
+        return output
+
+    def sql_for_inline_many_to_many_references(self, model, field, style):
+        "Create the references to other tables required by a many-to-many table"
+        import warnings
+        warnings.warn(
+            'Database creation API for m2m tables has been deprecated. M2M models are now automatically generated',
+            PendingDeprecationWarning
+        )
+
+        from django.db import models
+        opts = model._meta
+        qn = self.connection.ops.quote_name
+
+        table_output = [
+            '    %s %s %s %s (%s)%s,' %
+                (style.SQL_FIELD(qn(field.m2m_column_name())),
+                style.SQL_COLTYPE(models.ForeignKey(model).db_type(connection=self.connection)),
+                style.SQL_KEYWORD('NOT NULL REFERENCES'),
+                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(connection=self.connection)),
+                style.SQL_KEYWORD('NOT NULL REFERENCES'),
+                style.SQL_TABLE(field.rel.to._meta.qualified_name),
+                style.SQL_FIELD(qn(field.rel.to._meta.pk.column)),
+                self.connection.ops.deferrable_sql())
+        ]
+        deferred = []
+
+        return table_output, deferred
+
     def sql_indexes_for_model(self, model, style):
         "Returns the CREATE INDEX SQL statements for a single model"
         if not model._meta.managed or model._meta.proxy:
@@ -157,16 +283,25 @@
             else:
                 tablespace_sql = ''
             i_name = '%s_%s' % (model._meta.db_table, self._digest(f.column))
+            i_name = self.connection.ops.prep_db_index(model._meta.db_schema, i_name)
             output = [style.SQL_KEYWORD('CREATE INDEX') + ' ' +
                 style.SQL_TABLE(qn(truncate_name(i_name, self.connection.ops.max_name_length()))) + ' ' +
                 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 destroy 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:
@@ -174,12 +309,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
@@ -193,18 +329,38 @@
         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(qn(truncate_name(r_name, self.connection.ops.max_name_length())))))
         del references_to_delete[model]
         return output
 
+    def sql_destroy_many_to_many(self, model, f, style):
+        "Returns the DROP TABLE statements for a single m2m field"
+        import warnings
+        warnings.warn(
+            'Database creation API for m2m tables has been deprecated. M2M models are now automatically generated',
+            PendingDeprecationWarning
+        )
+
+        qn = self.connection.ops.quote_name
+        output = []
+        if f.rel.through._meta.auto_created:
+            output.append("%s %s;" % (style.SQL_KEYWORD('DROP TABLE'),
+                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
+
     def create_test_db(self, verbosity=1, autoclobber=False):
         """
         Creates a test database, prompting the user for confirmation if the
@@ -221,11 +377,18 @@
                 test_db_repr = " ('%s')" % test_database_name
             print "Creating test database for alias '%s'%s..." % (self.connection.alias, test_db_repr)
 
-        self._create_test_db(verbosity, autoclobber)
+        schema_apps = self._get_app_with_schemas()
+        schemas = self._get_schemas(schema_apps)
+        test_database_name = self._create_test_db(verbosity, autoclobber, schemas)
 
         self.connection.close()
         self.connection.settings_dict["NAME"] = test_database_name
 
+        # Create the test schemas.
+        cursor = self.connection.cursor()
+        self._create_test_schemas(verbosity, schemas, cursor)
+
+        call_command('syncdb', verbosity=verbosity, interactive=False, database=self.connection.alias)
         # Confirm the feature set of the test database
         self.connection.features.confirm()
 
@@ -271,6 +434,49 @@
 
         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)
+        return schema_apps
+
     def _get_test_db_name(self):
         """
         Internal implementation - returns the name of the test DB that will be
@@ -282,7 +488,7 @@
             return self.connection.settings_dict['TEST_NAME']
         return TEST_DATABASE_PREFIX + self.connection.settings_dict['NAME']
 
-    def _create_test_db(self, verbosity, autoclobber):
+    def _create_test_db(self, verbosity, autoclobber, schemas):
         "Internal implementation - creates the test db tables."
         suffix = self.sql_table_creation_suffix()
 
@@ -305,6 +511,7 @@
                 try:
                     if verbosity >= 1:
                         print "Destroying old test database '%s'..." % self.connection.alias
+                    self._destroy_test_schemas(verbosity, schemas, cursor)
                     cursor.execute("DROP DATABASE %s" % qn(test_database_name))
                     cursor.execute("CREATE DATABASE %s %s" % (qn(test_database_name), suffix))
                 except Exception, e:
Index: django/conf/project_template/settings.py
===================================================================
--- django/conf/project_template/settings.py	(revision 16443)
+++ django/conf/project_template/settings.py	(working copy)
@@ -17,6 +17,7 @@
         'PASSWORD': '',                  # Not used with sqlite3.
         'HOST': '',                      # Set to empty string for localhost. Not used with sqlite3.
         'PORT': '',                      # Set to empty string for default. Not used with sqlite3.
+        'SCHEMA': '',                    # Set to empty string for default.
     }
 }
 
Index: django/conf/global_settings.py
===================================================================
--- django/conf/global_settings.py	(revision 16443)
+++ django/conf/global_settings.py	(working copy)
@@ -150,6 +150,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.
 
 # New format
 DATABASES = {
Index: django/core/management/commands/syncdb.py
===================================================================
--- django/core/management/commands/syncdb.py	(revision 16443)
+++ django/core/management/commands/syncdb.py	(working copy)
@@ -56,8 +56,21 @@
         cursor = connection.cursor()
 
         # Get a list of already installed *models* so that references work right.
-        tables = connection.introspection.table_names()
+        schemas = connection.introspection.schema_names()
+        if schemas:
+            tables = []
+            default_schema_name = connection.features.default_schema_name
+            for schema in connection.introspection.schema_names():
+               if default_schema_name and schema == default_schema_name:
+                   sn = ''
+               else:
+                   sn = schema
+               for tn in connection.introspection.schema_table_names(schema):
+                   tables.append((sn, tn))
+        else:
+            tables = [('', tn) for tn in connection.introspection.table_names()]
         seen_models = connection.introspection.installed_models(tables)
+        seen_schemas = set()
         created_models = set()
         pending_references = {}
 
@@ -68,11 +81,23 @@
                 if router.allow_syncdb(db, m)])
             for app in models.get_apps()
         ]
+
+        def model_schema(model):
+            db_schema = model._meta.db_schema
+            if db_schema:
+                db_schema = connection.introspection.table_name_converter(db_schema)
+            return db_schema
+
         def model_installed(model):
             opts = model._meta
             converter = connection.introspection.table_name_converter
-            return not ((converter(opts.db_table) in tables) or
-                (opts.auto_created and converter(opts.auto_created._meta.db_table) in tables))
+            db_schema = model_schema(model)
+            schema_table = (db_schema, converter(opts.db_table))
+            return not ((schema_table in tables) or
+                (opts.auto_created and \
+                 (db_schema, converter(opts.auto_created._meta.db_table)) in tables)
+                 #(model_schema(opts.auto_created), converter(opts.auto_created._meta.db_table)) in tables)
+             )
 
         manifest = SortedDict(
             (app_name, filter(model_installed, model_list))
@@ -84,6 +109,13 @@
             print "Creating tables ..."
         for app_name, model_list in manifest.items():
             for model in model_list:
+                # Add model-defined schema tables if any.
+                db_schema = model_schema(model)
+                if db_schema and 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 >= 3:
                     print "Processing %s.%s model" % (app_name, model._meta.object_name)
@@ -96,10 +128,14 @@
                         sql.extend(connection.creation.sql_for_pending_references(refto, self.style, pending_references))
                 sql.extend(connection.creation.sql_for_pending_references(model, self.style, pending_references))
                 if verbosity >= 1 and sql:
-                    print "Creating table %s" % model._meta.db_table
+                    if db_schema:
+                        print "Creating table %s.%s" % (db_schema, model._meta.db_table)
+                    else:
+                        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))
+                if sql:
+                    tables.append((db_schema, connection.introspection.table_name_converter(model._meta.db_table)))
 
 
         transaction.commit_unless_managed(using=db)
Index: django/core/management/sql.py
===================================================================
--- django/core/management/sql.py	(revision 16443)
+++ django/core/management/sql.py	(working copy)
@@ -63,7 +63,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 = []
 
@@ -74,8 +75,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:
@@ -85,7 +97,12 @@
             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))
 
     # Close database connection explicitly, in case this output is being piped
@@ -116,7 +133,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()
     )
Index: django/contrib/contenttypes/generic.py
===================================================================
--- django/contrib/contenttypes/generic.py	(revision 16443)
+++ django/contrib/contenttypes/generic.py	(working copy)
@@ -133,6 +133,14 @@
     def m2m_reverse_name(self):
         return self.rel.to._meta.pk.column
 
+    def m2m_db_schema(self):
+        return self.rel.to._meta.db_schema
+
+    def m2m_qualified_name(self):
+        schema = self.m2m_db_schema()
+        table = self.m2m_db_table()
+        return connection.ops.prep_db_table(schema, table)
+
     def m2m_target_field_name(self):
         return self.model._meta.pk.name
 
Index: tests/regressiontests/introspection/tests.py
===================================================================
--- tests/regressiontests/introspection/tests.py	(revision 16443)
+++ tests/regressiontests/introspection/tests.py	(working copy)
@@ -58,7 +58,7 @@
 
     def test_sequence_list(self):
         sequences = connection.introspection.sequence_list()
-        expected = {'table': Reporter._meta.db_table, 'column': 'id'}
+        expected = {'table': Reporter._meta.db_table, 'column': 'id', 'schema': ''}
         self.assertTrue(expected in sequences,
                      'Reporter sequence not found in sequence_list()')
 
Index: tests/regressiontests/backends/tests.py
===================================================================
--- tests/regressiontests/backends/tests.py	(revision 16443)
+++ tests/regressiontests/backends/tests.py	(working copy)
@@ -157,22 +157,23 @@
         # A full flush is expensive to the full test, so we dig into the
         # internals to generate the likely offending SQL and run it manually
 
-        # Some convenience aliases
-        VLM = models.VeryLongModelNameZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
-        VLM_m2m = VLM.m2m_also_quite_long_zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz.through
-        tables = [
-            VLM._meta.db_table,
-            VLM_m2m._meta.db_table,
-        ]
-        sequences = [
-            {
-                'column': VLM._meta.pk.column,
-                'table': VLM._meta.db_table
-            },
-        ]
-        cursor = connection.cursor()
-        for statement in connection.ops.sql_flush(no_style(), tables, sequences):
-            cursor.execute(statement)
+		# Some convenience aliases
+		VLM = models.VeryLongModelNameZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
+		VLM_m2m = VLM.m2m_also_quite_long_zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz.through
+		tables = [
+			(VLM._meta.db_schema, VLM._meta.db_table),
+			(VLM_m2m._meta.db_schema, VLM_m2m._meta.db_table),
+		]
+		sequences = [
+			{
+				'column': VLM._meta.pk.column,
+				'table': VLM._meta.db_table,
+				'schema': VLM._meta.db_schema,
+			},
+		]
+		cursor = connection.cursor()
+		for statement in connection.ops.sql_flush(no_style(), tables, sequences):
+			cursor.execute(statement)
 
 class SequenceResetTest(TestCase):
     def test_generic_relation(self):
