From c643e12faf85478149386768d278c44936aa99a3 Mon Sep 17 00:00:00 2001 From: Adrian Holovaty Date: Tue, 16 May 2006 21:28:06 +0000 Subject: [PATCH] Fixed #1321 -- Made DJANGO_SETTINGS_MODULE optional. You can now call django.conf.settings.configure() to set settings manually if you don't have a settings module. Thanks, Malcolm Tredinnick, Luke Plant, Fredrik Lundh git-svn-id: http://code.djangoproject.com/svn/django/trunk@2927 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/conf/__init__.py | 91 ++++++++++++++++++++++++++----- django/template/defaultfilters.py | 10 +++- django/utils/translation.py | 11 ++-- docs/i18n.txt | 11 ++++ docs/settings.txt | 67 +++++++++++++++++++++++ docs/templates_python.txt | 38 +++++++++++++ tests/othertests/templates.py | 1 + tests/runtests.py | 4 ++ 8 files changed, 213 insertions(+), 20 deletions(-) diff --git a/django/conf/__init__.py b/django/conf/__init__.py index 291ba8ce3f..a9de8e876b 100644 --- a/django/conf/__init__.py +++ b/django/conf/__init__.py @@ -12,10 +12,61 @@ from django.conf import global_settings ENVIRONMENT_VARIABLE = "DJANGO_SETTINGS_MODULE" +class LazySettings: + """ + 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': + self.__dict__['_target'] = value + else: + setattr(self._target, name, value) + + def _import_settings(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 + previously configured the settings manually. + """ + try: + settings_module = os.environ[ENVIRONMENT_VARIABLE] + if not settings_module: # If it's set but is an empty string. + raise KeyError + except KeyError: + raise EnvironmentError, "Environment variable %s is undefined." % ENVIRONMENT_VARIABLE + + self._target = Settings(settings_module) + + def configure(self, default_settings=global_settings, **options): + """ + Called to manually configure the settings. The 'default_settings' + parameter sets where to retrieve any unspecified values from (its + argument must support attribute access (__getattr__)). + """ + if self._target != None: + raise EnvironmentError, 'Settings already configured.' + holder = UserSettingsHolder(default_settings) + for name, value in options.items(): + setattr(holder, name, value) + self._target = holder + class Settings: - 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(): @@ -27,7 +78,7 @@ class Settings: try: mod = __import__(self.SETTINGS_MODULE, '', '', ['']) except ImportError, e: - raise EnvironmentError, "Could not import settings '%s' (is it on sys.path?): %s" % (self.SETTINGS_MODULE, e) + raise EnvironmentError, "Could not import settings '%s' (Is it on sys.path? Does it have syntax errors?): %s" % (self.SETTINGS_MODULE, e) # Settings that should be converted into tuples if they're mistakenly entered # as strings. @@ -56,18 +107,32 @@ class Settings: # move the time zone info into os.environ os.environ['TZ'] = self.TIME_ZONE -# try to load DJANGO_SETTINGS_MODULE -try: - settings_module = os.environ[ENVIRONMENT_VARIABLE] - if not settings_module: # If it's set but is an empty string. - raise KeyError -except KeyError: - raise EnvironmentError, "Environment variable %s is undefined." % ENVIRONMENT_VARIABLE + def get_all_members(self): + return dir(self) -# instantiate the configuration object -settings = Settings(settings_module) +class UserSettingsHolder: + """ + Holder for user configured settings. + """ + # SETTINGS_MODULE does not really make 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 get_all_members(self): + return dir(self) + dir(self.default_settings) + +settings = LazySettings() # install the translation machinery so that it is available from django.utils import translation translation.install() - diff --git a/django/template/defaultfilters.py b/django/template/defaultfilters.py index 7820e72342..03069121ee 100644 --- a/django/template/defaultfilters.py +++ b/django/template/defaultfilters.py @@ -327,14 +327,18 @@ def get_digit(value, arg): # DATES # ################### -def date(value, arg=settings.DATE_FORMAT): +def date(value, arg=None): "Formats a date according to the given format" from django.utils.dateformat import format + if arg is None: + arg = settings.DATE_FORMAT return format(value, arg) -def time(value, arg=settings.TIME_FORMAT): +def time(value, arg=None): "Formats a time according to the given format" from django.utils.dateformat import time_format + if arg is None: + arg = settings.TIME_FORMAT return time_format(value, arg) def timesince(value): @@ -435,7 +439,7 @@ def pprint(value): return pformat(value) except Exception, e: return "Error in formatting:%s" % e - + # Syntax: register.filter(name of filter, callback) register.filter(add) register.filter(addslashes) diff --git a/django/utils/translation.py b/django/utils/translation.py index 0ee09e5b94..81cd8e2992 100644 --- a/django/utils/translation.py +++ b/django/utils/translation.py @@ -117,9 +117,12 @@ def translation(language): globalpath = os.path.join(os.path.dirname(sys.modules[settings.__module__].__file__), 'locale') - parts = settings.SETTINGS_MODULE.split('.') - project = __import__(parts[0], {}, {}, []) - projectpath = os.path.join(os.path.dirname(project.__file__), 'locale') + if settings.SETTINGS_MODULE is not None: + parts = settings.SETTINGS_MODULE.split('.') + project = __import__(parts[0], {}, {}, []) + projectpath = os.path.join(os.path.dirname(project.__file__), 'locale') + else: + projectpath = None def _fetch(lang, fallback=None): @@ -155,7 +158,7 @@ def translation(language): if os.path.isdir(localepath): res = _merge(localepath) - if os.path.isdir(projectpath): + if projectpath and os.path.isdir(projectpath): res = _merge(projectpath) for appname in settings.INSTALLED_APPS: diff --git a/docs/i18n.txt b/docs/i18n.txt index 9199a74295..e6660f939d 100644 --- a/docs/i18n.txt +++ b/docs/i18n.txt @@ -540,6 +540,17 @@ you can override base translations in your project path. Or, you can just build a big project out of several apps and put all translations into one big project message file. The choice is yours. +.. note:: + + If you're using manually configured settings, as described in the + `settings documentation`_, the ``locale`` directory in the project + directory will not be examined, since Django loses the ability to work out + the location of the project directory. (Django normally uses the location + of the settings file to determine this, and a settings file doesn't exist + if you're manually configuring your settings.) + +.. _settings documentation: http://www.djangoproject.com/documentation/settings/#using-settings-without-the-django-settings-module-environment-variable + All message file repositories are structured the same way. They are: * ``$APPPATH/locale//LC_MESSAGES/django.(po|mo)`` diff --git a/docs/settings.txt b/docs/settings.txt index d4666468fc..f027d5596b 100644 --- a/docs/settings.txt +++ b/docs/settings.txt @@ -724,3 +724,70 @@ Django apps. Just follow these conventions: * For settings that are sequences, use tuples instead of lists. This is purely for performance. * Don't reinvent an already-existing setting. + +Using settings without setting DJANGO_SETTINGS_MODULE +===================================================== + +In some cases, you might want to bypass the ``DJANGO_SETTINGS_MODULE`` +environment variable. For example, if you're using the template system by +itself, you likely don't want to have to set up an environment variable +pointing to a settings module. + +In these cases, you can configure Django's settings manually. Do this by +calling ``django.conf.settings.configure()``. + +Example:: + + from django.conf import settings + + settings.configure(DEBUG=True, TEMPLATE_DEBUG=True, + TEMPLATE_DIRS=('/home/web-apps/myapp', '/home/web-apps/base')) + +Pass ``configure()`` as many keyword arguments as you'd like, with each keyword +argument representing a setting and its value. Each argument name should be all +uppercase, with the same name as the settings described above. If a particular +setting is not passed to ``configure()`` and is needed at some later point, +Django will use the default setting value. + +Custom default settings +----------------------- + +If you'd like default values to come from somewhere other than +``django.conf.global_settings``, you can pass in a module or class that +provides the default settings as the ``default_settings`` argument (or as the +first positional argument) in the call to ``configure()``. + +In this example, default settings are taken from ``myapp_defaults``, and the +``DEBUG`` setting is set to ``True``, regardless of its value in +``myapp_defaults``:: + + from django.conf import settings + from myapp import myapp_defaults + + settings.configure(default_settings=myapp_defaults, DEBUG=True) + +The following example, which uses ``myapp_defaults`` as a positional argument, +is equivalent:: + + settings.configure(myapp_defaults, DEBUG = True) + +Either configure() or DJANGO_SETTINGS_MODULE is required +-------------------------------------------------------- + +If you're not setting the ``DJANGO_SETTINGS_MODULE`` environment variable, you +*must* call ``configure()`` at some point before using any code that reads +settings. + +If you don't set ``DJANGO_SETTINGS_MODULE`` and don't call ``configure()``, +Django will raise an ``EnvironmentError`` exception the first time a setting +is accessed. + +If you set ``DJANGO_SETTINGS_MODULE``, access settings values somehow, *then* +call ``configure()``, Django will raise an ``EnvironmentError`` saying settings +have already been configured. + +Also, it's an error to call ``configure()`` more than once, or to call +``configure()`` after any setting has been accessed. + +It boils down to this: Use exactly one of either ``configure()`` or +``DJANGO_SETTINGS_MODULE``. Not both, and not neither. diff --git a/docs/templates_python.txt b/docs/templates_python.txt index 21ae595624..5d27618525 100644 --- a/docs/templates_python.txt +++ b/docs/templates_python.txt @@ -7,6 +7,10 @@ perspective -- how it works and how to extend it. If you're just looking for reference on the language syntax, see `The Django template language: For template authors`_. +If you're looking to use the Django template system as part of another +application -- i.e., without the rest of the framework -- make sure to read +the `configuration`_ section later in this document. + .. _`The Django template language: For template authors`: http://www.djangoproject.com/documentation/templates/ Basics @@ -876,3 +880,37 @@ The only new concept here is the ``self.nodelist.render(context)`` in For more examples of complex rendering, see the source code for ``{% if %}``, ``{% for %}``, ``{% ifequal %}`` and ``{% ifchanged %}``. They live in ``django/template/defaulttags.py``. + +.. _configuration: + +Configuring the template system in standalone mode +================================================== + +.. note:: + + This section is only of interest to people trying to use the template + system as an output component in another application. If you are using the + template system as part of a Django application, nothing here applies to + you. + +Normally, Django will load all the configuration information it needs from its +own default configuration file, combined with the settings in the module given +in the ``DJANGO_SETTINGS_MODULE`` environment variable. But if you're using the +template system independently of the rest of Django, the environment variable +approach isn't very convenient, because you probably want to configure the +template system in line with the rest of your application rather than dealing +with settings files and pointing to them via environment variables. + +To solve this problem, you need to use the manual configuration option +described in the `settings file`_ documentation. Simply import the appropriate +pieces of the templating system and then, *before* you call any of the +templating functions, call ``django.conf.settings.configure()`` with any +settings you wish to specify. You might want to consider setting at least +``TEMPLATE_DIRS`` (if you are going to use template loaders), +``DEFAULT_CHARSET`` (although the default of ``utf-8`` is probably fine) and +``TEMPLATE_DEBUG``. All available settings are described in the +`settings documentation`_, and any setting starting with *TEMPLATE_* +is of obvious interest. + +.. _settings file: http://www.djangoproject.com/documentation/settings/#using-settings-without-the-django-settings-module-environment-variable +.. _settings documentation: http://www.djangoproject.com/documentation/settings/ diff --git a/tests/othertests/templates.py b/tests/othertests/templates.py index 6d8487c67a..ed7105bb71 100644 --- a/tests/othertests/templates.py +++ b/tests/othertests/templates.py @@ -507,4 +507,5 @@ def run_tests(verbosity=0, standalone=False): raise Exception, msg if __name__ == "__main__": + settings.configure() run_tests(1, True) diff --git a/tests/runtests.py b/tests/runtests.py index 3f8eda6ab4..a1b595fba6 100755 --- a/tests/runtests.py +++ b/tests/runtests.py @@ -73,6 +73,10 @@ class TestRunner: def run_tests(self): from django.conf import settings + # An empty access of the settings to force the default options to be + # installed prior to assigning to them. + settings.INSTALLED_APPS + # Manually set INSTALLED_APPS to point to the test models. settings.INSTALLED_APPS = [MODEL_TESTS_DIR_NAME + '.' + a for a in get_test_models()]