{"id":15,"date":"2017-11-10T19:39:09","date_gmt":"2017-11-10T19:39:09","guid":{"rendered":"https:\/\/agileleaf.com\/blog\/a-better-way-to-manage-settings-py-in-your-django-projects\/"},"modified":"2017-11-10T19:39:09","modified_gmt":"2017-11-10T19:39:09","slug":"a-better-way-to-manage-settings-py-in-your-django-projects","status":"publish","type":"post","link":"https:\/\/agileleaf.com\/blog\/a-better-way-to-manage-settings-py-in-your-django-projects\/","title":{"rendered":"A better way to manage settings.py in your Django projects"},"content":{"rendered":"<p><!--kg-card-begin: markdown--><\/p>\n<blockquote>\n<p><em>I use the term &quot;environment&quot; quite a lot in this article. If you&#8217;re not aware of what that means, or just want to be sure that I use it in the same way as you do, look at the definition I give at the <a href=\"#environment\">end of the article<\/a>.<\/em><\/p>\n<\/blockquote>\n<p><code>settings.py<\/code> is a core file in Django projects. It holds all the configuration values that your web app needs to work; database settings, logging configuration, where to find static files, API keys if you work with external APIs, and a bunch of other stuff.<\/p>\n<p>But once you start setting up your Django app on multiple environments; like production, testing, and staging \u2014 and on machines for new developers, you are likely to run into a pain point; managing the configuration across the different environments.<\/p>\n<p>This article discusses about this problem, provides the solution that we have used for most of our projects, and also lists other ways in which the Django community tackles this.<\/p>\n<h2 id=\"theproblem\">The problem<\/h2>\n<p>I think one of the reasons that many Django tutorials; including the official one, don&#8217;t mention this issue is because honestly, it&#8217;s not a <strong>huge<\/strong> pain. Compared to how often your make changes to the code for your views, templates, and models \u2013 you rarely touch the <code>settings.py<\/code> file once you have it setup.<\/p>\n<p>But do the same thing over and over again; over the period of years that some projects are in use, and you tend to get tired of it and start wanting a better solution. But what exactly is the problem?<\/p>\n<p>Imagine you are developing a new web application in Django. You have been working on it for a couple of weeks now. Initially it was just something you started as a proof-of-concept, but now your employer sees it going somewhere \u2013 and you get assigned a team to help you finish this thing and release it.<\/p>\n<p>When that second developer wants to start working on the project, they clone it from the Git\/Mercurial\/whatever repo, install the project dependencies, and run <code>python manage.py runserver<\/code> to test it out. But wait, what&#8217;s this.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/someweb.com\/content\/images\/2017\/11\/Screenshot-2017-11-10-15.25.30.png\" alt=\"Screenshot-2017-11-10-15.25.30\" loading=\"lazy\"><\/p>\n<p>Errors like these are common when you setup a Django project on a new machine. When you were working on your own, you set some configuration options; like the logging file path, to work on your machine. But now that your colleague wants to run the project on their machine, they don&#8217;t have the same file system structure as yours.<\/p>\n<p>The quickest way most beginner Django developers solve this is to change the <code>settings.py<\/code> file to make the project run on their machines. But this just creates another problem a bit later down the line.<\/p>\n<p>If your colleague pushes their modified <code>settings.py<\/code> to the code repo and you pull the changes, the project stops working on your machine. Because, wait for it \u2014 their file system structure is different than yours.<\/p>\n<h2 id=\"afirststabattheproblem\">A first stab at the problem<\/h2>\n<p>One solution is to use relative paths instead of absolute ones. So instead of<\/p>\n<pre><code>LOGGING_DIR = &quot;\/home\/jibran\/logs\/&quot;<\/code><\/pre>\n<p>you use<\/p>\n<pre><code>LOGGING_DIR = os.path.join(BASE_DIR, &quot;logs&quot;)<\/code><\/pre>\n<p>This way the logs are now written to a location relative to the <code>BASE_DIR<\/code> variable, which for Django projects is set to the directory containing the <code>manage.py<\/code> file.<\/p>\n<p>This solves the problem described above with system specific paths, but it&#8217;s not the complete answer. You have many other types of configuration options \u2013 the database username and password, authentication keys for APIs, <code>SMTP<\/code> options on how to send emails&#8230;<\/p>\n<p>When you want to deploy your application to multiple environments, API keys are something that are most likely to change. Many of the APIs we use everyday offer testing\/sandbox and live environments.<\/p>\n<p>SendWithUs (a service used to send emails) for example allows you to create test API keys that deliver emails to only 1 inbox, ideal when testing as you don&#8217;t want test emails going to your customers. When you have production, testing, and local development environments, you will need different keys for all these different setups.<\/p>\n<p>There is also issue of security; you ideally don&#8217;t want API keys for your services to be a part of your code repository. Why not?<\/p>\n<ul>\n<li>A security breach could let anyone have access to your repo. <a href=\"http:\/\/homakov.blogspot.ae\/2014\/02\/how-i-hacked-github-again.html\">It&#8217;s been known to happen!<\/a><\/li>\n<li>You could hire outside contractors or freelancers to work on your code. Ideally they shouldn&#8217;t be aware of your API keys.<\/li>\n<li>You may want to limit what API keys junior developers on your team have access to. If your keys are in your repo, you can&#8217;t have that protection.<\/li>\n<\/ul>\n<p>There are a number of good ways in which you can solve this problem. I&#8217;ll first show what the solution that we at Agile Leaf use. Later I&#8217;ll also list some other solutions from the Django community.<\/p>\n<h2 id=\"oursolution\">Our solution<\/h2>\n<p>The solution we use here at Agile Leaf is surprisingly simple; both to implement and understand. At the end of <code>settings.py<\/code> for our Django projects, we put<\/p>\n<pre><code>try:   from local_settings import *except ImportError:    raise Exception(&quot;A local_settings.py file is required to run this project&quot;)<\/code><\/pre>\n<p>and then we put all our environment dependent configuration options into a <code>local_settings.py<\/code> file that sits in the same directory as the <code>settings.py<\/code>. We make sure that the local settings file isn&#8217;t version controlled, and then each environment we deploy to, we create a new <code>local_settings.py<\/code> file on that machine with settings specific for that environment.<\/p>\n<p>The reason this works is because the <code>settings.py<\/code> file is just another Python script. When Django needs to read a configuration value, it will import this file. Since we are doing a <em>star import<\/em> from our local settings into the main settings file, all variables defined in <code>local_settings.py<\/code> become available inside <code>settings.py<\/code>. More importantly, these imported variables override any that are already defined in the <code>settings.py<\/code>, which is why this import goes at the end of the settings file.<\/p>\n<p>Now, instead of defining our API keys in the <code>settings.py<\/code> file, we define it in <code>local_settings.py<\/code>, like this.<\/p>\n<pre><code>SENDWITHUS_API_KEY = &quot;&lt;API KEY&gt;&quot;AWS_KEY = &quot;&lt;AWS_KEY&gt;&quot;...<\/code><\/pre>\n<p>Each developer on our team is then free to modify their own copy of the <code>local_settings.py<\/code> without needing to touch the main <code>settings.py<\/code> file. Since the local settings are not pushed to our code repo, we don&#8217;t get accidentally break each others development environments.<\/p>\n<p>More importantly, we don&#8217;t need to worry about having live API keys on our test environments, or test keys on our production environments. When we create a new environment, we first create a <code>local_settings.py<\/code> file for that environment.<\/p>\n<p>For example, the first time we create a production server for our projects, we will create the local settings file, which then gets saved on an internal machine that isn&#8217;t publicly accessible. The next time we need to create a production server, we can just copy this file over and have the new server up and running quickly.<\/p>\n<p>Which configuration options you put in <code>settings.py<\/code> and which you put in <code>local_settings.py<\/code> depends on your project. The best advice I can give is to move settings that definitely change between environments; like the database and logging configurations.<\/p>\n<p>As your project grows and you learn more about this approach, you&#8217;ll get an intuitive feel for where a specific configuration option should go to. I plan to write another article later discussing the particular method we use when dividing our configuration options between the main and local settings.<\/p>\n<h2 id=\"whythisway\">Why this way?<\/h2>\n<p>We settled upon this approach because it solves almost all of the pains we felt while developing Django projects.<\/p>\n<ul>\n<li>We don&#8217;t commit our secret API keys to our code repo.<\/li>\n<li>When the code is setup on a new machine without a local settings file, the developer gets a better error message telling them that a local settings file is needed to run the project, instead of cryptic &quot;Path does not exist&quot; errors.<\/li>\n<li>Because the local settings are imported at the end, we can add any configuration option to <code>local_settings.py<\/code> and override whatever <code>settings.py<\/code> already defines. For example our main settings file can have <code>DEBUG = False<\/code> but for our local development machines we can put <code>DEBUG = True<\/code> in the local settings file to override that.<\/li>\n<li>Because we are overriding the values defined in <code>settings.py<\/code>, we don&#8217;t need to put every configuration option in <code>local_settings.py<\/code>, only those that we need to change for the machine the project is running on. This also allows us to keep common settings; like the <code>INSTALLED_APPS<\/code>, <code>MIDDLEWARE<\/code> and other similar options in our <code>settings.py<\/code> file.<\/li>\n<li>Making changes to configuration for each environment is easy. Let&#8217;s say we want to change the API key for SendWithUs in our production environment. We modify the <code>local_settings.py<\/code> we have for our production environment, and then copy that file over to all the production servers.<\/li>\n<li>This doesn&#8217;t change how the configuration is done. It&#8217;s just another file that&#8217;s imported into the <code>settings.py<\/code> file. Which means that new Django developers who have just started using Django can directly use the knowledge they gained about configuration from the Django tutorial in our project. They don&#8217;t need to learn 1 more thing specific to our projects.<\/li>\n<\/ul>\n<p>We have been using this technique in our Django projects for a couple of years, and it has served us well. Give it a try. Next I&#8217;ll discuss one alternative in detail, and then list some other options available in the community.<\/p>\n<h3 id=\"asmalltweak\">A small tweak<\/h3>\n<p>A small tweak to our approach is to move the <code>from local_settings import *<\/code> near the top of <code>settings.py<\/code>. This has some benefits.<\/p>\n<p>You could define variables like<\/p>\n<pre><code>DATABASE_HOST = 'localhost:5678'DATABASE_USER = 'root'LOGGING_DIR = '\/home\/jibran\/logs'<\/code><\/pre>\n<p>at the start of <code>settings.py<\/code> with reasonable defaults for a local development environment. Then import local settings. Finally, instead of using something like<\/p>\n<pre><code>DATABASES = {    'default': {        'ENGINE': 'django.db.backends.postgresql',        'NAME': 'db_name',        'USER': 'jibran'    }}<\/code><\/pre>\n<p>in the <code>settings.py<\/code> file, you could use<\/p>\n<pre><code>DATABASES = {    'default': {        'ENGINE': 'django.db.backends.postgresql',        'NAME': DATABASE_NAME,        'USER': DATABASE_USER    }}<\/code><\/pre>\n<p>This allows you to make it even easier to create a new local settings file. If it&#8217;s imported at the bottom; like we do, you need to define that entire <code>DATABASES<\/code> config dictionary in the local settings to override the configuration in the main settings. By importing near the top, you can override just specific variables and <code>settings.py<\/code> will use those new values.<\/p>\n<p>We haven&#8217;t used this way yet because for us, the tradeoff of making sure that the file is imported in the correct place \u2013 after the variables that can be overridden, but before those variables are used vs. needing to write the complete config option to override (like <code>DATABASES<\/code> in the example above) isn&#8217;t worth it. But maybe it&#8217;s something you prefer.<\/p>\n<h2 id=\"otheroptions\">Other options<\/h2>\n<p>Over the years, people have successfully used a number of ways different than what we have described here. Some of these are listed here.<\/p>\n<h3 id=\"versioncontrolledenvironmentspecificsettingsfiles\">Version controlled environment specific settings files<\/h3>\n<p>One popular method that you can see suggested on Django forums and Stackoverflow is to have multiple settings files. For example, you could have<\/p>\n<ul>\n<li><code>settings.dev.py<\/code><\/li>\n<li><code>settings.prod.py<\/code><\/li>\n<li><code>settings.test.py<\/code><\/li>\n<\/ul>\n<p>Then when you need to run your project you define the <code>DJANGO_SETTINGS_MODULE<\/code> env var to point to the correct file.<\/p>\n<p>Let&#8217;s say you want to run the development server for your project called <strong>myproject<\/strong>. Instead of<\/p>\n<pre><code>python manage.py runserver<\/code><\/pre>\n<p>You would run<\/p>\n<pre><code>DJANGO_SETTINGS_MODULE=myproject.settings.dev python manage.py runserver<\/code><\/pre>\n<p>Likewise on the production and test environments, you&#8217;d configure <code>uwsgi<\/code> or <code>gunirocrn<\/code> to set <code>DJANGO_SETTINGS_MODULE<\/code> to the correct settings file name for that environment.<\/p>\n<p>I personally don&#8217;t like this because:<\/p>\n<ul>\n<li>You&#8217;re still committing API keys to your code repo<\/li>\n<li>You now have to remember to set the <code>DJANGO_SETTINGS_MODULE<\/code> env var every time you use <code>manage.py<\/code> commands. It&#8217;s easier for me to define a local settings once instead of needing to remember this.<\/li>\n<li>Each individual settings file now needs to contain all the configuration options. You&#8217;re not overriding anything, instead you have completely different configurations. Given how most projects need to change only some configuration values for different environments, it feels like overkill.<\/li>\n<li>It&#8217;s easy for the files to get out of sync. For example you can add a new application to <code>INSTALLED_APPS<\/code> for your local environment and forget to add it to the production environment settings. <a href=\"https:\/\/www.rdegges.com\/2011\/the-perfect-django-settings-file\/\">A blog post<\/a> I found while researching this article discusses a very good way of side stepping this problem, while having environment specific settings files.<\/li>\n<\/ul>\n<p>While I prefer my way \ud83d\ude42 I do see the benefits of this approach:<\/p>\n<ul>\n<li>Updating configuration on servers is now even easier. No need to have two workflows, one for updating the code and one for the settings.<\/li>\n<li>The project <em>literally<\/em> works out of the box. Once you put the code on a server, selecting the right settings module is all you need to do before running the code. No need to go messing around figuring out the right version of <code>local_settings.py<\/code> to use.<\/li>\n<\/ul>\n<h3 id=\"multiplesettingsfileswithoutneedingtoexplicitlyselectone\">Multiple settings files, without needing to explicitly select one<\/h3>\n<p>While researching for this article, I found <a href=\"https:\/\/stackoverflow.com\/questions\/88259\/how-do-you-configure-django-for-simple-development-and-deployment\/88331#88331\">this<\/a> answer on Stackoverflow. It&#8217;s similar to how you would create different settings files for different environments, but instead of selecting one manually, your <code>settings.py<\/code> file now selects for you based on which machine the project is running on.<\/p>\n<p>Some of the same reservations that I have to the last method apply here.<\/p>\n<h3 id=\"djangosplitsettings\">Django split settings<\/h3>\n<p>There&#8217;s a nice project I found called <a href=\"http:\/\/django-split-settings.readthedocs.io\/en\/latest\/\"><code>django_split_settings<\/code><\/a>. It seems to be a combination of the method we use, and splitting the settings across multiple files. You can include optional local settings files to override default configs.<\/p>\n<p>This seems like a nice option that we might try out in the future. One reservation though is that it adds another thing that new developers will now need to learn.<\/p>\n<h3 id=\"djangoconfigurations\">Django configurations<\/h3>\n<p><a href=\"http:\/\/django-configurations.readthedocs.io\/en\/latest\/\">django-configurations<\/a> is a Django module I found from the Stackoverflow answer I mentioned above. I haven&#8217;t researched it in detail so can&#8217;t say anything, but it&#8217;s another option for you to consider.<\/p>\n<h3 id=\"alltheotheroptions\">All the other options<\/h3>\n<p>I found during research for this article a page on the Django wiki that lists many different ways people handle this and other configuration pains. I&#8217;m sure it&#8217;s an interesting read to compare different ways. You can see it at <a href=\"https:\/\/code.djangoproject.com\/wiki\/SplitSettings\">https:\/\/code.djangoproject.com\/wiki\/SplitSettings<\/a>.<\/p>\n<p><a name=\"environment\"><\/a><\/p>\n<h1 id=\"defineenvironment\">Define environment<\/h1>\n<p>This article uses the term <strong>environment<\/strong> a lot. Some new Django developers might not know what the term means. I&#8217;ll try to define it clearly here.<\/p>\n<p>An environment (defined as what I mean when I use the word in this article) is a group of machines (may be just 1 machine) that use the same configuration. Here&#8217;s an example.<\/p>\n<p>Once you are done developing your web application, you usually get a cloud server from some provider like AWS, Digital Ocean, Google Cloud, or one of the other hundred options available out there. You then setup your first server to serve live traffic. This machine is your production environment, because it serves production\/live traffic.<\/p>\n<p>Eventually, you might want to add more servers to serve more users; by using load balancing. Any other servers you add will usually have the same configuration as your first server. They&#8217;ll use the same database, have the same path for logging files, etc. This group of machines that now serves your live traffic is also part of your production environment.<\/p>\n<p>Later you might want to have some servers on the cloud to run a testing environment that you can push new updates to before you update your live environment. Your testing team can use this to find bugs, or you could show new features to clients, without worrying about it effecting your live traffic. This is your testing environment.<\/p>\n<p>You also have your own machine, where you develop the application. Other developers <em>hopefully<\/em> have their own machines for this task; unless you&#8217;re weird and share just 1 machine between yourselves. I&#8217;m not judging!<\/p>\n<p>These development machines are your development environment.<\/p>\n<p>Hopefully this clears up what I mean when I use <strong>environment<\/strong> in this article.<\/p>\n<h1 id=\"signupforourmailinglist\">Signup for our mailing list<\/h1>\n<p>We try to publish interesting articles like this on tech, marketing, and other topics based on projects we work on. If you would like to be notified the next time we publish something, please signup for our mailing list. We promise not to spam you!<\/p>\n<p><!-- Begin MailChimp Signup Form --><link href=\"\/\/cdn-images.mailchimp.com\/embedcode\/horizontal-slim-10_7.css\" rel=\"stylesheet\" type=\"text\/css\">\n<style type=\"text\/css\">\t#mc_embed_signup{background:#fff; clear:left; font:14px Helvetica,Arial,sans-serif; width:100%;}\t\/* Add your own MailChimp form style overrides in your site stylesheet or in this style block.\t   We recommend moving this block and the preceding CSS link to the HEAD of your HTML file. *\/<\/style>\n<div id=\"mc_embed_signup\">\n<form action=\"https:\/\/agileleaf.us15.list-manage.com\/subscribe\/post?u=b0bb0e0bb5e16fc13d72bb2b0&amp;id=84ebe01e72\" method=\"post\" id=\"mc-embedded-subscribe-form\" name=\"mc-embedded-subscribe-form\" class=\"validate\" target=\"_blank\" novalidate>\n<div id=\"mc_embed_signup_scroll\">\t<label for=\"mce-EMAIL\">Subscribe to our mailing list<\/label>\t<input type=\"email\" value=\"\" name=\"EMAIL\" class=\"email\" id=\"mce-EMAIL\" placeholder=\"email address\" required>    <!-- real people should not fill this in and expect good things - do not remove this or risk form bot signups-->    <\/p>\n<div style=\"position: absolute; left: -5000px;\" aria-hidden=\"true\"><input type=\"text\" name=\"b_b0bb0e0bb5e16fc13d72bb2b0_84ebe01e72\" tabindex=\"-1\" value=\"\"><\/div>\n<div class=\"clear\"><input type=\"submit\" value=\"Subscribe\" name=\"subscribe\" id=\"mc-embedded-subscribe\" class=\"button\"><\/div>\n<\/p><\/div>\n<\/form>\n<\/div>\n<p><!--End mc_embed_signup--><\/p>\n<p><em>Do you have an idea for a web site or application you&#8217;d like developed and need an experienced team to create it for you? Get in touch with us at <a href=\"mailto:hello@agileleaf.com\">hello@agileleaf.com<\/a>.<\/em><\/p>\n<p><em>And if you&#8217;re a fellow developer working on a Django project and get stuck somewhere, reach out to us at <a href=\"mailto:hello@agileleaf.com\">hello@agileleaf.com<\/a>. We&#8217;ll try our best to get back to you and help with whatever you&#8217;re having problems with.<\/em><\/p>\n<p><!--kg-card-end: markdown--><\/p>\n","protected":false},"excerpt":{"rendered":"<p>I use the term &quot;environment&quot; quite a lot in this article. If you&#8217;re not aware of what that means, or just want to be sure that I use it in the same way as you do, look at the definition I give at the end of the article. settings.py is a core file in Django [&hellip;]<\/p>\n","protected":false},"author":4,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"nf_dc_page":"","footnotes":""},"categories":[1],"tags":[8,9],"class_list":["post-15","post","type-post","status-publish","format-standard","hentry","category-uncategorized","tag-django","tag-tech"],"_links":{"self":[{"href":"https:\/\/agileleaf.com\/blog\/wp-json\/wp\/v2\/posts\/15","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/agileleaf.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/agileleaf.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/agileleaf.com\/blog\/wp-json\/wp\/v2\/users\/4"}],"replies":[{"embeddable":true,"href":"https:\/\/agileleaf.com\/blog\/wp-json\/wp\/v2\/comments?post=15"}],"version-history":[{"count":0,"href":"https:\/\/agileleaf.com\/blog\/wp-json\/wp\/v2\/posts\/15\/revisions"}],"wp:attachment":[{"href":"https:\/\/agileleaf.com\/blog\/wp-json\/wp\/v2\/media?parent=15"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/agileleaf.com\/blog\/wp-json\/wp\/v2\/categories?post=15"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/agileleaf.com\/blog\/wp-json\/wp\/v2\/tags?post=15"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}