38

The Django documentation mentions that you can add your own settings to django.conf.settings. So if my project's settings.py defines

APPLES = 1

I can access that with settings.APPLES in my apps in that project.

But if my settings.py doesn't define that value, accessing settings.APPLES obviously won't work. Is there some way to define a default value for APPLES that is used if there is no explicit setting in settings.py?

I'd like best to define the default value in the module/package that requires the setting.

sth
  • 222,467
  • 53
  • 283
  • 367

5 Answers5

34

In my apps, I have a seperate settings.py file. In that file I have a get() function that does a look up in the projects settings.py file and if not found returns the default value.

from django.conf import settings

def get(key, default):
    return getattr(settings, key, default)


APPLES = get('APPLES', 1)

Then where I need to access APPLES I have:

from myapp import settings as myapp_settings

myapp_settings.APPLES

This allows an override in the projects settings.py, getattr will check there first and return the value if the attribute is found or use the default defined in your apps settings file.

mg.
  • 7,822
  • 1
  • 26
  • 30
Mike Ramirez
  • 10,750
  • 3
  • 26
  • 20
  • 3
    Isn't this fragile? Doesn't depend on project `settings.py` being imported before `app_settings.py`? – Flimm May 04 '16 at 14:01
  • @Flimm yes. If you try to import `myapp_settings` directly, it will initialize Django's [LazySettings](https://github.com/django/django/blob/ced0bdd923f22ee6750ec90c9582d452dbc56203/django/conf/__init__.py#L33) at the first `getattr`, and give a configuration error (because [this line](https://github.com/django/django/blob/ced0bdd923f22ee6750ec90c9582d452dbc56203/django/conf/__init__.py#L145) will return the partially-evaluated `myapp_settings` module *while we are evaluating it*, rather than enter an endless import loop). I haven't found a clean workaround. – scry Nov 20 '18 at 20:20
20

How about just:

getattr(app_settings, 'SOME_SETTING', 'default value')
Charlie
  • 8,530
  • 2
  • 55
  • 53
  • 2
    This is much simpler than some of the other suggestions here, but if you intend on using a setting/variable more than once you're breaking the DRY concept, in which case the other solutions might work. Food for thought :) – Daniel Apr 11 '15 at 06:33
  • 1
    How does it break DRY? – Charlie Apr 13 '15 at 02:20
  • 1
    If you intend on using SOME_SETTING more than once in your application then you'll have to place this `getattr(app_settings, 'SOME_SETTING', default_value)` everywhere you use it – Daniel Apr 13 '15 at 02:23
  • Just anywhere you want a default. – Charlie Apr 13 '15 at 02:35
  • Where does `app_settings` come from? is that supposed to be `django.conf.settings`? – run_the_race Nov 10 '21 at 14:20
  • @run_the_race `from myapp import settings as app_settings` – Charlie Nov 10 '21 at 16:24
  • @Charlie, where is `myapp` coming from? The OP is talking about django settings (not a certain apps settings). Do you not mean `from django.cong import settings as app_settings` (to work with your answer). – run_the_race Nov 10 '21 at 17:52
8

Here are two solutions. For both you can set settings.py files in your applications and fill them with default values.

Configure default value for a single application

Use from MYAPP import settings instead of from django.conf import settings in your code.

Edit YOURAPP/__init__.py:

from django.conf import settings as user_settings
from . import settings as default_settings

class AppSettings:
    def __getattr__(self, name):
        # If the setting you want is filled by the user, let's use it.
        if hasattr(user_settings, name):
            return getattr(user_settings, name)

        # If the setting you want has a default value, let's use it.
        if hasattr(default_settings, name):
            return getattr(default_settings, name)

        raise AttributeError("'Settings' object has no attribute '%s'" % name)

settings = AppSettings()

Configure default values for a whole project

Use from MYPROJECT import settings instead of from django.conf import settings in your code.

Edit MYPROJECT/MYPROJECT/__init__.py

import os, sys, importlib
from . import settings as user_settings

def get_local_apps():
    """Returns the locally installed apps names"""
    apps = []
    for app in user_settings.INSTALLED_APPS:
        path = os.path.join(user_settings.BASE_DIR, app)
        if os.path.exists(path) and app != __name__:
            apps.append(sys.modules[app])
    return apps

class AppSettings:
    SETTINGS_MODULE = 'settings'

    def __getattr__(self, setting_name):

        # If the setting you want is filled by the user, let's use it.
        if hasattr(user_settings, setting_name):
            return getattr(user_settings, setting_name)

        # Let's check every local app loaded by django.
        for app in get_local_apps():
            module_source = os.path.join(app.__path__[0], "%s.py" % self.SETTINGS_MODULE)
            module_binary = os.path.join(app.__path__[0], "%s.pyc" % self.SETTINGS_MODULE)
            if os.path.exists(module_source) or os.path.exists(module_binary):
                module = importlib.import_module("%s.%s" % (app.__name__, self.SETTINGS_MODULE))

                # Let's take the first default value for this setting we can find in any app
                if hasattr(module, setting_name):
                    return getattr(module, setting_name)

        raise AttributeError("'Settings' object has no attribute '%s'" % setting_name)

settings = AppSettings()

This solution may seem more easier to install, but it does not guarantee that the good default value will be returned. If several applications declare the same variable in their settings.py, you can not be sure which one will return the default value you asked.

azmeuk
  • 4,026
  • 3
  • 37
  • 64
2

Starting from Mike's answer, I now wrapped the default setting handling into a class with easy to use interface.

Helper module:

from django.conf import settings

class SettingsView(object):
   class Defaults(object):
      pass

   def __init__(self):
      self.defaults = SettingsView.Defaults()

   def __getattr__(self, name):
      return getattr(settings, name, getattr(self.defaults, name))

Usage:

from localconf import SettingsView

settings = SettingsView()
settings.defaults.APPLES = 1

print settings.APPLES

This prints the value from django.conf.settings, or the default if it isn't set there. This settings object can also be used to access all the standard setting values.

sth
  • 222,467
  • 53
  • 283
  • 367
  • I think app specific settings should be explicitly named and different from the main settings, just for easier reading. – Mike Ramirez Apr 09 '11 at 00:27
0

I recently had the same problem and created a Django app that is designed to be used for exactly such a case. It allows you to define default values for certain settings. It then first checks whether the setting is set in the global settings file. If not, it will return the default value.

I've extended it to also allow for some type checking or pre handling of the default value (e.g. a dotted class path can be converted to the class itself on load)

The app can be found at: https://pypi.python.org/pypi?name=django-pluggableappsettings&version=0.2.0&:action=display

Tim
  • 1,272
  • 11
  • 28