Index: django/conf/project_template/settings.py
===================================================================
--- django/conf/project_template/settings.py	(revision 15231)
+++ django/conf/project_template/settings.py	(working copy)
@@ -33,6 +33,9 @@
 # http://www.i18nguy.com/unicode/language-identifiers.html
 LANGUAGE_CODE = 'en-us'
 
+# Specify an overriding SITE_ID to statically determine your current site.
+# If this is not specified, the sites framwork will use the data on the request.
+# If this is specified, the sites framework will ignore the data on the request.
 SITE_ID = 1
 
 # If you set this to False, Django will make some optimizations so as not
Index: django/contrib/sites/managers.py
===================================================================
--- django/contrib/sites/managers.py	(revision 15231)
+++ django/contrib/sites/managers.py	(working copy)
@@ -1,6 +1,6 @@
-from django.conf import settings
 from django.db import models
 from django.db.models.fields import FieldDoesNotExist
+from django.contrib.sites.models import Site
 
 class CurrentSiteManager(models.Manager):
     "Use this to limit objects to those associated with the current site."
@@ -38,4 +38,6 @@
     def get_query_set(self):
         if not self.__is_validated:
             self._validate_field_name()
-        return super(CurrentSiteManager, self).get_query_set().filter(**{self.__field_name + '__id__exact': settings.SITE_ID})
+        return super(CurrentSiteManager, self).get_query_set().filter(
+          **{self.__field_name: Site.objects.get_current()}
+        )
Index: django/contrib/sites/middleware.py
===================================================================
--- django/contrib/sites/middleware.py	(revision 0)
+++ django/contrib/sites/middleware.py	(revision 0)
@@ -0,0 +1,35 @@
+from django.conf import settings
+from django.utils.cache import patch_vary_headers
+from django.contrib.sites.models import get_current_site, Site, RequestSite, SITE_CACHE
+
+
+class LazySite(object):
+    """
+    A lazy site object that refers to either Site instance or
+    a case insensitive RequestSite.
+    """
+    def __get__(self, request, obj_type=None):
+        if not hasattr(request, '_cached_site'):
+            request._cached_site = get_current_site(request)
+        return request._cached_site
+
+
+class SitesMiddleware(object):
+
+    def process_request(self, request):
+        if not hasattr(request.__class__, 'site'):
+            request.__class__.site = LazySite()
+        return None
+
+    def process_response(self, request, response):
+        """
+        Forces the HTTP Vary header onto requests to avoid having responses
+        cached from incorrect urlconfs.
+
+        If you'd like to disable this for some reason, set `FORCE_VARY_ON_HOST`
+        in your Django settings file to `False`.
+        """
+        if getattr(settings, 'SITES_VARY_ON_HOST', True):
+            patch_vary_headers(response, ('Host',))
+        return response
+
Index: django/contrib/sites/__init__.py
===================================================================
--- django/contrib/sites/__init__.py	(revision 15231)
+++ django/contrib/sites/__init__.py	(working copy)
@@ -0,0 +1,2 @@
+
+from django.contrib.sites.models import get_current_site
Index: django/contrib/sites/tests.py
===================================================================
--- django/contrib/sites/tests.py	(revision 15231)
+++ django/contrib/sites/tests.py	(working copy)
@@ -1,19 +1,34 @@
 from django.conf import settings
 from django.contrib.sites.models import Site, RequestSite, get_current_site
-from django.core.exceptions import ObjectDoesNotExist
+from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist
 from django.http import HttpRequest
 from django.test import TestCase
 
+NONE_VAL = object()
 
+class TestSite():
+    def __init__(self):
+        self.domain = "domain"
+        self.name = "name"
+        self.save = lambda x: x
+        self.delete = lambda x: x
+
 class SitesFrameworkTests(TestCase):
 
     def setUp(self):
-        Site(id=settings.SITE_ID, domain="example.com", name="example.com").save()
+        self.old_site_id = settings.SITE_ID if hasattr(settings, "SITE_ID") else NONE_VAL
+        settings.SITE_ID = 1
+        Site(id=settings.SITE_ID, domain="staticsite.com", name="staticsite.com").save()
+        Site(domain="dynamicsite.com", name="dynamicsite.com").save()
         self.old_Site_meta_installed = Site._meta.installed
         Site._meta.installed = True
 
     def tearDown(self):
         Site._meta.installed = self.old_Site_meta_installed
+        if self.old_site_id is not NONE_VAL:
+            settings.SITE_ID = self.old_site_id
+        else:
+            del settings.SITE_ID
 
     def test_site_manager(self):
         # Make sure that get_current() does not return a deleted Site object.
@@ -26,7 +41,7 @@
         # After updating a Site object (e.g. via the admin), we shouldn't return a
         # bogus value from the SITE_CACHE.
         site = Site.objects.get_current()
