Code

Ticket #14264: 14264.2.diff

File 14264.2.diff, 13.7 KB (added by gwilson, 3 years ago)
Line 
1=== modified file 'django/conf/__init__.py'
2--- django/conf/__init__.py     2011-06-30 08:06:19 +0000
3+++ django/conf/__init__.py     2011-09-10 20:02:53 +0000
4@@ -9,6 +9,7 @@
5 import os
6 import re
7 import time     # Needed for Windows
8+import types
9 import warnings
10 
11 from django.conf import global_settings
12@@ -49,9 +50,8 @@
13         """
14         if self._wrapped is not empty:
15             raise RuntimeError('Settings already configured.')
16-        holder = UserSettingsHolder(default_settings)
17-        for name, value in options.items():
18-            setattr(holder, name, value)
19+        holder = UserSettingsHolder(default_settings=default_settings,
20+                                    extra_settings=options)
21         self._wrapped = holder
22 
23     @property
24@@ -62,10 +62,25 @@
25         return self._wrapped is not empty
26 
27 
28-class BaseSettings(object):
29+class Settings(object):
30     """
31     Common logic for settings whether set by a module or by the user.
32     """
33+
34+    # Settings that should be converted into tuples if they're mistakenly
35+    # entered as strings.
36+    tuple_settings = ("INSTALLED_APPS", "TEMPLATE_DIRS")
37+    # Flag to specify whether or not changes should be made to environment.
38+    environ_changes_allowed = True
39+
40+    def __init__(self, settings_module=None, default_settings=global_settings,
41+                 extra_settings=None):
42+        if settings_module:
43+            # store the settings module in case someone later cares
44+            self.SETTINGS_MODULE = settings_module
45+        self.initialize([default_settings, settings_module, extra_settings])
46+        self.post_setup()
47+
48     def __setattr__(self, name, value):
49         if name in ("MEDIA_URL", "STATIC_URL") and value and not value.endswith('/'):
50             warnings.warn("If set, %s must end with a slash" % name,
51@@ -75,35 +90,30 @@
52                           "use STATIC_URL instead.", DeprecationWarning)
53         object.__setattr__(self, name, value)
54 
55-
56-class Settings(BaseSettings):
57-    def __init__(self, settings_module):
58-        # update this dict from global settings (but only for ALL_CAPS settings)
59-        for setting in dir(global_settings):
60-            if setting == setting.upper():
61-                setattr(self, setting, getattr(global_settings, setting))
62-
63-        # store the settings module in case someone later cares
64-        self.SETTINGS_MODULE = settings_module
65-
66-        try:
67-            mod = importlib.import_module(self.SETTINGS_MODULE)
68-        except ImportError, e:
69-            raise ImportError("Could not import settings '%s' (Is it on sys.path?): %s" % (self.SETTINGS_MODULE, e))
70-
71-        # Settings that should be converted into tuples if they're mistakenly entered
72-        # as strings.
73-        tuple_settings = ("INSTALLED_APPS", "TEMPLATE_DIRS")
74-
75-        for setting in dir(mod):
76-            if setting == setting.upper():
77-                setting_value = getattr(mod, setting)
78-                if setting in tuple_settings and type(setting_value) == str:
79-                    setting_value = (setting_value,) # In case the user forgot the comma.
80-                setattr(self, setting, setting_value)
81-
82-        # Expand entries in INSTALLED_APPS like "django.contrib.*" to a list
83-        # of all those apps.
84+    def initialize(self, settings_objs):
85+        """
86+        Initializes this settings object based on attributes/keys from the
87+        objects in the settings_objs list.
88+        """
89+        for obj in settings_objs:
90+            if not obj:
91+                continue
92+            for setting, value in dict_from_object(obj).iteritems():
93+                    setattr(self, setting, value)
94+
95+    def post_setup(self):
96+        self._correct_tuple_settings()
97+        # Must remain after call to correct tuple settings, because
98+        # INSTALLED_APPS is a tuple setting.
99+        self._expand_installed_apps()
100+        self._set_tz()
101+        self._setup_logging()
102+
103+    def _expand_installed_apps(self):
104+        """
105+        Expand glob entries in INSTALLED_APPS, e.g. "django.contrib.*", to a
106+        list of all those apps.
107+        """
108         new_installed_apps = []
109         for app in self.INSTALLED_APPS:
110             if app.endswith('.*'):
111@@ -119,6 +129,19 @@
112                 new_installed_apps.append(app)
113         self.INSTALLED_APPS = new_installed_apps
114 
115+    def _correct_tuple_settings(self):
116+        """
117+        For settings that are meant to be tuples, auto correct if user forgot
118+        trailing comma on a single value.
119+        """
120+        for setting in self.tuple_settings:
121+            value = getattr(self, setting, None)
122+            if isinstance(value, basestring):
123+                setattr(self, setting, (value,))
124+
125+    def _set_tz(self):
126+        if not self.environ_changes_allowed:
127+            return
128         if hasattr(time, 'tzset') and self.TIME_ZONE:
129             # When we can, attempt to validate the timezone. If we can't find
130             # this file, no check happens and it's harmless.
131@@ -131,6 +154,7 @@
132             os.environ['TZ'] = self.TIME_ZONE
133             time.tzset()
134 
135+    def _setup_logging(self):
136         # Settings are configured, so we can set up the logger if required
137         if self.LOGGING_CONFIG:
138             # First find the logging configuration function ...
139@@ -145,33 +169,50 @@
140             logging_config_func(self.LOGGING)
141 
142 
143-class UserSettingsHolder(BaseSettings):
144+class UserSettingsHolder(Settings):
145     """
146     Holder for user configured settings.
147     """
148     # SETTINGS_MODULE doesn't make much sense in the manually configured
149     # (standalone) case.
150     SETTINGS_MODULE = None
151-
152-    def __init__(self, default_settings):
153-        """
154-        Requests for configuration variables not in this class are satisfied
155-        from the module specified in default_settings (if possible).
156-        """
157-        self.default_settings = default_settings
158-
159-    def __getattr__(self, name):
160-        return getattr(self.default_settings, name)
161-
162-    def __dir__(self):
163-        return self.__dict__.keys() + dir(self.default_settings)
164-
165-    # For Python < 2.6:
166-    __members__ = property(lambda self: self.__dir__())
167+    # Don't make any modifications to the process environment variables.
168+    environ_changes_allowed = False
169+
170 
171 settings = LazySettings()
172 
173 
174+def import_module(name):
175+    try:
176+        mod = importlib.import_module(name)
177+    except ImportError, e:
178+        raise ImportError(
179+            "Could not import settings '%s' (Is it on sys.path?): %s"
180+            % (name, e))
181+    return mod
182+
183+
184+def dict_from_object(obj):
185+    """
186+    Return a dictionary of obj's attributes, where obj can be one of:
187+
188+    * A dictionary
189+    * A string containing the name of a settings module
190+    * A Settings instance
191+    * A module
192+
193+    Only attributes/keys that are ALL CAPS are returned.
194+    """
195+    if isinstance(obj, basestring):
196+        obj = import_module(obj)
197+    if not isinstance(obj, dict):
198+        obj = dict([(attr, getattr(obj, attr)) for attr in dir(obj)])
199+
200+    # Only keep attributes that are (ALL CAPS).
201+    keep = lambda k: k == k.upper()
202+    return dict([(k, v) for k, v in obj.iteritems() if keep(k)])
203+
204 
205 def compat_patch_logging_config(logging_config):
206     """
207
208=== modified file 'django/core/management/commands/diffsettings.py'
209--- django/core/management/commands/diffsettings.py     2010-02-21 23:39:27 +0000
210+++ django/core/management/commands/diffsettings.py     2011-09-10 19:50:42 +0000
211@@ -1,8 +1,5 @@
212 from django.core.management.base import NoArgsCommand
213 
214-def module_to_dict(module, omittable=lambda k: k.startswith('_')):
215-    "Converts a module namespace to a Python dictionary. Used by get_settings_diff."
216-    return dict([(k, repr(v)) for k, v in module.__dict__.items() if not omittable(k)])
217 
218 class Command(NoArgsCommand):
219     help = """Displays differences between the current settings.py and Django's
220@@ -13,20 +10,20 @@
221 
222     def handle_noargs(self, **options):
223         # Inspired by Postfix's "postconf -n".
224-        from django.conf import settings, global_settings
225+        from django.conf import settings, global_settings, dict_from_object
226 
227         # Because settings are imported lazily, we need to explicitly load them.
228         settings._setup()
229 
230-        user_settings = module_to_dict(settings._wrapped)
231-        default_settings = module_to_dict(global_settings)
232+        user_settings = dict_from_object(settings._wrapped)
233+        default_settings = dict_from_object(global_settings)
234 
235         output = []
236         keys = user_settings.keys()
237         keys.sort()
238         for key in keys:
239             if key not in default_settings:
240-                output.append("%s = %s  ###" % (key, user_settings[key]))
241+                output.append("%s = %r  ###" % (key, user_settings[key]))
242             elif user_settings[key] != default_settings[key]:
243-                output.append("%s = %s" % (key, user_settings[key]))
244+                output.append("%s = %r" % (key, user_settings[key]))
245         return '\n'.join(output)
246
247=== modified file 'django/test/utils.py'
248--- django/test/utils.py        2011-09-04 21:51:53 +0000
249+++ django/test/utils.py        2011-09-10 18:19:44 +0000
250@@ -221,9 +221,8 @@
251         return inner
252 
253     def enable(self):
254-        override = OverrideSettingsHolder(settings._wrapped)
255-        for key, new_value in self.options.items():
256-            setattr(override, key, new_value)
257+        override = OverrideSettingsHolder(default_settings=settings._wrapped,
258+                                          extra_settings=self.options)
259         settings._wrapped = override
260 
261     def disable(self):
262
263=== modified file 'tests/regressiontests/app_loading/test_settings.py'
264--- tests/regressiontests/app_loading/test_settings.py  2009-03-08 08:39:48 +0000
265+++ tests/regressiontests/app_loading/test_settings.py  2011-09-10 03:23:11 +0000
266@@ -1,3 +1,5 @@
267 INSTALLED_APPS = (
268     'parent.*',
269 )
270+
271+TIME_ZONE='Europe/London'
272
273=== modified file 'tests/regressiontests/app_loading/tests.py'
274--- tests/regressiontests/app_loading/tests.py  2011-04-27 16:51:43 +0000
275+++ tests/regressiontests/app_loading/tests.py  2011-09-10 03:45:40 +0000
276@@ -3,28 +3,11 @@
277 import sys
278 import time
279 
280-from django.conf import Settings
281+from django.conf import Settings, LazySettings
282 from django.db.models.loading import cache, load_app, get_model, get_models
283 from django.utils.unittest import TestCase
284 
285 
286-class InstalledAppsGlobbingTest(TestCase):
287-    def setUp(self):
288-        self.OLD_SYS_PATH = sys.path[:]
289-        sys.path.append(os.path.dirname(os.path.abspath(__file__)))
290-        self.OLD_TZ = os.environ.get("TZ")
291-
292-    def test_globbing(self):
293-        settings = Settings('test_settings')
294-        self.assertEqual(settings.INSTALLED_APPS, ['parent.app', 'parent.app1', 'parent.app_2'])
295-
296-    def tearDown(self):
297-        sys.path = self.OLD_SYS_PATH
298-        if hasattr(time, "tzset") and self.OLD_TZ:
299-            os.environ["TZ"] = self.OLD_TZ
300-            time.tzset()
301-
302-
303 class EggLoadingTest(TestCase):
304 
305     def setUp(self):
306@@ -123,3 +106,69 @@
307         self.assertEqual(
308             set(NotInstalledModel._meta.get_all_field_names()),
309             set(["id", "relatedmodel", "m2mrelatedmodel"]))
310+
311+
312+class SettingsMixin(object):
313+
314+    def setUp(self):
315+        self.OLD_SYS_PATH = sys.path[:]
316+        sys.path.append(os.path.dirname(os.path.abspath(__file__)))
317+        self.OLD_TZ = os.environ.get("TZ")
318+
319+    def tearDown(self):
320+        sys.path = self.OLD_SYS_PATH
321+        if hasattr(time, "tzset") and self.OLD_TZ:
322+            os.environ["TZ"] = self.OLD_TZ
323+            time.tzset()
324+
325+
326+class SettingsConfigureTest(SettingsMixin, TestCase):
327+    """
328+    Tests for settings using settings.configure().
329+    """
330+
331+    def test_installed_app_globbing(self):
332+        settings = LazySettings()
333+        settings.configure(INSTALLED_APPS=('parent.*',))
334+        self.assertEqual(settings.INSTALLED_APPS, ['parent.app', 'parent.app1', 'parent.app_2'])
335+
336+    def test_tuple_settings(self):
337+        settings = LazySettings()
338+        settings.configure(INSTALLED_APPS="parent.app", TEMPLATE_DIRS="foo")
339+        self.assertEqual(settings.INSTALLED_APPS, ['parent.app'])
340+        self.assertEqual(settings.TEMPLATE_DIRS, ('foo',))
341+
342+    def test_time_zone_environ(self):
343+        """
344+        Time zone environment variable shouldn't get altered.
345+        """
346+        orig_tz = os.environ.get('TZ')
347+        settings = LazySettings()
348+        settings.configure(TIME_ZONE='Europe/London')
349+        # Time zone updated, but not environment variable.
350+        self.assertEqual(settings.TIME_ZONE, 'Europe/London')
351+        self.assertEqual(os.environ.get('TZ'), orig_tz)
352+
353+
354+class SettingsTest(SettingsMixin, TestCase):
355+    """
356+    Tests for settings using Settings object.
357+    """
358+
359+    def test_installed_app_globbing(self):
360+        settings = Settings('test_settings')
361+        self.assertEqual(settings.INSTALLED_APPS, ['parent.app', 'parent.app1', 'parent.app_2'])
362+
363+    def test_tuple_settings(self):
364+        settings = Settings('tuple_settings')
365+        self.assertEqual(settings.INSTALLED_APPS, ['parent.app'])
366+        self.assertEqual(settings.TEMPLATE_DIRS, ('foo',))
367+
368+    def test_time_zone_environ(self):
369+        """
370+        Time zone environment variable shouldn't get altered.
371+        """
372+        orig_tz = os.environ.get('TZ')
373+        settings = Settings('test_settings')
374+        self.assertEqual(settings.TIME_ZONE, 'Europe/London')
375+        self.assertEqual(os.environ.get('TZ'), 'Europe/London')
376
377=== added file 'tests/regressiontests/app_loading/tuple_settings.py'
378--- tests/regressiontests/app_loading/tuple_settings.py 1970-01-01 00:00:00 +0000
379+++ tests/regressiontests/app_loading/tuple_settings.py 2011-09-10 06:48:24 +0000
380@@ -0,0 +1,3 @@
381+# Settings file for testing correction of tuple settings.
382+INSTALLED_APPS="parent.app"
383+TEMPLATE_DIRS="foo"
384