=== modified file 'django/conf/__init__.py'
--- django/conf/__init__.py	2011-06-30 08:06:19 +0000
+++ django/conf/__init__.py	2011-09-10 20:02:53 +0000
@@ -9,6 +9,7 @@
 import os
 import re
 import time     # Needed for Windows
+import types
 import warnings
 
 from django.conf import global_settings
@@ -49,9 +50,8 @@
         """
         if self._wrapped is not empty:
             raise RuntimeError('Settings already configured.')
-        holder = UserSettingsHolder(default_settings)
-        for name, value in options.items():
-            setattr(holder, name, value)
+        holder = UserSettingsHolder(default_settings=default_settings,
+                                    extra_settings=options)
         self._wrapped = holder
 
     @property
@@ -62,10 +62,25 @@
         return self._wrapped is not empty
 
 
-class BaseSettings(object):
+class Settings(object):
     """
     Common logic for settings whether set by a module or by the user.
     """
+
+    # Settings that should be converted into tuples if they're mistakenly
+    # entered as strings.
+    tuple_settings = ("INSTALLED_APPS", "TEMPLATE_DIRS")
+    # Flag to specify whether or not changes should be made to environment.
+    environ_changes_allowed = True
+
+    def __init__(self, settings_module=None, default_settings=global_settings,
+                 extra_settings=None):
+        if settings_module:
+            # store the settings module in case someone later cares
+            self.SETTINGS_MODULE = settings_module
+        self.initialize([default_settings, settings_module, extra_settings])
+        self.post_setup()
+
     def __setattr__(self, name, value):
         if name in ("MEDIA_URL", "STATIC_URL") and value and not value.endswith('/'):
             warnings.warn("If set, %s must end with a slash" % name,
@@ -75,35 +90,30 @@
                           "use STATIC_URL instead.", DeprecationWarning)
         object.__setattr__(self, name, value)
 
-
-class Settings(BaseSettings):
-    def __init__(self, settings_module):
-        # update this dict from global settings (but only for ALL_CAPS settings)
-        for setting in dir(global_settings):
-            if setting == setting.upper():
-                setattr(self, setting, getattr(global_settings, setting))
-
-        # store the settings module in case someone later cares
-        self.SETTINGS_MODULE = settings_module
-
-        try:
-            mod = importlib.import_module(self.SETTINGS_MODULE)
-        except ImportError, e:
-            raise ImportError("Could not import settings '%s' (Is it on sys.path?): %s" % (self.SETTINGS_MODULE, e))
-
-        # Settings that should be converted into tuples if they're mistakenly entered
-        # as strings.
-        tuple_settings = ("INSTALLED_APPS", "TEMPLATE_DIRS")
-
-        for setting in dir(mod):
-            if setting == setting.upper():
-                setting_value = getattr(mod, setting)
-                if setting in tuple_settings and type(setting_value) == str:
-                    setting_value = (setting_value,) # In case the user forgot the comma.
-                setattr(self, setting, setting_value)
-
-        # Expand entries in INSTALLED_APPS like "django.contrib.*" to a list
-        # of all those apps.
+    def initialize(self, settings_objs):
+        """
+        Initializes this settings object based on attributes/keys from the
+        objects in the settings_objs list.
+        """
+        for obj in settings_objs:
+            if not obj:
+                continue
+            for setting, value in dict_from_object(obj).iteritems():
+                    setattr(self, setting, value)
+
+    def post_setup(self):
+        self._correct_tuple_settings()
+        # Must remain after call to correct tuple settings, because
+        # INSTALLED_APPS is a tuple setting.
+        self._expand_installed_apps()
+        self._set_tz()
+        self._setup_logging()
+
+    def _expand_installed_apps(self):
+        """
+        Expand glob entries in INSTALLED_APPS, e.g. "django.contrib.*", to a
+        list of all those apps.
+        """
         new_installed_apps = []
         for app in self.INSTALLED_APPS:
             if app.endswith('.*'):
@@ -119,6 +129,19 @@
                 new_installed_apps.append(app)
         self.INSTALLED_APPS = new_installed_apps
 
+    def _correct_tuple_settings(self):
+        """
+        For settings that are meant to be tuples, auto correct if user forgot
+        trailing comma on a single value.
+        """
+        for setting in self.tuple_settings:
+            value = getattr(self, setting, None)
+            if isinstance(value, basestring):
+                setattr(self, setting, (value,))
+
+    def _set_tz(self):
+        if not self.environ_changes_allowed:
+            return
         if hasattr(time, 'tzset') and self.TIME_ZONE:
             # When we can, attempt to validate the timezone. If we can't find
             # this file, no check happens and it's harmless.
@@ -131,6 +154,7 @@
             os.environ['TZ'] = self.TIME_ZONE
             time.tzset()
 
+    def _setup_logging(self):
         # Settings are configured, so we can set up the logger if required
         if self.LOGGING_CONFIG:
             # First find the logging configuration function ...
@@ -145,33 +169,50 @@
             logging_config_func(self.LOGGING)
 
 
-class UserSettingsHolder(BaseSettings):
+class UserSettingsHolder(Settings):
     """
     Holder for user configured settings.
     """
     # SETTINGS_MODULE doesn't make much sense in the manually configured
     # (standalone) case.
     SETTINGS_MODULE = None
-
-    def __init__(self, default_settings):
-        """
-        Requests for configuration variables not in this class are satisfied
-        from the module specified in default_settings (if possible).
-        """
-        self.default_settings = default_settings
-
-    def __getattr__(self, name):
-        return getattr(self.default_settings, name)
-
-    def __dir__(self):
-        return self.__dict__.keys() + dir(self.default_settings)
-
-    # For Python < 2.6:
-    __members__ = property(lambda self: self.__dir__())
+    # Don't make any modifications to the process environment variables.
+    environ_changes_allowed = False
+
 
 settings = LazySettings()
 
 
+def import_module(name):
+    try:
+        mod = importlib.import_module(name)
+    except ImportError, e:
+        raise ImportError(
+            "Could not import settings '%s' (Is it on sys.path?): %s"
+            % (name, e))
+    return mod
+
+
+def dict_from_object(obj):
+    """
+    Return a dictionary of obj's attributes, where obj can be one of:
+
+    * A dictionary
+    * A string containing the name of a settings module
+    * A Settings instance
+    * A module
+
+    Only attributes/keys that are ALL CAPS are returned.
+    """
+    if isinstance(obj, basestring):
+        obj = import_module(obj)
+    if not isinstance(obj, dict):
+        obj = dict([(attr, getattr(obj, attr)) for attr in dir(obj)])
+
+    # Only keep attributes that are (ALL CAPS).
+    keep = lambda k: k == k.upper()
+    return dict([(k, v) for k, v in obj.iteritems() if keep(k)])
+
 
 def compat_patch_logging_config(logging_config):
     """