-        self.assertEqual(u"example.com", site.name)
+        self.assertEqual(u"staticsite.com", site.name)
         s2 = Site.objects.get(id=settings.SITE_ID)
         s2.name = "Example site"
         s2.save()
@@ -34,23 +49,94 @@
         self.assertEqual(u"Example site", site.name)
 
     def test_get_current_site(self):
+        del settings.SITE_ID
+
         # Test that the correct Site object is returned
         request = HttpRequest()
         request.META = {
-            "SERVER_NAME": "example.com",
+            "SERVER_NAME": "dynamicsite.com",
             "SERVER_PORT": "80",
         }
         site = get_current_site(request)
         self.assert_(isinstance(site, Site))
-        self.assertEqual(site.id, settings.SITE_ID)
+        self.assertEqual(site.id, self.old_site_id+1)
+        self.assertEqual(site.domain, "dynamicsite.com")
 
         # Test that an exception is raised if the sites framework is installed
         # but there is no matching Site
         site.delete()
         self.assertRaises(ObjectDoesNotExist, get_current_site, request)
 
+        settings.SITE_ID = 1
+
+        # by setting settings.SITE_ID, the requset-driven lookup is overridden
+        site = get_current_site(request)
+        self.assert_(isinstance(site, Site))
+        self.assertEqual(site.id, self.old_site_id)
+        self.assertEqual(site.domain, "staticsite.com")
+
+        del settings.SITE_ID
+
+        request = HttpRequest()
+        request.META = {
+            "SERVER_NAME": "staticsite.com",
+            "SERVER_PORT": "80",
+        }
+        site = get_current_site(request)
+        self.assert_(isinstance(site, Site))
+        self.assertEqual(site.id, self.old_site_id)
+        self.assertEqual(site.domain, "staticsite.com")
+
+        # Test that an exception is raised if the sites framework is installed
+        # but there is no matching Site
+        site.delete()
+        self.assertRaises(ObjectDoesNotExist, get_current_site, request)
+
+        # now, setting the SITE_ID should have no effect
+        settings.SITE_ID = 1
+        self.assertRaises(ObjectDoesNotExist, get_current_site, request)
+
         # A RequestSite is returned if the sites framework is not installed
         Site._meta.installed = False
         site = get_current_site(request)
         self.assert_(isinstance(site, RequestSite))
-        self.assertEqual(site.name, u"example.com")
+        self.assertEqual(site.name, u"staticsite.com")
+
+    def test_site_callback(self):
+        request = HttpRequest()
+
+        # baseline control
+        ts = TestSite()
+        settings.SITE_CALLBACK = lambda request, require_site_obj: ts
+        self.assertEqual(get_current_site(request), ts)
+
+        # test not an ORM Site
+        ts = TestSite()
+        settings.SITE_CALLBACK = lambda request, require_site_obj: ts
+        self.assertRaises(ImproperlyConfigured, get_current_site, request, True)
+
+        # test missing domain
+        ts = TestSite()
+        del ts.domain
+        settings.SITE_CALLBACK = lambda request, require_site_obj: ts
+        self.assertRaises(ImproperlyConfigured, get_current_site, request)
+
+        # test missing name
+        ts = TestSite()
+        del ts.name
+        settings.SITE_CALLBACK = lambda request, require_site_obj: ts
+        self.assertRaises(ImproperlyConfigured, get_current_site, request)
+
+        # test missing save
+        ts = TestSite()
+        del ts.save
+        settings.SITE_CALLBACK = lambda request, require_site_obj: ts
+        self.assertRaises(ImproperlyConfigured, get_current_site, request)
+
+        # test missing delete
+        ts = TestSite()
+        del ts.delete
+        settings.SITE_CALLBACK = lambda request, require_site_obj: ts
+        self.assertRaises(ImproperlyConfigured, get_current_site, request)
+
+        del settings.SITE_CALLBACK
Index: django/contrib/sites/models.py
===================================================================
--- django/contrib/sites/models.py	(revision 15231)
+++ django/contrib/sites/models.py	(working copy)
@@ -1,5 +1,6 @@
 from django.db import models
 from django.utils.translation import ugettext_lazy as _
+from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist
 
 
 SITE_CACHE = {}
@@ -7,24 +8,41 @@
 
 class SiteManager(models.Manager):
 
+    def get_site_by_request(self, request):
+        host = request.get_host().lower()
+        if ':' in host:
+            host, _ = host.split(':', 1)
+        return self.get(domain__iexact=host)
+
+    def get_site_by_id(self, id):
+        site = SITE_CACHE.get(id, None)
+        if site is None:
+            site = self.get(pk=id)
+            SITE_CACHE[id] = site
+        return site
+
     def get_current(self):
         """
         Returns the current ``Site`` based on the SITE_ID in the
         project's settings. The ``Site`` object is cached the first
         time it's retrieved from the database.
         """
