78

I'm trying to use local_setting in Django 1.2, but it's not working for me. At the moment I'm just adding local_settings.py to my project.

settings.py

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
        'NAME': 'banco1',                      # Or path to database file if using sqlite3.
        'USER': 'root',                      # Not used with sqlite3.
        'PASSWORD': '123',                  # Not used with sqlite3.
        'HOST': 'localhost',                      # Set to empty string for localhost. Not used with sqlite3.
        'PORT': '',                      # Set to empty string for default. Not used with sqlite3.
    }
}

local_settings.py

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
        'NAME': 'banco2',                      # Or path to database file if using sqlite3.
        'USER': 'root',                      # Not used with sqlite3.
        'PASSWORD': '123',                  # Not used with sqlite3.
        'HOST': 'localhost',                      # Set to empty string for localhost. Not used with sqlite3.
        'PORT': '',                      # Set to empty string for default. Not used with sqlite3.
    }
}

The problem is that local_settings.py doesn't override settings.py. What is wrong?

Khaled.K
  • 5,828
  • 1
  • 33
  • 51
Michel Andrade
  • 4,056
  • 5
  • 27
  • 28
  • 2
    At the moment I read this question, there are three very different and all interesting answers. I was puzzled by the comments on Daniel's solution. It looks to me like there is no one-fits-all solution. Daniel's solution is simple and efficient. jano's solution is somehow cleaner in a way that makes sense only in already clean environments, at a small cost. John's solution is somehow more heavyweight but interesting in the most constrained environments. All in all, just pick the simplest that fit your use case. – Stéphane Gourichon Jan 15 '15 at 09:47

8 Answers8

149

You can't just add local_settings.py, you have to explicity import it.

At the very end of your settings.py, add this:

try:
    from local_settings import *
except ImportError:
    pass

The try/except block is there so that Python just ignores the case when you haven't actually defined a local_settings file.

Daniel Roseman
  • 588,541
  • 66
  • 880
  • 895
  • Daniel, Django in the version 1.2 doesn't do that automatically? – Michel Andrade Feb 05 '11 at 22:02
  • @Michel: no, I don't think so. Where did you read that it did? – Daniel Roseman Feb 05 '11 at 22:07
  • 3
    Yeah `grep -r "local_setting" django` pretty much guarantees it's not "out of the box" behavior – Yuji 'Tomita' Tomita Feb 05 '11 at 22:24
  • 10
    This approach means you have unversioned code running in EVERY location.It's an anti-pattern. – pydanny Jan 31 '13 at 16:23
  • 2
    @pydanny what alternative do you suggest? – brodney Mar 18 '13 at 15:15
  • 1
    @pydanny - that answer explicitly says `local_settings.py (or more commonly prod_settings.py) is NOT in version control, and used in production by specifying --settings=prod_settings or similar.` So it still seems like unversioned code is running in every location. Is that not the case? – Joseph Dec 14 '15 at 22:46
  • 2
    It's a very bad idea to pass on the exception. If local_settings.py simply has a syntax error, it won't be imported and you may run with default settings from settings.py which you don't expect. For example, if settings.py connects to a productions database. It's better to always require a local_settings.py and throw an error if it's not found. – Matt Apr 13 '16 at 02:38
  • another down side of importing local settings as suggested here is that some system files such as django/middleware/common.py reference to the value of the variables directly like on line 48. must_prepend = settings.PREPEND_WWW and host and not host.startswith('www.') – Alex Alves Jun 25 '20 at 22:50
86

This is the best practice I think:

  • local_settings imports from settings
  • local_settings overrides settings specific to the local environment, especially DATABASES, SECRET_KEY, ALLOWED_HOSTS and DEBUG variables
  • pass to django management commands the flag --settings=local_settings

You could implement local_settings like this:

from settings import *

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
        'NAME': 'banco2',                      # Or path to database file if using sqlite3.
        'USER': 'root',                      # Not used with sqlite3.
        'PASSWORD': '123',                  # Not used with sqlite3.
        'HOST': 'localhost',                      # Set to empty string for localhost. Not used with sqlite3.
        'PORT': '',                      # Set to empty string for default. Not used with sqlite3.
    }
}

A few additional key points:

  • settings.py is in version control, written in a way such that it's ready to use by contributors
  • local_settings.py (or more commonly prod_settings.py) is NOT in version control, and used in production by specifying --settings=prod_settings or similar.

Touching the stock settings file as little as possible also makes it easier to upgrade your django version. When you upgrade Django to the next version, look at the diff in the stock settings.py and yours, and take actions as necessary depending on what changed. Changes in the default values can be important, and the less you touched the original settings.py file, the easier it will be to discern upstream changes.

