Index: django/core/management/commands/syncdb.py
===================================================================
--- django/core/management/commands/syncdb.py	(revision 8696)
+++ django/core/management/commands/syncdb.py	(working copy)
@@ -61,6 +61,9 @@
             app_name = app.__name__.split('.')[-2]
             model_list = models.get_models(app)
             for model in model_list:
+                # Add model defined schema tables if anny
+                if model._meta.db_schema:
+                    tables += connection.introspection.schema_table_names(model._meta.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)
Index: django/core/management/sql.py
===================================================================
--- django/core/management/sql.py	(revision 8696)
+++ django/core/management/sql.py	(working copy)
@@ -84,6 +84,10 @@
     references_to_delete = {}
     app_models = models.get_models(app)
     for model in app_models:
+        schema = model._meta.db_schema
+        # Find aditional tables in model defined schemas
+        if schema:
+            table_names += connection.introspection.get_schema_table_list(cursor, schema)
         if cursor and connection.introspection.table_name_converter(model._meta.db_table) in table_names:
             # The table exists, so it needs to be dropped
             opts = model._meta
Index: django/db/backends/__init__.py
===================================================================
--- django/db/backends/__init__.py	(revision 8696)
+++ django/db/backends/__init__.py	(working copy)
@@ -225,6 +225,13 @@
         """
         raise NotImplementedError()
 
+    def prep_db_table(self, db_schema, db_table):
+        """
+        Prepares and formats the table name if neccesary.
+        Just returns the db_table if not supported
+        """
+        return db_table
+
     def random_function_sql(self):
         """
         Returns a SQL expression that returns a random value.
@@ -386,6 +393,30 @@
         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
Index: django/db/backends/creation.py
===================================================================
--- django/db/backends/creation.py	(revision 8696)
+++ django/db/backends/creation.py	(working copy)
@@ -25,6 +25,17 @@
     def __init__(self, connection):
         self.connection = connection
 
+    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:
@@ -122,6 +133,10 @@
                 r_col = f.column
                 table = opts.db_table
                 col = opts.get_field(f.rel.field_name).column
+                # Add schema if we are related to a model in different schema
+                # and we are not in a different schema ourselfs
+                if rel_opts.db_schema and not opts.db_schema:
+                    table =  "%s.%s" % (self.default_schema(), table)
                 # 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_%x' % (r_col, col, abs(hash((r_table, table))))
@@ -243,8 +258,13 @@
                     tablespace_sql = ''
             else:
                 tablespace_sql = ''