+        from warnings import warn
+        warn(
+            "The SiteManager.get_current() method is deprecated in favor of the "
+            "get_current_site() function.", DeprecationWarning
+        )
+
         from django.conf import settings
         try:
             sid = settings.SITE_ID
         except AttributeError:
-            from django.core.exceptions import ImproperlyConfigured
-            raise ImproperlyConfigured("You're using the Django \"sites framework\" without having set the SITE_ID setting. Create a site in your database and set the SITE_ID setting to fix this error.")
-        try:
-            current_site = SITE_CACHE[sid]
-        except KeyError:
-            current_site = self.get(pk=sid)
-            SITE_CACHE[sid] = current_site
-        return current_site
+            raise ImproperlyConfigured(
+                "You're using the Django \"sites framework\" without having set "
+                "the SITE_ID setting. Create a site in your database and set "
+                "the SITE_ID setting to fix this error."
+            )
+        return self.get_site_by_id(sid)
 
     def clear_cache(self):
         """Clears the ``Site`` object cache."""
@@ -34,7 +52,7 @@
 
 class Site(models.Model):
 
-    domain = models.CharField(_('domain name'), max_length=100)
+    domain = models.CharField(_('domain name'), max_length=100, unique=True)
     name = models.CharField(_('display name'), max_length=50)
     objects = SiteManager()
 