janos
  • 120,954
  • 29
  • 226
  • 236
  • 2
    I like this approach. Touching the empty settings file as little as possible makes it easier to properly configure new settings when you upgrade your django version. – m000 Apr 02 '13 at 09:59
  • 3
    This should be the accepted answer, read the comments in the accepted one for reasons why that one is problematic ("unversioned code running in EVERY location"). – eggonlegs Feb 18 '14 at 06:07
  • 6
    I cannot see how this is different from the accepted answer. This local_settings should not be versioned, either. If it would be, it is possible for developers to accidentally change local_settings on the production machine. – GergelyPolonkai Jun 18 '14 at 08:50
  • The only thing that bothers me is the `import *`. http://stackoverflow.com/questions/2386714/why-is-import-bad – azmeuk Aug 31 '15 at 08:31
  • 1
    @azmeuk The context is important. `import *` is bad in general, but perfectly acceptable when it's the only import in a file. And it *should* be the import in `local_settings.py` – janos Aug 31 '15 at 08:41
  • Yep, I agree with @GergelyPolonkai. Why is it wrong to have unversioned code running? I mean, I am NOT going to add the production DB credentials to the repository. Also this involves passing around flags to choose the correct settings file, which is a mess. – José Tomás Tocino Sep 17 '15 at 11:43
  • I never said that the local settings should be in version control, and it really shouldn't. I edited to clarify that. I also added a note about passing flags: you only do that in production, in scripts which you setup one time. For easy development and contributions, it's best if the project is in a runnable state always without any flags. – janos Sep 17 '15 at 11:54
  • Note that django has a useful diffsettings command that will produce a diff between the original django settings and your current settings. See https://docs.djangoproject.com/en/3.1/topics/settings/#seeing-which-settings-you-ve-changed. – sacha Dec 31 '20 at 23:32
11

Since the topic resurfaces routinely let me summarise why you might want to consider this approach:

  • a dumb settings file is very fast and easy to change; especially in a production environment. No python required: any idiot can jump in and change the database password in a file which just lists names and values; especially compared to a complex python settings file full of mysterious dangerous BIGCAPS names.

  • the application settings should be completely separated from the application code. You can put a config.ini outside the repository root and never again worry about a repo pull clobbering your settings, or your personal settings polluting the repo, or that clever code in your settings.py not making it into the repo to everyone else's advantage.

This won't apply to small projects, but on bigger projects I've concluded that the local_settings strategy just doesn't cut it; over time enough application programming creeps in that it gets hard to handle; primarily as settings become derivative and/or codependent. There can be good justifications for settings to react according to the local settings which forces the import of a local_settings file to creep up toward the middle of settings.py. I find things start to get messy as that happens.

My current solution is to use a config file, I dub it "local.ini". It holds only those values which do actually change between deployed instances. There is no code: they are just values and booleans:

[global]
domain = 127.0.0.1:8000
database_host = 127.0.0.1
database_name = test_database
debug = Yes
google_analytics_id = UA-DEV-1
payments = testing
use_cdn = No

With this in place I can treat the settings.py like any other piece of application code: tweak it, check it in, and deploy it without having to worry about testing against whatever code might be lurking in a local_settings python code. My settings.py is free of race conditions that come up when later settings depend on local settings, and I can switch features on and off writing easy-to-follow linear code. No more hurriedly tweaking the local_settings file when I've forgotten to add some new value, and no more daves_local_settings.py and bobs_local_settings.py files creeping into the repository.

from ConfigParser import RawConfigParser
parser = RawConfigParser()

APPLICATION_ROOT = path.abspath(path.dirname(__file__))
parser.readfp(open(path.join(APPLICATION_ROOT, 'local.ini')))

# simple variables
DATABASE_HOST = parser.get('global', 'database_host')
DATABASE_NAME = parser.get('global', 'database_name')

# interdependencies
from version import get_cdn_version
CDN = 'd99phdomw5k72k.cloudfront.net'
if parser.getboolean('global', 'use_cdn'):
    STATIC_URL = '/{}/static/{}/'.format(CDN, get_cdn_version())
else:
    STATIC_URL = '/static/'


# switches
payments = parser.get('global', 'payments')
if payments == 'testing':
    PAYMENT_GATEWAY_ENDPOINT = 'https://api.sandbox.gateway.com'
else:
    PAYMENT_GATEWAY_ENDPOINT = 'https://api.live.gateway.com'

If you encounter a BOFH, like I had on one occasion, he got particularly excited about the ability to stick the local.ini into the /etc directory as /etc/ourapp.ini and so keep the application directory itself a pure repository export. Sure you could do that with a local_settings.py but the last thing he wanted to do was mess with python code. A simple config file he could handle.