+            # Use original db_table in index name if schema is provided
+            if model._meta.db_schema:
+                index_table_name = model._meta._db_table
+            else:
+                index_table_name = model._meta.db_table
             output = [style.SQL_KEYWORD('CREATE INDEX') + ' ' +
-                style.SQL_TABLE(qn('%s_%s' % (model._meta.db_table, f.column))) + ' ' +
+                style.SQL_TABLE(qn('%s_%s' % (index_table_name, f.column))) + ' ' +
                 style.SQL_KEYWORD('ON') + ' ' +
                 style.SQL_TABLE(qn(model._meta.db_table)) + ' ' +
                 "(%s)" % style.SQL_FIELD(qn(f.column)) +
@@ -253,6 +273,14 @@
             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"
         # Drop the table now
@@ -324,8 +352,55 @@
 
         return test_database_name
 
+    def _create_test_schemas(self, verbosity, schemas, cursor):
+        from django.core.management.color import color_style
+        style = color_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 color_style
+        style = color_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."
+        schema_apps = self._get_app_with_schemas()
+        schemas = self._get_schemas(schema_apps)
         suffix = self.sql_table_creation_suffix()
 
         if settings.TEST_DATABASE_NAME:
@@ -342,6 +417,12 @@
         self.set_autocommit()
         try:
             cursor.execute("CREATE DATABASE %s %s" % (qn(test_database_name), suffix))
+            #Connect to the new database to create schemas in it
+            self.connection.close()
+            settings.DATABASE_NAME = test_database_name
+            cursor = self.connection.cursor()
+            self.set_autocommit()
+            self._create_test_schemas(verbosity, schemas, cursor)
         except Exception, e:
             sys.stderr.write("Got an error creating the test database: %s\n" % e)
             if not autoclobber:
@@ -350,10 +431,17 @@
                 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..."
                     cursor.execute("CREATE DATABASE %s %s" % (qn(test_database_name), suffix))
+                    #Connect to the new database to create schemas in it
+                    self.connection.close()
+                    settings.DATABASE_NAME = test_database_name
+                    cursor = self.connection.cursor()
+                    self.set_autocommit()
+                    self._create_test_schemas(verbosity, schemas, cursor)
                 except Exception, e:
                     sys.stderr.write("Got an error recreating the test database: %s\n" % e)
                     sys.exit(2)
Index: django/db/backends/mysql/base.py
===================================================================
--- django/db/backends/mysql/base.py	(revision 8696)
+++ django/db/backends/mysql/base.py	(working copy)
@@ -142,8 +142,12 @@
     def quote_name(self, name):
         if name.startswith("`") and name.endswith("`"):
             return name # Quoting once is enough.
-        return "`%s`" % name
+        # add support for tablenames passed that also have their schema in their name
+        return "`%s`" % name.replace('.','`.`') 
 
+    def prep_db_table(self, db_schema, db_table):
+        return "%s.%s" % (db_schema, db_table)
+
     def random_function_sql(self):
         return 'RAND()'
 
Index: django/db/backends/mysql/creation.py
===================================================================
--- django/db/backends/mysql/creation.py	(revision 8696)
+++ django/db/backends/mysql/creation.py	(working copy)
@@ -65,4 +65,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/mysql/introspection.py
===================================================================
--- django/db/backends/mysql/introspection.py	(revision 8696)
+++ 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/postgresql/introspection.py
===================================================================
--- django/db/backends/postgresql/introspection.py	(revision 8696)
+++ django/db/backends/postgresql/introspection.py	(working copy)
@@ -29,6 +29,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 [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/postgresql/operations.py
===================================================================
--- django/db/backends/postgresql/operations.py	(revision 8696)
+++ django/db/backends/postgresql/operations.py	(working copy)
@@ -55,7 +55,8 @@
         return '%s'
 
     def last_insert_id(self, cursor, table_name, pk_name):
-        cursor.execute("SELECT CURRVAL('\"%s_%s_seq\"')" % (table_name, pk_name))
+        # add support for tablenames passed that also have their schema in their name
+        cursor.execute(("SELECT CURRVAL('\"%s_%s_seq\"')" % (table_name, pk_name)).replace('.', '"."'))
         return cursor.fetchone()[0]
 
     def no_limit_value(self):
@@ -64,8 +65,12 @@
     def quote_name(self, name):
         if name.startswith('"') and name.endswith('"'):
             return name # Quoting once is enough.
-        return '"%s"' % name
+        # add support for tablenames passed that also have their schema in their name
+        return '"%s"' % name.replace('.','"."') 
 
+    def prep_db_table(self, db_schema, db_table):
+        return "%s.%s" % (db_schema, db_table)
+
     def sql_flush(self, style, tables, sequences):
         if tables:
             if self.postgres_version[0] >= 8 and self.postgres_version[1] >= 1:
Index: django/db/models/options.py
===================================================================
--- django/db/models/options.py	(revision 8696)
+++ 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')
+                 'abstract', 'db_schema')
 
 class Options(object):
     def __init__(self, meta, app_label=None):
@@ -29,6 +29,7 @@
         self.module_name, self.verbose_name = None, None
         self.verbose_name_plural = None
         self.db_table = ''
+        self.db_schema = ''
         self.ordering = []
         self.unique_together =  []
         self.permissions =  []
@@ -95,6 +96,14 @@
             self.db_table = "%s_%s" % (self.app_label, self.module_name)
             self.db_table = truncate_name(self.db_table, connection.ops.max_name_length())
 
+        # Patch db_table with the schema if provided and allowed
+        if self.db_schema:
+            # Store original db_table in a save place first
+            self._db_table = self.db_table
+            self.db_table = connection.ops.prep_db_table(self.db_schema, self.db_table)
+            # If no changes were done then backend don't support schemas
+            if self._db_table == self.db_table:
+                self.db_schema = ''
 
     def _prepare(self, model):
         if self.order_with_respect_to:
Index: docs/ref/models/options.txt
===================================================================
--- docs/ref/models/options.txt	(revision 8696)
+++ docs/ref/models/options.txt	(working copy)
@@ -42,6 +42,21 @@
 aren't allowed in Python variable names -- notably, the hyphen -- that's OK.
 Django quotes column and table names behind the scenes.
 
+``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 Django will prefix any table names with the schema name.
+For example MySQL Django would use ``db_schema + '.' + db_table``.
+Be aware that postgres supports different schemas within the database. 
+MySQL solves the same thing by treating it as just another database.
+
+
+
 ``db_tablespace``
 -----------------
 
Index: docs/topics/db/models.txt
===================================================================
--- docs/topics/db/models.txt	(revision 8696)
+++ docs/topics/db/models.txt	(working copy)
@@ -594,7 +594,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: 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/models.py
===================================================================
--- tests/modeltests/schemas/models.py	(revision 0)
+++ tests/modeltests/schemas/models.py	(revision 0)
@@ -0,0 +1,98 @@
+# coding: utf-8
+
+from django.db import models
+
+
+class Blog(models.Model):
+    "Model in default schema"
+    name = models.CharField(max_length=50)
+
+    
+class Entry(models.Model):
+    "Model in custom schema that reference the default"
+    blog = models.ForeignKey(Blog)    
+    title = models.CharField(max_length=50)
+    
+    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'
+
+__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>]
+
+>>> 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
+ok
+
+# Test that all but sqlite3 backend suports schema and doesn't drop it.
+# Oracle is not tested
+>>> if settings.DATABASE_ENGINE != 'sqlite3' and settings.DATABASE_ENGINE != 'oracle':
+...     if e._meta.db_schema != 'test_schema':
+...         print "shouldn't drop or modify schema"
+
+>>> from django.core.management.sql import *
+>>> from django.core.management.color import color_style
+
+>>> style = color_style()
+>>> app = models.get_app('schemas')
+
+# Get the sql_create sequence
+>>> a = sql_create(app, style)
+
+
+#Done
+"""
+}