@@ -71,7 +89,7 @@
     The save() and delete() methods raise NotImplementedError.
     """
     def __init__(self, request):
-        self.domain = self.name = request.get_host()
+        self.domain = self.name = request.get_host().lower()
 
     def __unicode__(self):
         return self.domain
@@ -82,14 +100,59 @@
     def delete(self):
         raise NotImplementedError('RequestSite cannot be deleted.')
 
+    def __repr__(self):
+        return "<RequestSite: %s, %s>" % (self.domain, self.name)
 
-def get_current_site(request):
+
+ERROR_MSG_1 = "settings.SITE_CALLBACK must return an object with a '%s' field defined. "
+ERROR_MSG_2 = "settings.SITE_CALLBACK must return an object with a '%s' method defined. "
+
+def get_current_site(request, require_site_object=False):
     """
     Checks if contrib.sites is installed and returns either the current
     ``Site`` object or a ``RequestSite`` object based on the request.
     """
-    if Site._meta.installed:
-        current_site = Site.objects.get_current()
+    from django.conf import settings
+
+    if hasattr(settings, 'SITE_CALLBACK') and settings.SITE_CALLBACK is not None:
+        if callable(settings.SITE_CALLBACK):
+            site = settings.SITE_CALLBACK(request, require_site_object)
+
+            # validate returned object to guarantee contract
+            if site is None:
+                pass # if the settings.SITE_CALLBACK returns None, then use other methods
+            elif require_site_object and not site.__class__ is Site:
+                raise ImproperlyConfigured(
+                    'settings.SITE_CALLBACK must return a Site object when '
+                    'require_site_object argument is True.'
+                )
+            elif not hasattr(site, 'domain'):
+                raise ImproperlyConfigured(ERROR_MSG_1 % 'domain')
+            elif not hasattr(site, 'name'):
+                raise ImproperlyConfigured(ERROR_MSG_1 % 'name')
+            elif not hasattr(site, 'save') or not callable(site.save):
+                raise ImproperlyConfigured(ERROR_MSG_2 % 'save')
+            elif not hasattr(site, 'delete') or not callable(site.delete):
+                raise ImproperlyConfigured(ERROR_MSG_2 % 'delete')
+            else:
+                return site 
+        else:
+            raise ImproperlyConfigured(
+                'Settings contains a SITE_CALLBACK value which is not callable.'
+            )
+
+    if not Site._meta.installed:
+        if not require_site_object:
+            return RequestSite(request)
+        else:
+            raise ImproperlyConfigured(
+                'The Django "sites framework" is being used somewhere in '
+                'your project in a manner that requires it to be added '
+                'to INSTALLED_APPS in your settings file.'
+            )
+
+    elif hasattr(settings, 'SITE_ID'):
+        return Site.objects.get_site_by_id(settings.SITE_ID)
+
     else:
-        current_site = RequestSite(request)
-    return current_site
+        return Site.objects.get_site_by_request(request)
Index: django/contrib/sites/decorators.py
===================================================================
--- django/contrib/sites/decorators.py	(revision 0)
+++ django/contrib/sites/decorators.py	(revision 0)
@@ -0,0 +1,11 @@
+from django.utils.decorators import decorator_from_middleware
+
+from django.contrib.sites.middleware import SitesMiddleware
+
+site_aware = decorator_from_middleware(SitesMiddleware)
+site_aware.__name__ = "site_aware"
+site_aware.__doc__ = """
+This decorator adds a LazySite instance to the request in exactly the same
+way as the SitesMiddleware, but it can be used on a per view basis.
+"""
+
Index: tests/regressiontests/sites_framework/tests.py
===================================================================
--- tests/regressiontests/sites_framework/tests.py	(revision 15231)
+++ tests/regressiontests/sites_framework/tests.py	(working copy)
@@ -1,14 +1,21 @@
 from django.conf import settings
 from django.contrib.sites.models import Site
 from django.test import TestCase
+from django.test.client import Client
+from django.core.exceptions import ImproperlyConfigured, ObjectDoesNotExist
 
+
 from models import SyndicatedArticle, ExclusiveArticle, CustomArticle, InvalidArticle, ConfusedArticle
 
 class SitesFrameworkTestCase(TestCase):
     def setUp(self):
         Site.objects.get_or_create(id=settings.SITE_ID, domain="example.com", name="example.com")
         Site.objects.create(id=settings.SITE_ID+1, domain="example2.com", name="example2.com")
+        self.old_site_id = settings.SITE_ID
         
+    def tearDown(self):
+        settings.SITE_ID = self.old_site_id
+
     def test_site_fk(self):
         article = ExclusiveArticle.objects.create(title="Breaking News!", site_id=settings.SITE_ID)
         self.assertEqual(ExclusiveArticle.on_site.all().get(), article)
@@ -32,3 +39,65 @@
     def test_invalid_field_type(self):
         article = ConfusedArticle.objects.create(title="More Bad News!", site=settings.SITE_ID)
         self.assertRaises(TypeError, ConfusedArticle.on_site.all)
+
+    def test_middleware(self):
+        #setup meta installed
+        old_meta_installed = Site._meta.installed
+        Site._meta.installed = False
+
+        #baseline control
+        c = Client()
+        extra = { "SERVER_NAME": "example3.com", 
+                  "SERVER_PORT": "80" }
+
+        result = c.get("/sites/", **extra)
+        self.assertEquals(result.content, "")
+
+        #decorator, using RequestSite
+        result = c.get("/sites/decorated/", **extra)
+        self.assertEquals(result.content, "example3.com")
+
+        #set up middleware
+        middleware_str = 'django.contrib.sites.middleware.SitesMiddleware'
+        old_middleware_setting = settings.MIDDLEWARE_CLASSES
+        if middleware_str not in settings.MIDDLEWARE_CLASSES:
+            settings.MIDDLEWARE_CLASSES += (middleware_str,)
+        old_site_id = settings.SITE_ID
+        del settings.SITE_ID
+        c = Client()
+        Site._meta.installed = True 
+
+        #retrieve by request
+        extra["SERVER_NAME"] = "example2.com"
+        result = c.get("/sites/", **extra)
+        self.assertEquals(result.content, "example2.com")
+
+        #retrieve default, no site found
+        extra["SERVER_NAME"] = "example3.com"
+        result = c.get("/sites/", **extra)
+        # this is empty, instead of raising an exception, because 
+        # ObjectDoesNotExist has a "silent_variable_failure" field 
+        # set to True, which the template engine replaces with ""
+        self.assertEquals(result.content, "") 
+
+        #retrieve by site id
+        settings.SITE_ID = old_site_id
+        extra["SERVER_NAME"] = "example2.com"
+        result = c.get("/sites/", **extra)
+        self.assertEquals(result.content, "example.com")
+
+        #retrieve default, no site found
+        extra["SERVER_NAME"] = "example3.com"
+        result = c.get("/sites/", **extra)
+        self.assertEquals(result.content, "example.com")
+
+        #use RequestSite
+        Site._meta.installed = False
+        extra["SERVER_NAME"] = "example3.com"
+        result = c.get("/sites/", **extra)
+        self.assertEquals(result.content, "example3.com")
+
+        #reset to original values
+        Site._meta.installed = old_meta_installed
+        settings.MIDDLEWARE_CLASSES = old_middleware_setting
+
Index: tests/urls.py
===================================================================
--- tests/urls.py	(revision 15231)
+++ tests/urls.py	(working copy)
@@ -41,4 +41,7 @@
 
     # special headers views
     (r'special_headers/', include('regressiontests.special_headers.urls')),
+
+    # sites middleware tests
+    (r'sites/', include('regressiontests.sites_framework.urls')),
 )
Index: docs/ref/contrib/sites.txt
===================================================================
--- docs/ref/contrib/sites.txt	(revision 15231)
+++ docs/ref/contrib/sites.txt	(working copy)
@@ -6,26 +6,88 @@
    :synopsis: Lets you operate multiple Web sites from the same database and
               Django project
 
+.. versionchanged:: 1.3
+
+.. note::
+    Prior to Django 1.3, django.contrib.sites relied heavily on settings.SITE_ID
+    as the only manner in which to determine the "current site".  Now, with 
+    Django 1.3, settings.SITE_ID is only one possible way to determine the
+    "current site", with the preferred method being an inspection of the incomming
+    request.
+
+    For this reason, the `Site.objects.get_current()` manager method has been
+    depricated in favor of `django.contrib.sites.get_current_site`. settings.SITE_ID, 
+    which used to be a mandatory setting for django.contrib.sites, is now optional.
+
 Django comes with an optional "sites" framework. It's a hook for associating
 objects and functionality to particular Web sites, and it's a holding place for
 the domain names and "verbose" names of your Django-powered sites.
 
-Use it if your single Django installation powers more than one site and you
-need to differentiate between those sites in some way.
+This framework has a number of uses, but its primary use is to implement a 
+standard approach to "multitenancy", whereby a single instance or installation 
+of Django can be used to power more than one site.  In other words, the sites 
+framework will allow you to differentiate between distinct sites in your 
+application. Django iteslf uses the sites framework in a couple of ways in 
+the contrib apps, automatically via simple conventions.
 
-The whole sites framework is based on a simple model:
+The "sites" framework centralizes control in a single function, which
+should act as the entry point for anyone using the library:
 
-.. class:: django.contrib.sites.models.Site
+.. :func:`~django.contrib.sites.get_current_site`
 
-This model has :attr:`~django.contrib.sites.models.Site.domain` and
-:attr:`~django.contrib.sites.models.Site.name` fields. The :setting:`SITE_ID`
-setting specifies the database ID of the
-:class:`~django.contrib.sites.models.Site` object associated with that
-particular settings file.
+This function takes a request as a parameter, and returns an object
+representing the current site.  The returned object may be user-defined,
+or it may be one of two types of object that the sites framework itself 
+implements.  Regardless of what specific type of object is returned, however,
+a `domain` field, corresponding to the domain name of the site, and a `name` 
+field, which is a verbose name that describes the site, will always be defined.  
+Non-ORM objects that are returned are also required to implement mock `save`
+and `delete` methods, to maintain API compatibilty with the Site model.
 
-How you use this is up to you, but Django uses it in a couple of ways
-automatically via simple conventions.
+How ``get_current_site`` Works
+----------------------------
 
+.. versionchanged:: 1.3
+
+The return value of ``get_current_site`` can be determined by any of four
+things (in order of precidence):
+
+    * the optional SITE_CALLBACK setting, which should reference a callable
+      with the same signature and contract as ``get_current_site`` iteslf--
+      specifically, it must specify ``request`` and ``require_site_object`` as
+      parameters, and return an object with members ``domain``, ``name``, 
+      ``save``, and ``delete``
+
+    * whether or not the ``contrib.sites`` app is installed in your project,
+      i.e. referenced inside the INSTALLED_APPS list in settings; in the
+      event that ``contrib.sites`` is not installed, a non-ORM RequestSite
+      object is returned
+
+    * the optional SITE_ID setting, which points to an overriding Site 
+      database record.
+
+    * the hostname associated with the inbound http request, which will
+      be used to retrieve a Site ORM object from the database.
+
+The return value of SITE_CALLBACK can be any user-specified object.  However,
+it is important to note that whenever the ``require_site_object`` argument is
+passed in as ``True`` (the default is ``False``), the object returned must
+be a django.contrib.sites.models.Site object.
+
+Similarly, whenever ``contrib.sites`` is not installed in your project, a
+RequestSite object will be returned instead of a Site object.  However,
+if the ``require_site_object`` argument is passed in as ``True`` then failing
+to include the sites framework in your INSTALLED_APPS will cause an
+ImproperlyConfigured exception to be raised.
+
+Note that the SITE_ID setting acts to override whatever host might be specified
+in the request. The reason for this override behavior is explained in the section
+below titled "Static Multitenancy". 
+
+If the last step in the above precidence chain is reached and no Site object
+is found for the incomming request, then the ``get_current_site()`` function
+returns None.
+
 Example usage
 =============
 
@@ -74,11 +136,18 @@
 
           def article_detail(request, article_id):
               try:
-                  a = Article.objects.get(id=article_id, sites__id__exact=settings.SITE_ID)
+                  site = get_current_site(request, require_site_object=True)
+                  a = Article.objects.get(id=article_id, sites=site)
               except Article.DoesNotExist:
                   raise Http404
               # ...
 
+Note that because the ``Article`` model relates specifically to the ``Site`` 
+model, ``get_current_site()`` is called with the require_site_object parameter
+set to True.  If this value were not specified, there would be a risk that
+a ``RequestSite`` or even perhaps a user-defined site object being returned,
+which would cause a failure in the ORM when the above query were run.
+
 .. _ljworld.com: http://www.ljworld.com/
 .. _lawrence.com: http://www.lawrence.com/
 
@@ -100,67 +169,45 @@
         # ...
         site = models.ForeignKey(Site)
 
-This has the same benefits as described in the last section.
+This has the same benefits as described in the next section.
 
 .. _hooking-into-current-site-from-views:
 
 Hooking into the current site from views
 ----------------------------------------
 
+.. versionchanged:: 1.3
+
 You can use the sites framework in your Django views to do
 particular things based on the site in which the view is being called.
 For example::
 
     from django.conf import settings
+    from django.contrib.sites import get_current_site 
 
     def my_view(request):
-        if settings.SITE_ID == 3:
-            # Do something.
-        else:
-            # Do something else.
-
-Of course, it's ugly to hard-code the site IDs like that. This sort of
-hard-coding is best for hackish fixes that you need done quickly. A slightly
-cleaner way of accomplishing the same thing is to check the current site's
-domain::
-
-    from django.conf import settings
-    from django.contrib.sites.models import Site
-
-    def my_view(request):
-        current_site = Site.objects.get(id=settings.SITE_ID)
+        current_site = get_current_site(request)
         if current_site.domain == 'foo.com':
             # Do something
         else:
             # Do something else.
 
-The idiom of retrieving the :class:`~django.contrib.sites.models.Site` object
-for the value of :setting:`settings.SITE_ID <SITE_ID>` is quite common, so
-the :class:`~django.contrib.sites.models.Site` model's manager has a
-``get_current()`` method. This example is equivalent to the previous one::
+For code that relies on getting the current domain but cannot be certain
+that the sites framework will be installed for any given project, the
+:func:`~django.contrib.sites.get_current_site` function will return either
+a Site instance (if the sites framework is installed) or a RequestSite 
+instance (if it is not). This allows loose coupling with the sites framework 
+and provides a usable fallback for cases where it is not installed.
 
-    from django.contrib.sites.models import Site
+Additionally, you can implement your own version of ``get_current_site()``
+and assign it to settings.SITE_CALLBACK in order to override all ``get_current_site()``
+behavior.  See the section "How ``get_current_site`` Works", above.
 
-    def my_view(request):
-        current_site = Site.objects.get_current()
-        if current_site.domain == 'foo.com':
-            # Do something
-        else:
-            # Do something else.
+Getting the current domain for display
+--------------------------------------
 
 .. versionchanged:: 1.3
 
-For code which relies on getting the current domain but cannot be certain
-that the sites framework will be installed for any given project, there is a
-utility function :func:`~django.contrib.sites.models.get_current_site` that
-takes a request object as an argument and returns either a Site instance (if
-the sites framework is installed) or a RequestSite instance (if it is not).
-This allows loose coupling with the sites framework and provides a usable
-fallback for cases where it is not installed.
-
-Getting the current domain for display
---------------------------------------
-
 LJWorld.com and Lawrence.com both have e-mail alert functionality, which lets
 readers sign up to get notifications when news happens. It's pretty basic: A
 reader signs up on a Web form, and he immediately gets an e-mail saying,
@@ -168,22 +215,21 @@
 
 It'd be inefficient and redundant to implement this signup-processing code
 twice, so the sites use the same code behind the scenes. But the "thank you for
-signing up" notice needs to be different for each site. By using
-:class:`~django.contrib.sites.models.Site`
-objects, we can abstract the "thank you" notice to use the values of the
-current site's :attr:`~django.contrib.sites.models.Site.name` and
-:attr:`~django.contrib.sites.models.Site.domain`.
+signing up" notice needs to be different for each site. By using site objects,
+we can abstract the "thank you" notice to use the values of whatever object
+is returned by :func:`~django.contrib.sites.get_current_site`, specifically 
+``name`` and ``domain``.
 
 Here's an example of what the form-handling view looks like::
 
-    from django.contrib.sites.models import Site
+    from django.contrib.sites import get_current_site
     from django.core.mail import send_mail
 
     def register_for_newsletter(request):
         # Check form values, etc., and subscribe the user.
         # ...
 
-        current_site = Site.objects.get_current()
+        current_site = get_current_site(request)
         send_mail('Thanks for subscribing to %s alerts' % current_site.name,
             'Thanks for your subscription. We appreciate it.\n\n-The %s team.' % current_site.name,
             'editor@%s' % current_site.domain,
@@ -195,11 +241,67 @@
 lawrence.com alerts." On LJWorld.com, the e-mail has the subject "Thanks for
 subscribing to LJWorld.com alerts." Same goes for the e-mail's message body.
 
-Note that an even more flexible (but more heavyweight) way of doing this would
-be to use Django's template system. Assuming Lawrence.com and LJWorld.com have
-different template directories (:setting:`TEMPLATE_DIRS`), you could simply farm out
-to the template system like so::
+It's a good idea to exploit the sites objects returned by
+:func:`~django.contrib.sites.get_current_site` objects as much as possible, 
+to remove unneeded complexity and redundancy.
 
+Getting the current domain for full URLs
+----------------------------------------
+
+.. versionchanged:: 1.3
+
+Django's ``get_absolute_url()`` convention is nice for getting your objects'
+URL without the domain name, but in some cases you might want to display the
+full URL -- with ``http://`` and the domain and everything -- for an object.
+To do this, you can use the sites framework. A simple example::
+
+    from django.contrib.sites import get_current_site
+    from django.shortcuts import render
+    from myapp.models import MyModel
+
+    def view_with_full_urls(request, obj_id):
+        obj = MyModel.objects.get(id=obj_id)
+        partial_url = obj.get_absolute_url()
+        domain = get_current_site(request).domain         
+
+        context_data = { "full_url" : 'http://%s%s' % (domain, partial_url) }
+        return render(request, "my_template.html", context_data)
+
+Static Multitenancy vs. Dynamic Multitenancy
+============================================
+
+.. versionadded:: 1.3
+
+As per 'wikipedia'_:
+
+    "Multitenancy refers to a principle in software architecture where a single instance 
+    of the software runs on a server, serving multiple client organizations (tenants). 
+    Multitenancy is contrasted with a multi-instance architecture where separate software 
+    instances (or hardware systems) are set up for different client organizations."
+
+Django implements two forms of multitenancy, what is referred to in this document
+as "static" (or partial) multitenancy and "dynamic" (or full) multitenancy. 
+
+Static multitenancy is distinguished by its deployment architecture: a common
+database installation is shared by N runtime instances of Django, all running off of
+the same codebase, but each instance differing in its localized configuration. 
+Two instances operating in this fasion might differ, for example, only in the templates 
+installed in their respective template directories.  Each instance would be 
+affiliated with only a single ``Site``, configured using settings.SITE_ID, 
+and would access specific site-affiliated data only affilitated with that ``Site``.  
+In such a configuration, to change the ``Site`` with which an instance is affiliated 
+would require restarging that instance and possibly reconfiguring the webserver.
+
+Revisit for a moment the case of Lawrence.com and LJWorld.com's email notification system.
+Using Django's template system, each website can send out notifications that contain
+unique layout and design elements, in addition to unique content.  All that a static 
+multitenant setup requires to enable this behavior is that the TEMPLATE_DIRS setting in 
+each instance differ, and that :file:`subject.txt` and :file:`message.txt` template
+files exist in both the LJWorld.com and Lawrence.com template directories. 
+
+The view code itself would be the same, however, in both runtime instances, and would
+reference the same database:
+
     from django.core.mail import send_mail
     from django.template import loader, Context
 
@@ -208,61 +310,82 @@
         # ...
 
         subject = loader.get_template('alerts/subject.txt').render(Context({}))
-        message = loader.get_template('alerts/message.txt').render(Context({}))
+        message = loader.get_template('alerts/message.html').render(Context({}))
         send_mail(subject, message, 'editor@ljworld.com', [user.email])
 
         # ...
 
-In this case, you'd have to create :file:`subject.txt` and :file:`message.txt` template
-files for both the LJWorld.com and Lawrence.com template directories. That
-gives you more flexibility, but it's also more complex.
+Typically, a static multitenant implementation involves a small and relatively static 
+set of sites running off a given codebase.  In a dynamic multitenant system, however, 
+the set of tenant sites changes often and should be managable via the database without 
+the need for differing instance configurations.
 
-It's a good idea to exploit the :class:`~django.contrib.sites.models.Site`
-objects as much as possible, to remove unneeded complexity and redundancy.
+In a dynamic multitnant system, all instances are identical. Instead of relying on a 
+global settings.SITE_ID value to determine the current site for the whole instance, 
+each request is determined to have a "current site" based on the host it specifies.  
+The requirement to have the ``request`` on hand in every situation where the "current
+site" is to be retrieved does place constraints on a system's design.  Furthermore, 
+dynamic multitenant systems cannot take advantage of static configuration differences 
+(such as in the above example) in order to provide differing functionality.  All
+multitenant behavior must be handled programatically.  Even with these limitations,
+however, dynamic multitenancy is superior to static multitenency in its flexibility, 
+and its ability to scale to a large number of tenants.
 
-Getting the current domain for full URLs
-----------------------------------------
+...note::
+    Prior to Django version 1.3, only "static multitenancy" was officially supported,
+    and as a result at various places in the Django source code settings.SITE_ID 
+    was used to retrieve the "current site" object without reference to the 
+    incomming request.  Prior versions of this documentation encouraged such an approach. 
+    With the introduction of `get_current_site` as the standard entry-point to the 
+    sites framework, however, direct use of settings.SITE_ID to retrieve the current 
+    site, or any attempt to retrieve the current site inside the request-response 
+    loop without reference to the incomming request, has been deprecated.
+    
+    Care should be taken in implementing your multitenant system to always have the 
+    recent request on hand wherever you might wish to retrieve the current site. Always
+    use ``get_current_site`` in every such circumstance, even if you are only 
+    implementing static multitenancy, in order to ensure that your application is
+    compatible with both forms of multitenancy should you wish to shift your 
+    implementation in the future, and to avoid lock-in.
 
-Django's ``get_absolute_url()`` convention is nice for getting your objects'
-URL without the domain name, but in some cases you might want to display the
-full URL -- with ``http://`` and the domain and everything -- for an object.
-To do this, you can use the sites framework. A simple example::
+.. _wikipedia: http://en.wikipedia.org/wiki/Multitenancy
 
-    >>> from django.contrib.sites.models import Site
-    >>> obj = MyModel.objects.get(id=3)
-    >>> obj.get_absolute_url()
-    '/mymodel/objects/3/'
-    >>> Site.objects.get_current().domain
-    'example.com'
-    >>> 'http://%s%s' % (Site.objects.get_current().domain, obj.get_absolute_url())
-    'http://example.com/mymodel/objects/3/'
-
 Caching the current ``Site`` object
 ===================================
 
+.. versionchanged:: 1.3
+
 As the current site is stored in the database, each call to
-``Site.objects.get_current()`` could result in a database query. But Django is a
-little cleverer than that: on the first request, the current site is cached, and
-any subsequent call returns the cached data instead of hitting the database.
+:func:`~django.contrib.sites.get_current_site` could result in a database query.
+But Django helps reduce this burden when a ``Site`` object's id (such as 
+settings.SITE_ID) is being used to retrieve the Site record. On the first request, 
+the current site is cached, and any subsequent call returns the cached data instead 
+of hitting the database.
 
 If for any reason you want to force a database query, you can tell Django to
-clear the cache using ``Site.objects.clear_cache()``::
+clear the cache using ``Site.objects.clear_cache()``:
 
     # First call; current site fetched from database.
-    current_site = Site.objects.get_current()
+    current_site = get_current_site(request)
     # ...
 
-    # Second call; current site fetched from cache.
-    current_site = Site.objects.get_current()
+    # Second call; current site fetched from cache, as long as SITE_ID is defined.
+    current_site = get_current_site(request)
     # ...
 
     # Force a database query for the third call.
     Site.objects.clear_cache()
-    current_site = Site.objects.get_current()
+    current_site = get_current_site(request)
 
 The ``CurrentSiteManager``
 ==========================
 
+..note::
+    The ``CurrentSiteManager`` is deprecated in version 1.3, due to its 
+    reliance on settings.SITE_ID as the sole manner it uses to determing
+    the current site.  Similar functionality can be achived by using
+    ``get_current_site`` in your views.
+
 .. class:: django.contrib.sites.managers.CurrentSiteManager
 
 If :class:`~django.contrib.sites.models.Site` plays a key role in your
@@ -401,9 +524,10 @@
 :class:`~django.contrib.sites.models.RequestSite` class, which can be used as a
 fallback when the database-backed sites framework is not available.
 
-A :class:`~django.contrib.sites.models.RequestSite` object has a similar
-interface to a normal :class:`~django.contrib.sites.models.Site` object, except
-its :meth:`~django.contrib.sites.models.RequestSite.__init__()` method takes an
+The :class:`~django.contrib.sites.models.RequestSite` object, referred to at 
+various places above, has a similar interface to a normal 
+:class:`~django.contrib.sites.models.Site` object, except its 
+:meth:`~django.contrib.sites.models.RequestSite.__init__()` method takes an
 :class:`~django.http.HttpRequest` object. It's able to deduce the
 :attr:`~django.contrib.sites.models.RequestSite.domain` and
 :attr:`~django.contrib.sites.models.RequestSite.name` by looking at the
@@ -411,3 +535,9 @@
 and :meth:`~django.contrib.sites.models.RequestSite.delete()` methods to match
 the interface of :class:`~django.contrib.sites.models.Site`, but the methods
 raise :exc:`NotImplementedError`.
+
+As noted above, if your application cannot handle a ``RequestSite`` object, for
+instance because you need a ``Site`` object to use in a foreign key or many to many 
+database relation, you can call ``get_current_site`` with the ``require_site_object``
+keyword argument set to ``True``.  In such a case, if the sites framework is
+not installed, an exception will be raised.