John Mee
  • 50,179
  • 34
  • 152
  • 186
  • 1
    What makes you want to use a different syntax (and require a parser) as opposed to a regular Python file? This is why we namespace. – Joost Apr 29 '15 at 09:02
  • Because a `regular Python file` will be executed, whereas a config file is a very simple collection of dumb settings; there is no temptation to get it doing "smart" things, and any idiot (eg non-python people, or yourself 12 months later) can work out how to update the basic settings without any fear of breaking everything. – John Mee Apr 29 '15 at 11:22
  • The syntax is practically identical though. I guess it's a matter of preference. – Joost Apr 29 '15 at 11:31
  • 1
    Like I said, there's little payoff on small projects; but once your settings.py grows beyond 50 lines, or the `import local_settings.py` is no longer at the very bottom, you'll see more value. – John Mee Apr 29 '15 at 12:09
  • For me, the worst part about this is you need to write code for every single settings value. And if you just import everything in the file you'll need to cast the variables from stings if they are something else. Also, I like concatenating things. I have lots of lists and dictionaries in my config as well. – kagronick Dec 02 '15 at 03:46
9

I kept a copy of __local_settings.py:

  • local_settings.py is being ignored in the version control, but not __local_settings.py
  • update README.md to inform the team on how to setup: cp {__,}local_settings.py (which make a copy for their local_settings)

In the past

I used to import those settings.

# settings.py
DATABASE = {...}

try:
    from .local_settings import *
except ImportError:
    pass

now

I just import the settings itself from the local_settings.py.

And with the following command: python manage.py runserver --settings=<proj>.local_settings.

# local_settings.py & __local_settings.py
from .settings import *

DATABASE = {...}

And since, I usually don't interact with manage.py directly, because some parameters are explicitly necessary for me, (e.g. address:port). Therefore, I put all of those command into my Makefile.

For example, here's my Makefile:

run:
    python manage.py runserver 0.0.0.0:8000 --settings=<proj>.local_settings

sh:
    python manage.py shell_plus --settings=<proj>.local_settings

dep:
    npm install
    pip install -r requirements.txt

Thus:

make dep
make sh 
make run

Conclusion

Provided that you are not using Makefile as your workflow, then you may use the earlier method, but if you are using makefile, then i believe it is better to be more explicit in your Makefile.

Yeo
  • 11,416
  • 6
  • 63
  • 90
  • Makefile is just an idea & opinion. Try to use the earlier approach may be better in general – Yeo Mar 14 '16 at 13:31
7

Before running the server do

export DJANGO_SETTINGS_MODULE=your_app_name.local_settings where your_app_name should be replaced by your App's name. And don't forget to do

from settings import *

in your local_settings.py file

Siddhesh Suthar
  • 425
  • 7
  • 9
  • 2
    You should omit the .py from the line, (and don't forget to replace appname with your actual application) – johanvdw Apr 20 '18 at 07:12
5

Yet another approach is to use python-dotenv and environment variables to customize settings for different environments.

Create the .env file along-side your settings.py:

# .env
SECRET_KEY=your-secret-key
DATABASE_PASSWORD=your-database-password

Add the following code to your settings.py:

# settings.py
from dotenv import load_dotenv
load_dotenv()

# OR, explicitly providing path to '.env'
from pathlib import Path  # python 3.4+
env_path = Path('.') / '.env'
load_dotenv(dotenv_path=env_path)

At this point, parsed keys/values from the .env file are present as environment variables and they can be conveniently accessed via os.getenv():

# settings.py
import os
SECRET_KEY = os.getenv('SECRET_KEY')
DATABASE_PASSWORD = os.getenv('DATABASE_PASSWORD')   
Eugene Yarmash
  • 142,882
  • 41
  • 325
  • 378
2

I found the similar solution. This is my configuration for this case:

settings.py:

DEBUG = False

try:
    from local_settings import *

except ImportError:
    pass

if DEBUG is False:
    ALLOWED_HOSTS = ['sth.com']
    DATABASES = {
        ....
    }

local_settings.py:

from settings import *
ALLOWED_HOSTS = ['*']
DEBUG = True
DATABASES = {
    ...
}
Darex1991
  • 855
  • 1
  • 10
  • 24
  • I tried multiple solutions, and this works for me. By importing the local_settings on top of the page rather than at the bottom as recommended in other posts. – Rene Chan Aug 06 '21 at 22:35
2

Add this to the end of the file settings.py

try:
    from .local_settings import *
except ImportError:
    pass

And create file local_settings.py with your new settings for example

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
        'NAME': 'banco2',                      # Or path to database file if using sqlite3.
        'USER': 'root',                      # Not used with sqlite3.
        'PASSWORD': '123',                  # Not used with sqlite3.
        'HOST': 'localhost',                      # Set to empty string for localhost. Not used with sqlite3.
        'PORT': '',                      # Set to empty string for default. Not used with sqlite3.
    }
}
Holovin
  • 31
  • 7