=== modified file 'django/core/management/commands/diffsettings.py'
--- django/core/management/commands/diffsettings.py	2010-02-21 23:39:27 +0000
+++ django/core/management/commands/diffsettings.py	2011-09-10 19:50:42 +0000
@@ -1,8 +1,5 @@
 from django.core.management.base import NoArgsCommand
 
-def module_to_dict(module, omittable=lambda k: k.startswith('_')):
-    "Converts a module namespace to a Python dictionary. Used by get_settings_diff."
-    return dict([(k, repr(v)) for k, v in module.__dict__.items() if not omittable(k)])
 
 class Command(NoArgsCommand):
     help = """Displays differences between the current settings.py and Django's
@@ -13,20 +10,20 @@
 
     def handle_noargs(self, **options):
         # Inspired by Postfix's "postconf -n".
-        from django.conf import settings, global_settings
+        from django.conf import settings, global_settings, dict_from_object
 
         # Because settings are imported lazily, we need to explicitly load them.
         settings._setup()
 
-        user_settings = module_to_dict(settings._wrapped)
-        default_settings = module_to_dict(global_settings)
+        user_settings = dict_from_object(settings._wrapped)
+        default_settings = dict_from_object(global_settings)
 
         output = []
         keys = user_settings.keys()
         keys.sort()
         for key in keys:
             if key not in default_settings:
-                output.append("%s = %s  ###" % (key, user_settings[key]))
+                output.append("%s = %r  ###" % (key, user_settings[key]))
             elif user_settings[key] != default_settings[key]:
-                output.append("%s = %s" % (key, user_settings[key]))
+                output.append("%s = %r" % (key, user_settings[key]))
         return '\n'.join(output)

=== modified file 'django/test/utils.py'
--- django/test/utils.py	2011-09-04 21:51:53 +0000
+++ django/test/utils.py	2011-09-10 18:19:44 +0000
@@ -221,9 +221,8 @@
         return inner
 
     def enable(self):
-        override = OverrideSettingsHolder(settings._wrapped)
-        for key, new_value in self.options.items():
-            setattr(override, key, new_value)
+        override = OverrideSettingsHolder(default_settings=settings._wrapped,
+                                          extra_settings=self.options)
         settings._wrapped = override
 
     def disable(self):

=== modified file 'tests/regressiontests/app_loading/test_settings.py'
--- tests/regressiontests/app_loading/test_settings.py	2009-03-08 08:39:48 +0000
+++ tests/regressiontests/app_loading/test_settings.py	2011-09-10 03:23:11 +0000
@@ -1,3 +1,5 @@
 INSTALLED_APPS = (
     'parent.*',
 )
+
+TIME_ZONE='Europe/London'

=== modified file 'tests/regressiontests/app_loading/tests.py'
--- tests/regressiontests/app_loading/tests.py	2011-04-27 16:51:43 +0000
+++ tests/regressiontests/app_loading/tests.py	2011-09-10 03:45:40 +0000
@@ -3,28 +3,11 @@
 import sys
 import time
 
-from django.conf import Settings
+from django.conf import Settings, LazySettings
 from django.db.models.loading import cache, load_app, get_model, get_models
 from django.utils.unittest import TestCase
 
 
-class InstalledAppsGlobbingTest(TestCase):
-    def setUp(self):
-        self.OLD_SYS_PATH = sys.path[:]
-        sys.path.append(os.path.dirname(os.path.abspath(__file__)))
-        self.OLD_TZ = os.environ.get("TZ")
-
-    def test_globbing(self):
-        settings = Settings('test_settings')
-        self.assertEqual(settings.INSTALLED_APPS, ['parent.app', 'parent.app1', 'parent.app_2'])
-
-    def tearDown(self):
-        sys.path = self.OLD_SYS_PATH
-        if hasattr(time, "tzset") and self.OLD_TZ:
-            os.environ["TZ"] = self.OLD_TZ
-            time.tzset()
-
-
 class EggLoadingTest(TestCase):
 
     def setUp(self):
@@ -123,3 +106,69 @@
         self.assertEqual(
             set(NotInstalledModel._meta.get_all_field_names()),
             set(["id", "relatedmodel", "m2mrelatedmodel"]))
+
+
+class SettingsMixin(object):
+
+    def setUp(self):
+        self.OLD_SYS_PATH = sys.path[:]
+        sys.path.append(os.path.dirname(os.path.abspath(__file__)))
+        self.OLD_TZ = os.environ.get("TZ")
+
+    def tearDown(self):
+        sys.path = self.OLD_SYS_PATH
+        if hasattr(time, "tzset") and self.OLD_TZ:
+            os.environ["TZ"] = self.OLD_TZ
+            time.tzset()
+
+
+class SettingsConfigureTest(SettingsMixin, TestCase):
+    """
+    Tests for settings using settings.configure().
+    """
+
+    def test_installed_app_globbing(self):
+        settings = LazySettings()
+        settings.configure(INSTALLED_APPS=('parent.*',))
+        self.assertEqual(settings.INSTALLED_APPS, ['parent.app', 'parent.app1', 'parent.app_2'])
+
+    def test_tuple_settings(self):
+        settings = LazySettings()
+        settings.configure(INSTALLED_APPS="parent.app", TEMPLATE_DIRS="foo")
+        self.assertEqual(settings.INSTALLED_APPS, ['parent.app'])
+        self.assertEqual(settings.TEMPLATE_DIRS, ('foo',))
+
+    def test_time_zone_environ(self):
+        """
+        Time zone environment variable shouldn't get altered.
+        """
+        orig_tz = os.environ.get('TZ')
+        settings = LazySettings()
+        settings.configure(TIME_ZONE='Europe/London')
+        # Time zone updated, but not environment variable.
+        self.assertEqual(settings.TIME_ZONE, 'Europe/London')
+        self.assertEqual(os.environ.get('TZ'), orig_tz)
+
+
+class SettingsTest(SettingsMixin, TestCase):
+    """
+    Tests for settings using Settings object.
+    """
+
+    def test_installed_app_globbing(self):
+        settings = Settings('test_settings')
+        self.assertEqual(settings.INSTALLED_APPS, ['parent.app', 'parent.app1', 'parent.app_2'])
+
+    def test_tuple_settings(self):
+        settings = Settings('tuple_settings')
+        self.assertEqual(settings.INSTALLED_APPS, ['parent.app'])
+        self.assertEqual(settings.TEMPLATE_DIRS, ('foo',))
+
+    def test_time_zone_environ(self):
+        """
+        Time zone environment variable shouldn't get altered.
+        """
+        orig_tz = os.environ.get('TZ')
+        settings = Settings('test_settings')
+        self.assertEqual(settings.TIME_ZONE, 'Europe/London')
+        self.assertEqual(os.environ.get('TZ'), 'Europe/London')

=== added file 'tests/regressiontests/app_loading/tuple_settings.py'
--- tests/regressiontests/app_loading/tuple_settings.py	1970-01-01 00:00:00 +0000
+++ tests/regressiontests/app_loading/tuple_settings.py	2011-09-10 06:48:24 +0000
@@ -0,0 +1,3 @@
+# Settings file for testing correction of tuple settings.
+INSTALLED_APPS="parent.app"
+TEMPLATE_DIRS="foo"

