=== modified file 'django/conf/__init__.py'
--- django/conf/__init__.py	2011-06-30 08:06:19 +0000
+++ django/conf/__init__.py	2011-09-10 06:47:02 +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,59 @@
                           "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
+    def initialize(self, settings_objs):
+        """
+        Initializes this settings object based on attributes/keys from the
+        objects in the settings_objs list.
+
+        If the item is a string, then attempt to import a module by name.
+        Once we have a module object, or if a module object was passed, then
+        then construct a dictionary from the attributes in the module.
+        Once we have a dictionary, or if a dictionary was passed, then set all
+        ALL CAPS keys and values as attributes on this settings object.
+        """
+        for obj in settings_objs:
+            if not obj:
+                continue
+            if isinstance(obj, basestring):
+                obj = self._import_module(obj)
+            if isinstance(obj, types.ModuleType):
+                obj = self._dict_from_module(obj)
+            # obj should now be a dictionary.
+            for setting, value in obj.iteritems():
+                # Only set attributes that are (ALL CAPS).
+                if setting == setting.upper():
+                    setattr(self, setting, value)
+
+    def _import_module(self, name):
 
         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.
+            raise ImportError(
+                "Could not import settings '%s' (Is it on sys.path?): %s"
+                % (self.SETTINGS_MODULE, e))
+        return mod
+
+    def _dict_from_module(self, module):
+        d = {}
+        for setting in dir(module):
+            d[setting] = getattr(module, setting)
+        return d
+
+    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 +158,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 +183,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,20 +198,23 @@
             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
+    # Don't make any modifications to the process environment variables.
+    environ_changes_allowed = False
 
-    def __init__(self, default_settings):
+    def __init__(self, *args, **kwargs):
         """
         Requests for configuration variables not in this class are satisfied
         from the module specified in default_settings (if possible).
         """
-        self.default_settings = default_settings
+        self.default_settings = kwargs['default_settings']
+        super(UserSettingsHolder, self).__init__(*args, **kwargs)
 
     def __getattr__(self, name):
         return getattr(self.default_settings, name)

=== 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"

