I have a Django project that I\'d like to distribute on a public repository like bitbucket or github. I\'d like it to be as easy to install as possible, so I\'m including t
I'd go about it this way:
Have the secret key in a separate file "secret_key.py". This file does not exist for a pristine installation. In your settings.py include something like:
try:
from .secret_key import SECRET_KEY
except ImportError:
SETTINGS_DIR = os.path.abspath(os.path.dirname(__file__))
generate_secret_key(os.path.join(SETTINGS_DIR, 'secret_key.py'))
from .secret_key import SECRET_KEY
The function generate_secret_key(filename)
that you will write generates a file called filename
(which, as we call it, will be secret_key.py
in the same dir as settings.py
) with the contents:
SECRET_KEY = '....random string....'
Where random string is the generated key based on a random number.
For key generation you can use Umang's suggestion https://stackoverflow.com/a/16630719/166761.
I would solve the problem like this:
I_AM_A_DUMMY_KEY_CHANGE_ME
./manage.py gen_secret_key
Generally speaking, you can divide Django configuration into things that are app-specific and things that are server-specific. This falls into the latter category.
There are a number of ways you can tackle the problem of server-specific configuration, it is discussed more in this question.
For this particular instance, using the approach I outline in my answer to the other question, I'd put a placeholder in settings_local.py.sample
for distribution, and during installation, I'd copy that over to settings_local.py
and edit to suit.
In this solution I use django-dotenv, which is one of the dependencies of my project, as listed in requirements.txt
like django-dotenv==1.4.1
. The advantage of this approach is you have a different .env
file for each environment where the application is installed.
Create the file utils.py
in the same directory of settings.py
with the following content:
from django.utils.crypto import get_random_string
def generate_secret_key(env_file_name):
env_file = open(env_file_name, "w+")
chars = 'abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)'
generated_secret_key = get_random_string(50, chars)
env_file.write("SECRET_KEY = '{}'\n".format(generated_secret_key))
env_file.close()
Then modify the settings.py
file as follows:
import dotenv
from [project-folder-name] import utils
...
try:
SECRET_KEY = os.environ['SECRET_KEY']
except KeyError:
path_env = os.path.join(BASE_DIR, '.env')
utils.generate_secret_key(path_env)
dotenv.read_dotenv(path_env)
SECRET_KEY = os.environ['SECRET_KEY']
For those who don't use django-dotenv
, all you have to do it to add it as a dependency and change the manage.py
to load it at startup:
import dotenv
if __name__ == "__main__":
dotenv.read_dotenv()
Carles Barrobés made an excellent answer but it is incomplete, here is my version for python 3 with the missing function to work.
from django.core.management.utils import get_random_secret_key
def generate_secret_key (filepath):
secret_file = open(filepath, "w")
secret = "SECRET_KEY= " + "\""+ get_random_secret_key() + "\"" + "\n"
secret_file.write(secret)
secret_file.close()
try:
from .secret_key import SECRET_KEY
except ModuleNotFoundError:
SETTINGS_DIR = os.path.abspath(os.path.dirname(__file__))
generate_secret_key(os.path.join(SETTINGS_DIR, 'secret_key.py'))
from .secret_key import SECRET_KEY
Take notice that I changed the ImportError
for ModuleNotFoundError
and creates the python file secret_key.py
to gather the SECRET_KEY like a variable instead to parse a txt file.
In my code I have three levels of settings file inspired by Two Scoops of Django, so a middle one goes like this where BASE_PRIVATE_DIR is set up in the base template. In my case this is from the django directory ../../mysite_private but somewhere ouside the normal files under the application git.:
from .base import *
ALLOWED_HOSTS = ['staging.django.site']
#Allow local override which is per deployment instance. There should probably then be
# an instance git for version control of the production data
try:
import sys
private_path = BASE_PRIVATE_DIR.child('production')
sys.path.append(private_path)
from private_settings import *
except ImportError:
print(" No production overide private_settings.py found. This is probably an error = {}".format(private_path))
# If it doesnt' exist that is fine and just use system and environment defaults