Package: df-config
df_configDjango,the web framework for perfectionists with deadlines, is based on a single settings Python module defined in a environment variable.
However, these settings could be organized into three categories:settings that are very common and that can be kept as-is for most projects (USE_TZ = TrueorMEDIA_URL = '/media/'),settings that are specific to your project but common to all instances of your project (likeINSTALLED_APPS),settings that are installation-dependent (DATABASE_PASSWORD, …)Moreover, there are dependencies between settings. For example,ADMIN_EMAIL,ALLOWED_HOSTSandCSRF_COOKIE_DOMAINdepend
on the same domain name of your site,  andSESSION_COOKIE_SECUREandCSRF_COOKIE_SECUREcan be set only when you use TLS.
df_config allows to use functions to dynamically define these settings using some other settings as parameters.Finally, df_config dynamically merges several sources to define the final settings::mod:df_config.config.defaultsthat aims at providing good default values,yourproject.defaultsfor your project-specific settings,/etc/yourproject/settings(.py|.ini)for installation-dependent settings,environment variables are also read if you prefer to use them.df_config also defines settings that should be valid for most sites, based on common installed Django apps.You can define a list of settings that are read from a traditionnal text configuration file (.ini format <https://docs.python.org/3/library/configparser.html>_).
Finally, df_config also searches for environment variables, orlocal_settings.pyandlocal_settings.inisetting files in the working directory.Requirements and installationdf_config works with:Python 3.6+,Django 2.0+.python-mpipinstalldf_configHow to use it?df_config assumes that your project has a main moduleyourproject.
Then you just have two steps to do:update yourmanage.pyfile:#!/usr/bin/env pythonfromdf_config.manageimportmanage,set_envset_env(module_name="yourproject")if__name__=="__main__":manage()copy your current settings (as-is) intoyourproject/defaults.py,You can take a look to the resulting settings and from which source they are read:python3manage.pyconfigpython-v2If you want a single settings file, you can also create it:python3manage.pyconfigpython--filenamesettings.pyupdate your setup.pyYou should the entry point in yoursetup.pyfile:entry_points={"console_scripts":["yourproject-ctl = df_config.manage:manage"]}Once installed, the commandyourproject-ctl(in fact, you can changectlby anything without hyphen) executes the standardmaanagecommand.dynamize your settings!First, you can provide sensible defaults settings inyourproject.pyand overwrite the dev settings inlocal_settings.py.
Then real things can begin:
For example, imagine that you currently have the following settings:LOG_DIRECTORY='/var/myproject/logs'STATIC_ROOT='/var/myproject/static'MEDIA_ROOT='/var/myproject/media'FILE_UPLOAD_TEMP_DIR='/var/myproject/tmp'If you change the base directory/var/myproject, four variables needs to be changed (and you will forget to change at least one).
Now, you can write:LOCAL_PATH='/var/myproject'LOG_DIRECTORY='{LOCAL_PATH}/logs'STATIC_ROOT='{LOCAL_PATH}/static'MEDIA_ROOT='{LOCAL_PATH}/media'FILE_UPLOAD_TEMP_DIR='{LOCAL_PATH}/tmp'Now, you just have to redefineLOCAL_PATH; but you can even go slightly further:fromdf_config.config.dynamic_settingsimportDirectoryLOCAL_PATH=Directory('/var/myproject')LOG_DIRECTORY=Directory('{LOCAL_PATH}/logs')STATIC_ROOT=Directory('{LOCAL_PATH}/static')MEDIA_ROOT=Directory('{LOCAL_PATH}/media')FILE_UPLOAD_TEMP_DIR=Directory('{LOCAL_PATH}/tmp')If you run thecheckcommand, you will be warned for missing directories, and thecollectstaticandmigratecommands
will attempt to create them.
Of course, you still havesettings.MEDIA_ROOT == '/var/myproject/media'in your code, when settings are loaded.You can use more complex things, instead of:SERVER_BASE_URL='http://www.example.com'SERVER_NAME='www.example.com'USE_SSL=FalseALLOWED_HOSTS=['www.example.com']CSRF_COOKIE_DOMAIN='www.example.com'You could just write:fromurllib.parseimporturlparsefromdf_config.config.dynamic_settingsimportCallableSettingSERVER_BASE_URL='http://www.example.com'SERVER_NAME=CallableSetting(lambdax:urlparse(x['SERVER_BASE_URL']).hostname,'SERVER_BASE_URL')USE_SSL=CallableSetting(lambdax:urlparse(x['SERVER_BASE_URL']).scheme=='https','SERVER_BASE_URL')ALLOWED_HOSTS=CallableSetting(lambdax:[urlparse(x['SERVER_BASE_URL']).hostname],'SERVER_BASE_URL')CSRF_COOKIE_DOMAIN=CallableSetting(lambdax:urlparse(x['SERVER_BASE_URL']).hostname,'SERVER_BASE_URL')Configuration files and environment variablesYour user probably prefer use .ini files instead of Python ones.
By default, df_config searches for a listINI_MAPPINGinto the moduleyourproject.iniconfor usesdf_config.iniconf.INI_MAPPING.fromdf_config.config.fieldsimportConfigFieldINI_MAPPING=[ConfigField("global.server_url","SERVER_BASE_URL",help_str="Public URL of your website.",env_name="SERVER_BASE_URL")]Some specialized classes are available indf_config.config.fields:CharConfigField,IntegerConfigField,FloatConfigField,ListConfigField,BooleanConfigField,ChoiceConfigFile.
You can also pickup some predefined list indf_config.iniconf.You can also use environment variables instead of an .ini file (only for values in the INI_MAPPING list):exportSERVER_URL=http://www.example-2.com
python3manage.pyconfigpython-v2|grepSERVER_BASE_URLYou can check the current config as a .ini file or as environment variables:exportSERVER_URL=http://www.example-2.com
python3manage.pyconfigenv
python3manage.pyconfiginidynamic settingsBy default, anystris assumed to be a template string: for example,LOG_DIRECTORY = '{LOCAL_PATH}/logs'needsLOCAL_PATHto be defined.
So, if one of your setting include such values, it need to be encapsulated:fromdf_config.config.dynamic_settingsimportRawValueLOG_DIRECTORY=RawValue('{LOCAL_PATH}/logs')Other dynamic classes are:fromdf_config.config.dynamic_settingsimportDirectory,AutocreateFileContent,SettingReference,CallableSetting,ExpandIterablefromdf_config.guesses.miscimportgenerate_secret_key,secure_hsts_secondsDIRNAME=Directory("/tmp/directory")# creates /tmp/directory with collectstatic/migrate commands, otherwise you have a warningSECRET_KEY=AutocreateFileContent("{LOCAL_PATH}/secret_key.txt",generate_secret_key,mode=0o600,use_collectstatic=False,use_migrate=True)# if the file does not exist, SECRET_KEY = generate_secret_key()# if the file does exist, SECRET_KEY is equal to its content# if the migrate command is called and the file does not exist, it is created and the result of generate_secret_key() is written to itTEMPLATE_DEBUG=SettingReference('DEBUG')# TEMPLATE_DEBUG is always equal to DEBUG, even if DEBUG is defined in another fileSECURE_HSTS_SECONDS=CallableSetting(secure_hsts_seconds,"USE_SSL")# the secure_hsts_seconds is called with a dict that contains the currently resolved settings# at least USE_SSL is present in this dict (maybe some other values are also defined)# the list of required settings can be directly set as an attribute of the callable:# secure_hsts_seconds.required_settings = ["USE_SSL"]# SECURE_HSTS_SECONDS = CallableSetting(secure_hsts_seconds)INSTALLED_APPS=["django.contrib.admin",...,ExpandIterable("EXTRA_APPS"),"django.contrib.auth"]# EXTRA_APPS must be a list, whose values are inserted in the final valauesserver commandYou can choose the application server used in production:DF_SERVER="gunicorn"# "gunicorn" or "daphne"A new Django commandserveris available and launchesgunicornordaphne. The application and the listen address/port are specified so you do no have to set them.HerokuEnvironment variable names have been chosen to be compatible with the Heroku default environment:SECRET_KEY: should set it in yourapp.jsonfileDATABASE_URL: automatically set by the "heroku-postgresql" addonPORT: set by defaultHEROKU_APP_NAME: you should set it in yourapp.jsonfileDjango app detectionA few well-known Django applications are automatically detected and added to the list ofINSTALLED_APPS:django-redis_sessionsdjango-pipelinedjango-debug-toolbardjango-allauthwhitenoisedjango-cspDefaults settings are also proposed so these applications should be working out of the box.