diff --git a/django/test/testcases.py b/django/test/testcases.py
index d85bc27..bfa1aeb 100644
--- a/django/test/testcases.py
+++ b/django/test/testcases.py
@@ -2,7 +2,6 @@ from __future__ import with_statement
 
 import re
 import sys
-from contextlib import contextmanager
 from functools import wraps
 from urlparse import urlsplit, urlunsplit
 from xml.dom.minidom import parseString, Node
@@ -17,7 +16,7 @@ from django.db import (transaction, connection, connections, DEFAULT_DB_ALIAS,
 from django.http import QueryDict
 from django.test import _doctest as doctest
 from django.test.client import Client
-from django.test.utils import get_warnings_state, restore_warnings_state
+from django.test.utils import get_warnings_state, restore_warnings_state, override_settings
 from django.utils import simplejson, unittest as ut2
 from django.utils.encoding import smart_str
 
@@ -342,21 +341,12 @@ class TransactionTestCase(ut2.TestCase):
         """
         restore_warnings_state(self._warnings_state)
 
-    @contextmanager
-    def settings(self, **options):
+    def settings(self, **kwargs):
         """
         A context manager that temporarily sets a setting and reverts
         back to the original value when exiting the context.
         """
-        old_wrapped = settings._wrapped
-        override = UserSettingsHolder(settings._wrapped)
-        try:
-            for key, new_value in options.items():
-                setattr(override, key, new_value)
-            settings._wrapped = override
-            yield
-        finally:
-            settings._wrapped = old_wrapped
+        return override_settings(**kwargs)
 
     def assertRedirects(self, response, expected_url, status_code=302,
                         target_status_code=200, host=None, msg_prefix=''):
diff --git a/django/test/utils.py b/django/test/utils.py
index bf1dc4f..1201dd4 100644
--- a/django/test/utils.py
+++ b/django/test/utils.py
@@ -2,16 +2,21 @@ import sys
 import time
 import os
 import warnings
-from django.conf import settings
+from django.conf import settings, UserSettingsHolder
 from django.core import mail
 from django.core.mail.backends import locmem
 from django.test import signals
 from django.template import Template, loader, TemplateDoesNotExist
 from django.template.loaders import cached
 from django.utils.translation import deactivate
+from django.utils.functional import wraps
 
-__all__ = ('Approximate', 'ContextList', 'setup_test_environment',
-       'teardown_test_environment', 'get_runner')
+
+__all__ = (
+    'Approximate', 'ContextList',  'get_runner', 'settings_override',
+    'setup_test_environment', 'teardown_test_environment',
+    'setup_test_template_loader', 'restore_template_loaders',
+)
 
 RESTORE_LOADERS_ATTR = '_original_template_source_loaders'
 
@@ -160,3 +165,39 @@ def restore_template_loaders():
     """
     loader.template_source_loaders = getattr(loader, RESTORE_LOADERS_ATTR)
     delattr(loader, RESTORE_LOADERS_ATTR)
+
+
+class override_settings(object):
+    """
+    Acts as either a decorator, or a context manager.  If it's a decorator it
+    takes a function and returns a wrapped function.  If it's a contextmanager
+    it's used with the ``with`` statement.  In either event entering/exiting
+    are called before and after, respectively, the function/block is executed.
+    """
+    def __init__(self, **kwargs):
+        self.options = kwargs
+        self.wrapped = settings._wrapped
+
+    def __enter__(self):
+        self.enable()
+
+    def __exit__(self, exc_type, exc_value, traceback):
+        self.disable()
+
+    def __call__(self, func):
+        @wraps(func)
+        def inner(*args, **kwargs):
+            self.enable()
+            result = func(*args, **kwargs)
+            self.disable()
+            return result
+        return inner
+
+    def enable(self):
+        override = UserSettingsHolder(settings._wrapped)
+        for key, new_value in self.options.items():
+            setattr(override, key, new_value)
+        settings._wrapped = override
+
+    def disable(self):
+        settings._wrapped = self.wrapped
diff --git a/docs/topics/testing.txt b/docs/topics/testing.txt
index 61e860c..f9739e5 100644
--- a/docs/topics/testing.txt
+++ b/docs/topics/testing.txt
@@ -1376,7 +1376,14 @@ this use case Django provides a standard `Python context manager`_
     from django.test import TestCase
 
     class LoginTestCase(TestCase):
-        def test_overriding_settings(self):
+
+        def test_login(self):
+
+            # First check for the default behavior
+            response = self.client.get('/sekrit/')
+            self.assertRedirects(response, '/accounts/login/?next=/sekrit/')
+
+            # Then override the LOGING_URL setting
             with self.settings(LOGIN_URL='/other/login/'):
                 response = self.client.get('/sekrit/')
                 self.assertRedirects(response, '/other/login/?next=/sekrit/')
@@ -1384,7 +1391,50 @@ this use case Django provides a standard `Python context manager`_
 This example will override the :setting:`LOGIN_URL` setting for the code
 in the ``with`` block and reset its value to the previous state afterwards.
 
+.. function:: utils.override_settings
+
+In case you want to override a setting for just one test method or even the
+whole TestCase class, Django provides the
+:func:`django.test.utils.override_settings` decorator_. It's used like this::
+
+    from django.test import TestCase
+    from django.test.utils import override_settings
+
+    class LoginTestCase(TestCase):
+
+        @override_settings(LOGIN_URL='/other/login/')
+        def test_login(self):
+            response = self.client.get('/sekrit/')
+            self.assertRedirects(response, '/other/login/?next=/sekrit/')
+
+The decorator can also be applied to test case classes::
+
+    from django.test import TestCase
+    from django.test.utils import override_settings
+
+    class LoginTestCase(TestCase):
+
+        def test_login(self):
+            response = self.client.get('/sekrit/')
+            self.assertRedirects(response, '/other/login/?next=/sekrit/')
+
+    LoginTestCase = override_settings(LOGIN_URL='/other/login/')(LoginTestCase)
+
+On Python 2.6 and higher you can also use the well known decorator syntax to
+decorate the class::
+
+    from django.test import TestCase
+    from django.test.utils import override_settings
+
+    @override_settings(LOGIN_URL='/other/login/')
+    class LoginTestCase(TestCase):
+
+        def test_login(self):
+            response = self.client.get('/sekrit/')
+            self.assertRedirects(response, '/other/login/?next=/sekrit/')
+
 .. _`Python context manager`: http://www.python.org/dev/peps/pep-0343/
+.. _`decorator`: http://www.python.org/dev/peps/pep-0318/
 
 Emptying the test outbox
 ~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/tests/regressiontests/mail/tests.py b/tests/regressiontests/mail/tests.py
index 706b3af..5d5f52b 100644
--- a/tests/regressiontests/mail/tests.py
+++ b/tests/regressiontests/mail/tests.py
@@ -9,48 +9,14 @@ from StringIO import StringIO
 import tempfile
 import threading
 
-from django.conf import settings
 from django.core import mail
 from django.core.mail import (EmailMessage, mail_admins, mail_managers,
         EmailMultiAlternatives, send_mail, send_mass_mail)
 from django.core.mail.backends import console, dummy, locmem, filebased, smtp
 from django.core.mail.message import BadHeaderError
 from django.test import TestCase
+from django.test.utils import override_settings
 from django.utils.translation import ugettext_lazy
-from django.utils.functional import wraps
-
-
-def alter_django_settings(**kwargs):
-    oldvalues = {}
-    nonexistant = []
-    for setting, newvalue in kwargs.iteritems():
-        try:
-            oldvalues[setting] = getattr(settings, setting)
-        except AttributeError:
-            nonexistant.append(setting)
-        setattr(settings, setting, newvalue)
-    return oldvalues, nonexistant
-
-
-def restore_django_settings(state):
-    oldvalues, nonexistant = state
-    for setting, oldvalue in oldvalues.iteritems():
-        setattr(settings, setting, oldvalue)
-    for setting in nonexistant:
-        delattr(settings, setting)
-
-
-def with_django_settings(**kwargs):
-    def decorator(test):
-        @wraps(test)
-        def decorated_test(self):
-            state = alter_django_settings(**kwargs)
-            try:
-                return test(self)
-            finally:
-                restore_django_settings(state)
-        return decorated_test
-    return decorator
 
 
 class MailTests(TestCase):
@@ -251,7 +217,7 @@ class MailTests(TestCase):
             shutil.rmtree(tmp_dir)
         self.assertTrue(isinstance(mail.get_connection(), locmem.EmailBackend))
 
-    @with_django_settings(
+    @override_settings(
         EMAIL_BACKEND='django.core.mail.backends.locmem.EmailBackend',
         ADMINS=[('nobody', 'nobody@example.com')],
         MANAGERS=[('nobody', 'nobody@example.com')])
@@ -323,10 +289,11 @@ class BaseEmailBackendTests(object):
     email_backend = None
 
     def setUp(self):
-        self.__settings_state = alter_django_settings(EMAIL_BACKEND=self.email_backend)
+        self.settings_override = override_settings(EMAIL_BACKEND=self.email_backend)
+        self.settings_override.enable()
 
     def tearDown(self):
-        restore_django_settings(self.__settings_state)
+        self.settings_override.disable()
 
     def assertStartsWith(self, first, second):
         if not first.startswith(second):
@@ -375,7 +342,7 @@ class BaseEmailBackendTests(object):
         self.assertEqual(message.get_payload(), "Content")
         self.assertEqual(message["from"], "=?utf-8?q?Firstname_S=C3=BCrname?= <from@example.com>")
 
-    @with_django_settings(MANAGERS=[('nobody', 'nobody@example.com')])
+    @override_settings(MANAGERS=[('nobody', 'nobody@example.com')])
     def test_html_mail_managers(self):
         """Test html_message argument to mail_managers"""
         mail_managers('Subject', 'Content', html_message='HTML Content')
@@ -390,7 +357,7 @@ class BaseEmailBackendTests(object):
         self.assertEqual(message.get_payload(1).get_payload(), 'HTML Content')
         self.assertEqual(message.get_payload(1).get_content_type(), 'text/html')
 
-    @with_django_settings(ADMINS=[('nobody', 'nobody@example.com')])
+    @override_settings(ADMINS=[('nobody', 'nobody@example.com')])
     def test_html_mail_admins(self):
         """Test html_message argument to mail_admins """
         mail_admins('Subject', 'Content', html_message='HTML Content')
@@ -405,8 +372,9 @@ class BaseEmailBackendTests(object):
         self.assertEqual(message.get_payload(1).get_payload(), 'HTML Content')
         self.assertEqual(message.get_payload(1).get_content_type(), 'text/html')
 
-    @with_django_settings(ADMINS=[('nobody', 'nobody+admin@example.com')],
-                         MANAGERS=[('nobody', 'nobody+manager@example.com')])
+    @override_settings(
+        ADMINS=[('nobody', 'nobody+admin@example.com')],
+        MANAGERS=[('nobody', 'nobody+manager@example.com')])
     def test_manager_and_admin_mail_prefix(self):
         """
         String prefix + lazy translated subject = bad output
@@ -421,7 +389,7 @@ class BaseEmailBackendTests(object):
         message = self.get_the_message()
         self.assertEqual(message.get('subject'), '[Django] Subject')
 
-    @with_django_settings(ADMINS=(), MANAGERS=())
+    @override_settings(ADMINS=(), MANAGERS=())
     def test_empty_admins(self):
         """
         Test that mail_admins/mail_managers doesn't connect to the mail server
@@ -501,13 +469,14 @@ class FileBackendTests(BaseEmailBackendTests, TestCase):
     email_backend = 'django.core.mail.backends.filebased.EmailBackend'
 
     def setUp(self):
-        super(FileBackendTests, self).setUp()
         self.tmp_dir = tempfile.mkdtemp()
-        self.__settings_state = alter_django_settings(EMAIL_FILE_PATH=self.tmp_dir)
+        self.addCleanup(shutil.rmtree, self.tmp_dir)
+        self.settings_override = override_settings(EMAIL_FILE_PATH=self.tmp_dir)
+        self.settings_override.enable()
+        super(FileBackendTests, self).setUp()
 
     def tearDown(self):
-        restore_django_settings(self.__settings_state)
-        shutil.rmtree(self.tmp_dir)
+        self.settings_override.disable()
         super(FileBackendTests, self).tearDown()
 
     def flush_mailbox(self):
@@ -642,13 +611,15 @@ class SMTPBackendTests(BaseEmailBackendTests, TestCase):
     @classmethod
     def setUpClass(cls):
         cls.server = FakeSMTPServer(('127.0.0.1', 0), None)
-        cls.settings = alter_django_settings(
+        cls.settings_override = override_settings(
             EMAIL_HOST="127.0.0.1",
             EMAIL_PORT=cls.server.socket.getsockname()[1])
+        cls.settings_override.enable()
         cls.server.start()
 
     @classmethod
     def tearDownClass(cls):
+        cls.settings_override.disable()
         cls.server.stop()
 
     def setUp(self):
diff --git a/tests/regressiontests/settings_tests/tests.py b/tests/regressiontests/settings_tests/tests.py
index 750ccc6..d2f7f5c 100644
--- a/tests/regressiontests/settings_tests/tests.py
+++ b/tests/regressiontests/settings_tests/tests.py
@@ -1,7 +1,15 @@
 from __future__ import with_statement
-import os
+import os, sys
 from django.conf import settings, global_settings
 from django.test import TestCase
+from django.test.utils import override_settings
+from django.utils.unittest import skipIf
+
+
+class SettingGetter(object):
+    def __init__(self):
+        self.test = getattr(settings, 'TEST', 'undefined')
+
 
 class SettingsTests(TestCase):
 
@@ -29,6 +37,35 @@ class SettingsTests(TestCase):
             settings.TEST = 'test'
         self.assertRaises(AttributeError, getattr, settings, 'TEST')
 
+    @override_settings(TEST='override')
+    def test_decorator(self):
+        self.assertEqual('override', settings.TEST)
+
+    def test_context_manager(self):
+        self.assertRaises(AttributeError, getattr, settings, 'TEST')
+        override = override_settings(TEST='override')
+        self.assertRaises(AttributeError, getattr, settings, 'TEST')
+        override.enable()
+        self.assertEqual('override', settings.TEST)
+        override.disable()
+        self.assertRaises(AttributeError, getattr, settings, 'TEST')
+
+    def test_class_decorator(self):
+        self.assertEqual(SettingGetter().test, 'undefined')
+        DecoratedSettingGetter = override_settings(TEST='override')(SettingGetter)
+        self.assertEqual(DecoratedSettingGetter().test, 'override')
+        self.assertRaises(AttributeError, getattr, settings, 'TEST')
+
+    @skipIf(sys.version_info[:2] < (2, 6), "Python version is lower than 2.6")
+    def test_new_class_decorator(self):
+        self.assertEqual(SettingGetter().test, 'undefined')
+        @override_settings(TEST='override')
+        class DecoratedSettingGetter(SettingGetter):
+            pass
+        self.assertEqual(DecoratedSettingGetter().test, 'override')
+        self.assertRaises(AttributeError, getattr, settings, 'TEST')
+
+
     #
     # Regression tests for #10130: deleting settings.
     #
