diff --git a/django/conf/__init__.py b/django/conf/__init__.py index fc69bed7c61..81787d63d5e 100644 --- a/django/conf/__init__.py +++ b/django/conf/__init__.py @@ -8,40 +8,19 @@ a list of all possible variables. import os import time # Needed for Windows + from django.conf import global_settings +from django.utils.functional import LazyObject ENVIRONMENT_VARIABLE = "DJANGO_SETTINGS_MODULE" -class LazySettings(object): +class LazySettings(LazyObject): """ A lazy proxy for either global Django settings or a custom settings object. The user can manually configure settings prior to using them. Otherwise, Django uses the settings module pointed to by DJANGO_SETTINGS_MODULE. """ - def __init__(self): - # _target must be either None or something that supports attribute - # access (getattr, hasattr, etc). - self._target = None - - def __getattr__(self, name): - if self._target is None: - self._import_settings() - if name == '__members__': - # Used to implement dir(obj), for example. - return self._target.get_all_members() - return getattr(self._target, name) - - def __setattr__(self, name, value): - if name == '_target': - # Assign directly to self.__dict__, because otherwise we'd call - # __setattr__(), which would be an infinite loop. - self.__dict__['_target'] = value - else: - if self._target is None: - self._import_settings() - setattr(self._target, name, value) - - def _import_settings(self): + def _setup(self): """ Load the settings module pointed to by the environment variable. This is used the first time we need any settings at all, if the user has not @@ -56,7 +35,7 @@ class LazySettings(object): # problems with Python's interactive help. raise ImportError("Settings cannot be imported, because environment variable %s is undefined." % ENVIRONMENT_VARIABLE) - self._target = Settings(settings_module) + self._wrapped = Settings(settings_module) def configure(self, default_settings=global_settings, **options): """ @@ -69,13 +48,13 @@ class LazySettings(object): holder = UserSettingsHolder(default_settings) for name, value in options.items(): setattr(holder, name, value) - self._target = holder + self._wrapped = holder def configured(self): """ Returns True if the settings have already been configured. """ - return bool(self._target) + return bool(self._wrapped) configured = property(configured) class Settings(object): diff --git a/django/utils/functional.py b/django/utils/functional.py index 9a12eda5a10..23614d17124 100644 --- a/django/utils/functional.py +++ b/django/utils/functional.py @@ -251,3 +251,39 @@ def allow_lazy(func, *resultclasses): return func(*args, **kwargs) return lazy(func, *resultclasses)(*args, **kwargs) return wraps(func)(wrapper) + +class LazyObject(object): + """ + A wrapper for another class that can be used to delay instantiation of the + wrapped class. + + This is useful, for example, if the wrapped class needs to use Django + settings at creation time: we want to permit it to be imported without + accessing settings. + """ + def __init__(self): + self._wrapped = None + + def __getattr__(self, name): + if self._wrapped is None: + self._setup() + if name == "__members__": + # Used to implement dir(obj) + return self._wrapped.get_all_members() + return getattr(self._wrapped, name) + + def __setattr__(self, name, value): + if name == "_wrapped": + # Assign to __dict__ to avoid infinite __setattr__ loops. + self.__dict__["_wrapped"] = value + else: + if self._wrapped is None: + self._setup() + setattr(self._wrapped, name, value) + + def _setup(self): + """ + Must be implemented by subclasses to initialise the wrapped object. + """ + raise NotImplementedError + diff --git a/tests/regressiontests/comment_tests/tests/app_api_tests.py b/tests/regressiontests/comment_tests/tests/app_api_tests.py index 6a9eb1c6379..c4d9ebfef33 100644 --- a/tests/regressiontests/comment_tests/tests/app_api_tests.py +++ b/tests/regressiontests/comment_tests/tests/app_api_tests.py @@ -41,7 +41,7 @@ class CustomCommentTest(CommentTestCase): del settings.INSTALLED_APPS[-1] settings.COMMENTS_APP = self.old_comments_app if settings.COMMENTS_APP is None: - delattr(settings._target, 'COMMENTS_APP') + delattr(settings._wrapped, 'COMMENTS_APP') def testGetCommentApp(self): from regressiontests.comment_tests import custom_comments