<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Matt Layman</title>
    <description>The latest articles on DEV Community by Matt Layman (@mblayman).</description>
    <link>https://dev.to/mblayman</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F213585%2F167d49ee-86fe-41e1-bbb8-73e65b7c7de3.jpeg</url>
      <title>DEV Community: Matt Layman</title>
      <link>https://dev.to/mblayman</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mblayman"/>
    <language>en</language>
    <item>
      <title>Understand Django: Security and Django</title>
      <dc:creator>Matt Layman</dc:creator>
      <pubDate>Thu, 10 Mar 2022 15:23:48 +0000</pubDate>
      <link>https://dev.to/mblayman/understand-django-security-and-django-3pof</link>
      <guid>https://dev.to/mblayman/understand-django-security-and-django-3pof</guid>
      <description>&lt;p&gt;In the last &lt;a href="https://www.mattlayman.com/understand-django/"&gt;Understand Django&lt;/a&gt; article, we learned about where apps slow down. We explored techniques that help sites handle the load and provide a fast experience for users.&lt;/p&gt;

&lt;p&gt;With this article, we will look at security. How does a Django site stay safe on the big, bad internet? Let's find out.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Security Confession
&lt;/h2&gt;

&lt;p&gt;I have a confession to make. Of all the topics that I've covered about Django in this series, &lt;em&gt;this is my least favorite one.&lt;/em&gt; Perhaps that's why I've pushed the subject so far into this list of articles.&lt;/p&gt;

&lt;p&gt;I have a very hard time getting excited about security because it feels like a pure cost to me. As developers, we're in this arms race against malicious people who want to steal and profit from the data of others. In a perfect world, everyone would respect the privacy of others and leave private data alone. Alas, the world is far from perfect.&lt;/p&gt;

&lt;p&gt;The bad actors have devised clever and tricky methods of exploiting websites to steal data. Because of this, application developers have to implement guards in an attempt to prevent these exploits. Implementing those guards detract from the main objective of site building and often feels like a drag on efficiency.&lt;/p&gt;

&lt;p&gt;All that being said, &lt;strong&gt;security is super important&lt;/strong&gt;. Even if you're like me and the topic doesn't naturally interest you (or actively feels like a waste of time), the security of your application matters.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Privacy matters.&lt;/li&gt;
&lt;li&gt;Trust matters.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If we cannot protect the information that users of our Django sites bring, then trust will rapidly erode and, most likely, your users will disappear along with it.&lt;/p&gt;

&lt;p&gt;As noted in this section, security is not my favorite topic. I'm going to describe some security topics as they relate to Django, but if you want to learn from people who &lt;em&gt;love&lt;/em&gt; security, then I would recommend reading from the &lt;a href="https://owasp.org/"&gt;Open Web Application Security Project&lt;/a&gt;. This popular group can teach you far more about security than I can, and do it with gusto!&lt;/p&gt;

&lt;h2&gt;
  
  
  The Three &lt;strong&gt;C&lt;/strong&gt;s
&lt;/h2&gt;

&lt;p&gt;Security has a bunch of acronyms to learn. I don't know if this is something that security researchers like to do, or if the it's because the problems that the acronyms stand for are challenging to understand. Either way, let's look at three common acronyms that start with C and the problems they address.&lt;/p&gt;

&lt;h3&gt;
  
  
  CSRF
&lt;/h3&gt;

&lt;p&gt;In a number of these Understand Django articles, I have discussed CSRF briefly. In the forms article, I did some hand waving and stated that you need a CSRF token for security reasons and basically said "trust me" at the time.&lt;/p&gt;

&lt;p&gt;CSRF stands for &lt;em&gt;Cross Site Request Forgery&lt;/em&gt;. In my terms, a CSRF attack allows an attacker to use someone's credentials to a different site without their permission. With a bit of imagination, you can see where this goes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Attacker socially manipulates a user to click a link.&lt;/li&gt;
&lt;li&gt;The click activity exploits the user's credentials to a site and changes something about the user's account like their email address.&lt;/li&gt;
&lt;li&gt;The attacker changes the email address to something they control.&lt;/li&gt;
&lt;li&gt;If the original site is something like an e-commerce site, the attacker may make purchases using the user's stored credit card information.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Django include a capability to help thwart this kind of attack. Through the use of &lt;strong&gt;CSRF tokens&lt;/strong&gt;, we can help prevent bad actors from performing actions without user consent.&lt;/p&gt;

&lt;p&gt;A CSRF token works by including a generated value that gets submitted along with the form. The template looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jinja"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"POST"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="nv"&gt;csrf_token&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"myvalue"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When this renders, the result would be something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;form&lt;/span&gt; &lt;span class="na"&gt;method=&lt;/span&gt;&lt;span class="s"&gt;"POST"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"hidden"&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"csrfmiddlewaretoken"&lt;/span&gt;
    &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"gubC92ukKk62qtvkjp1t6iinHgflk9LD5Uke53QYHqUobOIzRp9nv2DuFqwx9ors"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"myvalue"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;input&lt;/span&gt; &lt;span class="na"&gt;type=&lt;/span&gt;&lt;span class="s"&gt;"submit"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The value would naturally be different from my example. When the form is submitted, the CSRF token gets checked for validity. A valid CSRF token is required to make a &lt;code&gt;POST&lt;/code&gt; request, so this level of checking can help prevent attackers from changing a user's data on your site.&lt;/p&gt;

&lt;p&gt;You can learn more about CSRF with Django's &lt;a href="https://docs.djangoproject.com/en/4.0/ref/csrf/"&gt;Cross Site Request Forgery protection&lt;/a&gt; reference page.&lt;/p&gt;

&lt;h3&gt;
  
  
  CORS
&lt;/h3&gt;

&lt;p&gt;Imagine that you've built &lt;code&gt;myapp.com&lt;/code&gt;. For your user interface, instead of Django templates, you built a client UI using a JavaScript framework like &lt;a href="https://vuejs.org/"&gt;Vue.js&lt;/a&gt;. Your application is serving static files at &lt;code&gt;myapp.com&lt;/code&gt;, and you built a Django-powered API that is handling the data which gets called at &lt;code&gt;api.myapp.com&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In this scenario, browsers will require you to set up CORS. CORS is &lt;em&gt;Cross-Origin Resource Sharing&lt;/em&gt;. The goal of CORS is to help protect a domain from undesirable access.&lt;/p&gt;

&lt;p&gt;In our example, your API at &lt;code&gt;api.myapp.com&lt;/code&gt; may only be designed to work with the user interface at &lt;code&gt;myapp.com&lt;/code&gt;. With CORS, you can configure your API so that it will reject any requests that do not come from the &lt;code&gt;myapp.com&lt;/code&gt; domain. This helps prevent bad actors from using &lt;code&gt;api.myapp.com&lt;/code&gt; in the browser.&lt;/p&gt;

&lt;p&gt;Django does &lt;em&gt;not&lt;/em&gt; include tools to handle CORS configuration from the core package. To make this work, you'll need to reach for a third party package. Since CORS configuration is handled through HTTP headers, you'll find that the very appropriately named &lt;a href="https://github.com/adamchainz/django-cors-headers"&gt;django-cors-headers&lt;/a&gt; package is exactly what you need.&lt;/p&gt;

&lt;p&gt;I won't walk through the whole setup of that package because the README does a good job of explaining the process, but I will highlight the crucial setting. With django-cors-headers, you need to set the &lt;code&gt;CORS_ALLOWED_ORIGINS&lt;/code&gt; list. Anything not in that list will be blocked by CORS controls in the browser. Our example configuration would look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;CORS_ALLOWED_ORIGINS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s"&gt;"https://myapp.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you read about CORS on the internet, you'll probably run into advice to set the HTTP header of &lt;code&gt;Access-Control-Allow-Origin: *&lt;/code&gt;. This wildcard is what you'll get if you set &lt;code&gt;CORS_ALLOW_ALL_ORIGINS = True&lt;/code&gt; in django-cors-headers. &lt;em&gt;This is probably not what you really want. Using this feature opts your site out of CORS protection.&lt;/em&gt; Unless you have some public web API that is &lt;em&gt;designed&lt;/em&gt; to work for many domains, you should try to avoid opting out of CORS.&lt;/p&gt;

&lt;p&gt;CORS is not a core concept that you will find in Django. If you want to learn more about the specifics of CORS, check out &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS"&gt;Cross-Origin Resource Sharing (CORS)&lt;/a&gt; from the Mozilla Developer Network (MDN).&lt;/p&gt;

&lt;h3&gt;
  
  
  CSP
&lt;/h3&gt;

&lt;p&gt;The final &lt;strong&gt;C&lt;/strong&gt; in our tour is &lt;em&gt;Content Security Policy&lt;/em&gt; or CSP, for short. You might roughly think of CSP as the inverse of CORS. Where CORS defines what parts of the internet can access your domain, CSP defines what your domain can access from the internet.&lt;/p&gt;

&lt;p&gt;The goal of CSP is to protect users on your site from running JavaScript (and other potentially harmful resources like images) from places that you don't want.&lt;/p&gt;

&lt;p&gt;To understand how your site can be vulnerable to these kinds of attacks, we need to understand a well-known attack vector called Cross-Site Scripting (XSS). XSS is when a bad actor finds a way to run a script on your domain. Here's a classic way that XSS can happen:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A site has a form that accepts text data.&lt;/li&gt;
&lt;li&gt;Then the site displays that text data &lt;em&gt;in its raw form&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At first, that seems harmless.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"area-where-user-content-gets-displayed"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  What could possibly go wrong?
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For an honest interaction like "What could possibly go wrong?" as user input, that is truly harmless. What about this?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"area-where-user-content-gets-displayed"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  I am getting &lt;span class="nt"&gt;&amp;lt;i&amp;gt;&lt;/span&gt;sneaky&lt;span class="nt"&gt;&amp;lt;/i&amp;gt;&lt;/span&gt;.
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, the user added a bit of HTML markup. Again, this is fairly benign and will only add some unanticipated italics. What if the user is a bit more clever than that?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"area-where-user-content-gets-displayed"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;&lt;span class="nx"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Boom goes the dynamite!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's where a site gets into trouble. If this is rendered on a page, a little alert box will appear. You can imagine this happening in a forum or some other sharing site where multiple people will see this output. That's annoying, but it's still not horrible.&lt;/p&gt;

&lt;p&gt;What does a really bad scenario look like? A really bad scenario is where the bad guys figure out that your site is unsafe in this way. Consider this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;class=&lt;/span&gt;&lt;span class="s"&gt;"area-where-user-content-gets-displayed"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://really-bad-guys.com/owned.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now your users are really in trouble. In this final version, the bad guys won. A user's browser will download and execute whatever JavaScript is in &lt;code&gt;owned.js&lt;/code&gt;. This code could do all kinds of stuff like using the &lt;code&gt;fetch&lt;/code&gt; API to run AJAX requests that can change the user's account credentials and steal their account.&lt;/p&gt;

&lt;p&gt;How do we defend against this kind of attack? There isn't a singular answer. In fact, multiple layers of protection is often what you really want. In security, this idea is called "defense-in-depth." If you have multiple layers to protect your site, then the site may become a less appealing target for attackers.&lt;/p&gt;

&lt;p&gt;For this particular scenario, we can use a couple of things&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HTML escaping of untrusted input&lt;/li&gt;
&lt;li&gt;CSP&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The real problem above is that the site is rendering user input without any modification. This is a problem with HTML because the raw characters are interpreted as HTML code and not just user data.&lt;/p&gt;

&lt;p&gt;The simplest solution is to make sure that any characters that mean something specific to HTML (like &lt;code&gt;&amp;lt;&lt;/code&gt; or &lt;code&gt;&amp;gt;&lt;/code&gt;) are replaced with escape codes (&lt;code&gt;&amp;amp;lt;&lt;/code&gt; or &lt;code&gt;&amp;amp;gt;&lt;/code&gt;) that will display the character in the browser without treating it like the actual HTML code character. &lt;strong&gt;Django does this auto-escaping of user data by default&lt;/strong&gt;. You can disable this behavior for portions of a template using a variety of template tags like &lt;code&gt;autoescape&lt;/code&gt; and the (ironically named?) &lt;code&gt;safe&lt;/code&gt; tag.&lt;/p&gt;

&lt;p&gt;Because there are ways to opt out of safe behavior from HTML escaping and because clever attackers might find other ways to inject script calls into your site, CSP is another layer of protection.&lt;/p&gt;

&lt;p&gt;Primarily, CSP is possible with a &lt;code&gt;Content-Security-Policy&lt;/code&gt; HTTP header. You can read all of the gritty details on the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP"&gt;Content Security Policy (CSP)&lt;/a&gt; article on MDN. Like CORS, CSP is not something that Django supports out-of-the-box. Thankfully, Mozilla (yep, the same Mozilla from MDN), offers a &lt;a href="https://django-csp.readthedocs.io/en/latest/index.html"&gt;django-csp&lt;/a&gt; package that you can use to configure an appropriate policy for your Django site.&lt;/p&gt;

&lt;p&gt;In a content security policy, you mark everything that you want to allow. This fundamentally changes what requests your site will connect to. Instead of allowing everything by default, the site operates on a model that denies things by default. With a "deny by default" stance, you can then pick resources which you deem are safe for your site. Modern browsers respect the policy declared by the HTTP header and will refuse to connect to resources out of your policy when users visit your site at your domain.&lt;/p&gt;

&lt;p&gt;There is something obvious that we should get out of the way. This setup and configuration requires &lt;em&gt;more work&lt;/em&gt;. Having more secure systems requires effort, study, and plenty of frustration as you make a site more secure. The benefits to your users or customers is that their data stays safe. I think most people expect this level of protection by default.&lt;/p&gt;

&lt;p&gt;So, do you have to become a security expert to build websites? I don't think so. There is a set of fundamental issues with web application security that you should know about (and we've covered some of those issues already), but you don't need to be prepared to go to black hat conventions in order to work on the web.&lt;/p&gt;

&lt;p&gt;Before finishing up this security article, let's look at what Django provides so that you can be less of a security expert and use the knowledge of the community.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check Command Revisited
&lt;/h2&gt;

&lt;p&gt;The Django documentation includes a good overview of the framework's security features on the &lt;a href="https://docs.djangoproject.com/en/dev/topics/security/"&gt;Security in Django&lt;/a&gt; page.&lt;/p&gt;

&lt;p&gt;Aside from the content outlined on the security page, we can return to the check command discussed in previous articles. Recall that Django includes a &lt;code&gt;check&lt;/code&gt; command that can check your site's configuration before you deploy a site live. The command looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;./manage.py check &lt;span class="nt"&gt;--deploy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output from this command can show where your configuration is less than ideal from a security perspective.&lt;/p&gt;

&lt;p&gt;The security warnings that come from running the &lt;code&gt;check&lt;/code&gt; command are defined in &lt;code&gt;django.core.checks.security&lt;/code&gt;. A more readable version of the available security checks is on the &lt;a href="https://docs.djangoproject.com/en/4.0/ref/checks/#security"&gt;System check framework&lt;/a&gt; reference page.&lt;/p&gt;

&lt;p&gt;Scanning through the list of checks, you'll find that&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;many checks center around configuring your site to run with HTTPS. Secure connections used to reference SSL for Secure Sockets Layer. Along the way, that layer changed names to TLS for Transport Layer Security. In practice, if you see either of those terms, think &lt;code&gt;https://&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;other checks confirm that your site has the kinds of &lt;a href="https://docs.djangoproject.com/en/4.0/ref/middleware/#security-middleware"&gt;middleware&lt;/a&gt; installed that offer some of the protection discussed previously (like CSRF).&lt;/li&gt;
&lt;li&gt;still other checks look for core settings that should be set like &lt;code&gt;DEBUG = False&lt;/code&gt; and defining the &lt;code&gt;ALLOWED_HOSTS&lt;/code&gt; setting.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is a good comment in the Django security checks reference docs that is worth repeating here:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The security checks do not make your site secure. They do not audit code, do intrusion detection, or do anything particularly complex. Rather, they help perform an automated, low-hanging-fruit checklist, that can help you to improve your site’s security.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;When you're thinking through security, do some homework and don't let your brain go on autopilot. Remember that users develop a trust relationship with your websites. That trust is easy to break and may cause people to leave your site forever.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;In this article, we explored security topics and how they related to Django. We covered:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CSRF&lt;/li&gt;
&lt;li&gt;CORS&lt;/li&gt;
&lt;li&gt;CSP&lt;/li&gt;
&lt;li&gt;Cross-site scripting (XSS)&lt;/li&gt;
&lt;li&gt;The security checks and information available from the &lt;code&gt;check&lt;/code&gt; command&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the &lt;em&gt;last&lt;/em&gt; article of the Understand Django series, we'll get into debugging. You'll learn about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Debugging tools like &lt;code&gt;pdb&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Browser tools&lt;/li&gt;
&lt;li&gt;Strategies for finding and fixing problems&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you'd like to follow along with the series, please feel free to sign up for my newsletter where I announce all of my new content. If you have other questions, you can reach me online on Twitter where I am &lt;a href="https://twitter.com/mblayman"&gt;@mblayman&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>python</category>
      <category>django</category>
      <category>security</category>
    </item>
    <item>
      <title>Understand Django: Go Fast With Django</title>
      <dc:creator>Matt Layman</dc:creator>
      <pubDate>Wed, 19 Jan 2022 15:38:15 +0000</pubDate>
      <link>https://dev.to/mblayman/understand-django-go-fast-with-django-2g81</link>
      <guid>https://dev.to/mblayman/understand-django-go-fast-with-django-2g81</guid>
      <description>&lt;p&gt;In the last &lt;a href="https://www.mattlayman.com/understand-django/"&gt;Understand Django&lt;/a&gt; article, we learned about commands. Commands are the way to execute scripts that interact with your Django app.&lt;/p&gt;

&lt;p&gt;With this article, we're going to dig into performance. How do you make your Django site faster? Keep reading to find out.&lt;/p&gt;

&lt;h2&gt;
  
  
  Theory Of Performance
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;There are two ways to make a website faster:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Do &lt;strong&gt;more&lt;/strong&gt; work.&lt;/li&gt;
&lt;li&gt;Do &lt;strong&gt;less&lt;/strong&gt; work.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;How we do more work &lt;em&gt;and&lt;/em&gt; less work depends on the type of work that we're trying to address on the site.&lt;/p&gt;

&lt;p&gt;When we're talking about doing more work, what I'm really saying is that we often need to increase the &lt;strong&gt;&lt;em&gt;throughput&lt;/em&gt;&lt;/strong&gt; of a site. Throughput is a measure of work over time. By increasing throughput, a site can serve more users concurrently.&lt;/p&gt;

&lt;p&gt;A very natural throughput measure on a Django site is &lt;em&gt;requests per second&lt;/em&gt;. A page view on a site could contain multiple requests, so requests per second isn't a perfect analog to how many users your site can handle, but it's still a useful measure to help you reason about site performance.&lt;/p&gt;

&lt;p&gt;On the flip side of doing more work, what does it mean to do less work to improve performance?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The fastest code is no code.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Every line of code that you write must be processed by a computer when it runs. If your code is inefficient or if too much is running, that will naturally mean that it will take longer to produce a final result.&lt;/p&gt;

&lt;p&gt;The time from when an input happens to when an output is received is called &lt;strong&gt;&lt;em&gt;latency&lt;/em&gt;&lt;/strong&gt;. If a user clicks on a link on your site, how long does it take for them to get a response. That time delay is latency.&lt;/p&gt;

&lt;p&gt;Less work doesn't just mean that you're running too much code! There are a lot of factors that might contribute to latency, some of them are easier to optimize than others.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Inefficient code - mistakes in development can make a computer slower than necessary&lt;/li&gt;
&lt;li&gt;Data size - sending more data requires more effort to deliver to users&lt;/li&gt;
&lt;li&gt;Geographic location - the speed of light is a real limit on network communication&lt;/li&gt;
&lt;li&gt;and more!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you can reduce latency on your site, you can improve the experience of the people using the site.&lt;/p&gt;

&lt;p&gt;In the rest of this article, you'll learn how you can do both more and less work to make a better site that will benefit your users.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measure First
&lt;/h2&gt;

&lt;p&gt;Before optimizing, we have to recognize what kind of work is impacting an app. In other words, &lt;em&gt;what is the resource constraint that is preventing an app from performing better?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Measuring applications, especially those that are running with live traffic, can be a tricky endeavor. I think we can look at an app from a zoomed out macro level or a zoomed in point of view.&lt;/p&gt;

&lt;p&gt;I would start my analysis from inspecting the system overall for patterns. Broadly, you will find that performance tends to fall into a couple of major bottleneck categories:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I/O bound&lt;/li&gt;
&lt;li&gt;CPU bound&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;I/O bound&lt;/em&gt; means that the system is limited (that's the "bound" part) by the inputs and outputs of the system. Since that's still a really vague statement, let's make it more concrete. An I/O bound system is one that is waiting for work to be available. Classic examples include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;waiting for responses from a database,&lt;/li&gt;
&lt;li&gt;waiting for content from a file system,&lt;/li&gt;
&lt;li&gt;waiting for data to transfer over a network,&lt;/li&gt;
&lt;li&gt;and so on.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Optimizing an I/O bound system is all about minimizing those waiting moments.&lt;/p&gt;

&lt;p&gt;Conversely, &lt;em&gt;CPU bound&lt;/em&gt; systems are systems that are drowning in immediate work to calculate. The computer's &lt;strong&gt;C&lt;/strong&gt;entral &lt;strong&gt;P&lt;/strong&gt;rocessing &lt;strong&gt;U&lt;/strong&gt;nit can't keep up with all that it's being asked to do. Classic examples of CPU bound work are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Data science computations for machine learning&lt;/li&gt;
&lt;li&gt;Image processing and rendering&lt;/li&gt;
&lt;li&gt;Test suite execution&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Optimizing a CPU bound system focuses heavily on making calculations faster and cheaper.&lt;/p&gt;

&lt;p&gt;Since we understand that an application that is underperforming is likely to be I/O bound or CPU bound, we can start to look for these patterns within the system. The easiest initial signal to observe is the CPU load. If the processors on your production machines are running at very high CPU utilization, then it's a pretty clear indicator that your app is CPU bound. In my estimation, you'll rarely see this for web applications. &lt;strong&gt;Most underperforming web applications are likely to be I/O bound.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The reason that web apps are often I/O bound has to do with their typical function. Most apps are fetching data from a database and displaying it to a user. There isn't a massive amount of computation (comparatively to something like machine learning) that the app needs to do. Thus, your app is probably waiting around for data from the database.&lt;/p&gt;

&lt;p&gt;If you observe that the system is not CPU bound, then the next action is to dig deeper and find where the application is spending its time waiting, but how? Philosophically, you should now have an ok understanding of what to be looking for, but what tools can you use to accomplish the task of measurement?&lt;/p&gt;

&lt;p&gt;We need to rewind a bit. A moment ago, I also made an assumption that you know how to find the CPU load of your application. That may not be the case.&lt;/p&gt;

&lt;p&gt;The easiest way to monitor basic resource information about your app including CPU, memory, disk usage, and more may come from your hosting vendor. My preferred hosting vendor, Heroku, displays all these kinds of metrics on a single page so I can assess system performance at a glance. Other vendors like Digital Ocean or AWS provide tools to help you see this information too.&lt;/p&gt;

&lt;p&gt;If your hosting vendor doesn't provide these tools, then you'll have to use other techniques. Presumably, if you're using a Virtual Private Server (VPS) for hosting, you have access to the server itself via ssh. On the server that's running your application, you can use a program like &lt;code&gt;top&lt;/code&gt;. This is a classic program for checking which processes are using the "top" amount of resources. &lt;code&gt;top&lt;/code&gt; will show the list of processes ordered by what is consuming the most CPU and will refresh the order of the list every second to provide a current snapshot in time. (&lt;strong&gt;Tip&lt;/strong&gt;: use &lt;code&gt;q&lt;/code&gt; to quit &lt;code&gt;top&lt;/code&gt; after you start it.)&lt;/p&gt;

&lt;p&gt;While &lt;code&gt;top&lt;/code&gt; is useful and gets the job done to learn about CPU usage, it's not exactly the friendliest tool out there. There are alternatives to &lt;code&gt;top&lt;/code&gt; that may offer a better user experience. I personally find &lt;code&gt;top&lt;/code&gt; sufficient, but I know &lt;code&gt;htop&lt;/code&gt; is a popular alternative.&lt;/p&gt;

&lt;p&gt;If you don't have tools from your hosting provider and don't want to use ssh to log into a server, there are other options to consider. Broadly, this other category of tools is called Application Performance Monitoring (APM). APM tools are vendors that will monitor your application (go figure!) if you install the tool along with your app. Application performance is very important to businesses, so the software industry is full of vendors to choose from with a wide range of features.&lt;/p&gt;

&lt;p&gt;To see what these tools can be like for free, you might want to check out &lt;a href="https://www.datadoghq.com/"&gt;Datadog&lt;/a&gt; which has a free tier (Datadog is not a sponsor, I've just used their service, enjoyed it, and know that it's free for a small number of servers). Other popular vendors include &lt;a href="https://scoutapm.com/"&gt;Scout APM&lt;/a&gt; and &lt;a href="https://newrelic.com/"&gt;New Relic&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Finally, we've reached a point where you can diagnose the performance constraints on your application using a wide variety of tools or services. Let's see how to fix problems you may be experiencing!&lt;/p&gt;

&lt;h2&gt;
  
  
  Do More
&lt;/h2&gt;

&lt;p&gt;We can address throughput and do more with a few different knobs.&lt;/p&gt;

&lt;p&gt;When thinking about doing more, try to think about the system in two different scaling dimensions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Horizontally&lt;/li&gt;
&lt;li&gt;Vertically&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Horizontal and vertical scaling are methods of describing &lt;em&gt;how&lt;/em&gt; to do more in software systems.&lt;/p&gt;

&lt;p&gt;Let's relate this to a silly example to give you a good intuitive feel for scaling. Imagine that you need to move large bags of dirt (approximately 40 lbs / 18 kg per bag) to plant a huge garden. The job is to unload the hundreds of bags from a delivery truck to your imaginary back yard. You enlist the help of your friends to get the job done.&lt;/p&gt;

&lt;p&gt;One strategy is to get your &lt;em&gt;strongest&lt;/em&gt; friends to help. Maybe there aren't as many of them, but their strength can make quick work of moving the bags. This is &lt;em&gt;vertical scaling&lt;/em&gt;. The additional power of your friends allows them to move the bags more easily than someone with an average or weaker build.&lt;/p&gt;

&lt;p&gt;Another strategy is to get &lt;em&gt;lots&lt;/em&gt; of friends to help. Maybe these friends can't move as many bags as the stronger ones, but many hands make light work. This is &lt;em&gt;horizontal scaling&lt;/em&gt;. The increased number of people allows the group to move more bags because more individuals can do the work simultaneously.&lt;/p&gt;

&lt;p&gt;We can apply this same thinking to computer systems.&lt;/p&gt;

&lt;h3&gt;
  
  
  Vertical Scaling
&lt;/h3&gt;

&lt;p&gt;To achieve vertical scaling, you would run your application on a more powerful computer. Cloud vendors give you all kinds of tools to do this. Picking a faster computer is naturally going to cost you more, so vendors make many options available (check out &lt;a href="https://aws.amazon.com/ec2/instance-types/"&gt;this page from AWS&lt;/a&gt; to see the dizzying array of options).&lt;/p&gt;

&lt;p&gt;When should you think about vertical scaling? One natural case is when your application is CPU bound. If the processor is struggling to process the requests from an application, a faster processor may help. With a higher clock speed from a faster individual CPU, a computer will be able to process an individual request faster.&lt;/p&gt;

&lt;p&gt;Moving to a larger computer is typically consider vertical scaling, but it may be possible to have horizontal effects by moving to a larger computer because of how modern computers are designed. These day, a larger computers typically come with a higher number of CPUs. Each individual CPU may be faster than a smaller computer configuration &lt;em&gt;and&lt;/em&gt; there will be more CPUs on the single machine. Because of this characteristic, you will likely need to change your application configuration to take advantage of the additional power.&lt;/p&gt;

&lt;p&gt;In the Understand Django deployment article, we discussed Gunicorn's &lt;code&gt;--workers&lt;/code&gt; flag. Recall that Python application servers like Gunicorn work by creating a main process and a set of worker processes. The main process will distribute incoming network connections to the worker processes to handle the actual traffic on your website. If you vertically scale the server machine from a size that has 1 CPU to a machine that has 2, 4, or more CPUs, and you don't change the number of workers, then you'll waste available CPU capacity and won't see most of the benefits from the upgrade in server size.&lt;/p&gt;

&lt;p&gt;If modern vertical scaling uses more CPUs when moving to a bigger machine, then what is horizontal scaling? The difference is primarily in the number of computers needed to do the scaling. Vertical scaling changes a single machine to achieve more throughput. Horizontal scaling pulls multiple machines into the equation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Horizontal Scaling
&lt;/h3&gt;

&lt;p&gt;Conceptually, how does horizontal scaling work? With the vertical scaling model, you can see a clear connection between users making a request to your website's domain and a single machine handling those requests (i.e., the main process from your application server distributes requests). With the horizontal model, we're now discussing multiple computers. How does a single domain name handle routing to multiple computers? With more computers!&lt;/p&gt;

&lt;p&gt;Like the main process that distributes requests, we need a central hub that is able to route traffic to the different machines in your horizontally scaled system. This hub is usually called a &lt;strong&gt;load balancer&lt;/strong&gt;. A load balancer can be used for multiple things. I see load balancers used primarily to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Route traffic to the different application servers in a system.&lt;/li&gt;
&lt;li&gt;Handle the TLS certificate management that makes HTTPS possible.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Since the load balancer doesn't do most of the actual work of processing a request, your system can increase its throughput by increasing the number of application servers. In this setup, each application server "thinks" that it is the main server that's handling requests. The load balancer behaves like a client that's making requests on behalf of the actual user. This kind of configuration is called a proxy setup.&lt;/p&gt;

&lt;p&gt;If you want to learn more about horizontal scaling with a load balancer, then I suggest you check out &lt;a href="https://www.nginx.com/"&gt;Nginx&lt;/a&gt; (pronounced "engine X"), &lt;a href="http://www.haproxy.org/"&gt;HAProxy&lt;/a&gt; (which stands for "high availability proxy"), or &lt;a href="https://docs.aws.amazon.com/elasticloadbalancing/latest/application/introduction.html"&gt;AWS ALBs&lt;/a&gt; (for "application load balancer"). These tools are commonly reached for and have a reputation for being strong load balancers.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's Better?
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;What are the tradeoffs between horizontal scaling and vertical scaling?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;When you add more pieces to a system, you're increasing the complexity of the system. Thus, vertical scaling can, at least initially, produce a design with lower operational complexity. Personally, if I ran a service on some VPS like Digital Ocean or AWS, I would probably reach for vertical scaling first. A bigger machine would allow me to use a higher number of concurrent worker processes to increase throughput, and I would avoid the complexity of deploying multiple application servers.&lt;/p&gt;

&lt;p&gt;In reality, I run my side projects on a Platform as a Service, Heroku. With my choice of Heroku, the service already includes a load balancer by default. This means that I can trivially scale horizontally by changing a setting in Heroku that will start multiple application servers.&lt;/p&gt;

&lt;p&gt;While vertical scaling may be a good fit if you don't have an existing load balancer, that scaling path does have downsides to consider.&lt;/p&gt;

&lt;p&gt;First, in a vertically scaled world, downtime on your server could mean downtime &lt;em&gt;for your service&lt;/em&gt;. Whether a site is reachable or not reachable is called "availability" by the software industry. If your entire site is tied to a large vertically scaled server, then it can act as a single point of failure if there is a problem.&lt;/p&gt;

&lt;p&gt;Secondly, a vertically scaled service may potentially have more cost for you. In my experience, most websites have high and low periods of usage throughout the day. For instance, my current employer is a US healthcare company that provides telemedicine visits for people that need to speak with a doctor virtually. When it's the middle of the night in the US, the site utilization is naturally lower as most people are sleeping.&lt;/p&gt;

&lt;p&gt;One common cost optimization is to use fewer computing resources during periods of lower utilization. On a vertically scaled service, it is harder to change computer sizes quickly. Thus, that computing resources usage is relatively fixed, even if no one is using your service. In contrast, a horizontally scaled service can be configured to use "auto-scaling."&lt;/p&gt;

&lt;p&gt;Auto-scaling is the idea that the infrastructure can be resized dynamically, depending on the use of the site. During periods of high activity, more computers can be added automatically to join the load balancer distribution and handle additional load. When activity dies down, these extra machines can be removed from use. This cost saving technique helps ensure that your system is only using what it needs.&lt;/p&gt;

&lt;p&gt;The truth is that if your system reaches a large enough size and scale, then picking horizontal or vertical scaling is a false choice. As a system matures and grows, you may need to have a mix of the two scaling types, so that your service has the characteristics that you need (like availability).&lt;/p&gt;

&lt;p&gt;I hope that I've helped equip you with some mental modeling tools. With these tools, you should have some idea of how to handle more traffic when your site becomes wildly popular. 😄&lt;/p&gt;

&lt;p&gt;In this section, we focused on increasing throughput by changing your service's infrastructure to handle more load. Now let's shift from the macro point of view to the micro view and talk about how to improve throughput by doing less.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do Less
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;How do you make your Django site do less work?&lt;/em&gt; We should always measure, but since I believe most websites are I/O bound, let's focus on techniques to improve in that dimension.&lt;/p&gt;

&lt;h3&gt;
  
  
  Optimizing Database Queries
&lt;/h3&gt;

&lt;p&gt;The most common performance problem that I've encountered with Django applications is the N+1 query bug (some people will describe it as the 1+N query bug for reasons that may become evident in a moment).&lt;/p&gt;

&lt;p&gt;The N+1 bug occurs when your application code calls the database in a loop. How many queries are in this made up example?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;application.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Movie&lt;/span&gt;

&lt;span class="n"&gt;movies&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Movie&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;movie&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;movies&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;movie&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;director&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's a bit of a trick question because you might have a custom manager (i.e., &lt;code&gt;objects&lt;/code&gt;), but, in the simplest scenario, there is one query to fetch the movies, and one query for each director.&lt;/p&gt;

&lt;p&gt;The reason for this behavior is that Django does a lazy evaluation of the movies &lt;code&gt;QuerySet&lt;/code&gt;. The ORM is not aware that it needs to do a join on the movie and director tables to fetch all the data. The first query on the movie table occurs when the iteration happens in the Python &lt;code&gt;for&lt;/code&gt; loop. When the &lt;code&gt;print&lt;/code&gt; function tries to access the &lt;code&gt;director&lt;/code&gt; foreign key, the ORM does not have the director information cached in Python memory for the query set. Django must then fetch the director data in another database query to display the director's name.&lt;/p&gt;

&lt;p&gt;This adds up to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1 query for the movies table&lt;/li&gt;
&lt;li&gt;N queries on the director table for each iteration through the &lt;code&gt;for&lt;/code&gt; loop&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Hence the name, "N+1" query bug.&lt;/p&gt;

&lt;p&gt;The reason that this is so bad is because calling the database is way slower than accessing data in Python memory. Also, this problem gets worse if there are more rows to iterate over (i.e., more movies to process and, thus, more directors to fetch individually).&lt;/p&gt;

&lt;p&gt;The way to fix this issue is to hint to Django that the code is going to access data from the deeper relationship. We can do that hinting to the ORM with &lt;code&gt;select_related&lt;/code&gt;. Let's see the example with this change.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;application.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Movie&lt;/span&gt;

&lt;span class="n"&gt;movies&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Movie&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;select_related&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"director"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nb"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;movie&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;movies&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;movie&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;director&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the reworked example, the ORM will "know" that it must fetch the director data. Because of this extra information, the framework will fetch from both the movie and director tables &lt;em&gt;in a single query&lt;/em&gt; when the &lt;code&gt;for&lt;/code&gt; loop iteration starts.&lt;/p&gt;

&lt;p&gt;Under the hood, Django performs a more complex &lt;code&gt;SELECT&lt;/code&gt; query that includes a join on the two tables. The database sends back all the data at once and Django caches the data in Python memory. Now, when execution reaches the &lt;code&gt;print&lt;/code&gt; line, the &lt;code&gt;director.name&lt;/code&gt; attribute can pull from memory instead of needing to trigger another database query.&lt;/p&gt;

&lt;p&gt;The performance savings here can be massive, especially if your code works with a lot of database rows at once.&lt;/p&gt;

&lt;p&gt;While &lt;code&gt;select_related&lt;/code&gt; is fantastic, it doesn't work for all scenarios. Other types of relationships like a many to many relationship can't be fetched in a single query. For those scenarios, you can reach for &lt;code&gt;prefetch_related&lt;/code&gt;. With this method, Django will issue a smaller number of queries (usually 1 per table) and blend the results together in memory. In practice, &lt;code&gt;prefetch_related&lt;/code&gt; operates very much like &lt;code&gt;select_related&lt;/code&gt; in most circumstances. Check out the Django docs to understand more.&lt;/p&gt;

&lt;h3&gt;
  
  
  Caching Expensive Work
&lt;/h3&gt;

&lt;p&gt;If you know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Execution will likely happen many times&lt;/li&gt;
&lt;li&gt;Is expensive to create, AND&lt;/li&gt;
&lt;li&gt;Won't need to change&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then you're looking at work that is a very good candidate to cache. With caching, Django can save the results of some expensive operation into a very fast caching tool and restore those results later.&lt;/p&gt;

&lt;p&gt;A good example of this might be a news site. A news site is very "read heavy," that is, users are more likely to use the site for viewing information than for writing and saving information to the site. A news site is also a good example because users will read the same article, and the content of that article is fixed in form.&lt;/p&gt;

&lt;p&gt;Django includes tools to make it simple to work with the cache to optimize content like our news site example.&lt;/p&gt;

&lt;p&gt;The simplest of these tools is the &lt;code&gt;cache_page&lt;/code&gt; decorator. This decorator can cache the results of an entire Django view for a period of time. When a page doesn't have any personalization, this can be a quick and effective way to serve HTML results from a view. You can find this decorator in &lt;code&gt;django.views.decorators.cache&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You may need a lower level of granularity than a whole page. For instance, your site might have some kind of logged in user and a customized navigation bar with a profile picture or something similar. In that scenario, you can't really cache the whole page and serve that to multiple users, because other users would see the customized navigation bar of the first user who made the request. If this is the kind of situation you're in, then the &lt;code&gt;cache&lt;/code&gt; template tag may be the best tool for you.&lt;/p&gt;

&lt;p&gt;Here's a template example of the &lt;code&gt;cache&lt;/code&gt; tag in use.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jinja"&gt;&lt;code&gt;&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="nv"&gt;load&lt;/span&gt; &lt;span class="nv"&gt;cache&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;

Hi &lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;user.username&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;, this part won't be cached.

&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="nv"&gt;cache&lt;/span&gt; &lt;span class="nv"&gt;600&lt;/span&gt; &lt;span class="nv"&gt;my_cache_key_name&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
    Everything inside of here will be cached.
    The first argument to `cache` is how long this should be cached
    in seconds. This cache fragment will cache for 10 minutes.
    Cached chunks need a key name to help the cache system
    find the right cache chunk.

    This cache example usage is a bit silly because this is static text
    and there is no expensive computation in this chunk.
&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="nv"&gt;endcache&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this scheme, any expensive computation that your template does will be cached. &lt;em&gt;Be mindful with this tag!&lt;/em&gt; The tag is useful if computation happens during rendering, but if you're do the evaluation and fetching &lt;em&gt;inside of your view&lt;/em&gt; instead of at template rendering time, then you're unlikely to get the benefits that you want.&lt;/p&gt;

&lt;p&gt;Finally, there is the option to use the cache interface directly. Here's the basic usage pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# application/views.py
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.core.cache&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;cache&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;application.complex&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;calculate_expensive_thing&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;some_view&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;expensive_result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"expensive_computation"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;expensive_result&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;expensive_result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;calculate_expensive_thing&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"expensive_computation"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expensive_result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Handle the rest of the view.
&lt;/span&gt;    &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the first request to this view, the &lt;code&gt;expensive_result&lt;/code&gt; won't be in the cache, so the view will calculate the result and save it to the cache. On subsequent requests, the expensive result can be pulled from the cache. In this example, I'm using the default timeout for the cache, but you can control the timeout values when you need more control. The cache system has plenty of other cool features, so check it out in the docs.&lt;/p&gt;

&lt;p&gt;As fair warning, caching often requires more tools and configuration. Django works with very popular cache tools like Redis and Memcached, but you'll have to configure one of the tools on your own. The Django documentation will help you, but be prepared for more work on your part.&lt;/p&gt;

&lt;p&gt;Database optimization and caching are go to techniques for optimization. When you're optimizing, how do you know that you're doing it right? What gains are you getting? Let's look at some tools next that will let you answer those questions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tools To Measure Change
&lt;/h2&gt;

&lt;p&gt;We'll look at tools at an increasing level of complexity. This first tool is one that is massively useful while developing in Django. The other tools are more general purpose tools, but they still are worth knowing about, so that you'll know when to reach for them.&lt;/p&gt;

&lt;p&gt;Each of these tools helps you get real performance data. By measuring the before and after of your changes, you can learn if the changes are actually producing the gains that you expect or hope to achieve.&lt;/p&gt;

&lt;h3&gt;
  
  
  Django Debug Toolbar
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://django-debug-toolbar.readthedocs.io/en/latest/index.html"&gt;Django Debug Toolbar&lt;/a&gt; is a critical tool that I add to my projects. The toolbar acts as an overlay on your site that opens to give you a tray of different categories of diagnostic information about your views.&lt;/p&gt;

&lt;p&gt;Each category of information is grouped into a "panel," and you can select between the different panels to dig up information that will assist you while doing optimization work.&lt;/p&gt;

&lt;p&gt;You'll find panels like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SQL&lt;/li&gt;
&lt;li&gt;Templates&lt;/li&gt;
&lt;li&gt;Request&lt;/li&gt;
&lt;li&gt;Time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The SQL panel is where I spend nearly all of my time when optimizing. This panel will display all the queries that a page requests. For each query, you can find what code triggered the database query and you even get the exact SQL &lt;code&gt;SELECT&lt;/code&gt;. You can also get an &lt;code&gt;EXPLAIN&lt;/code&gt; about a query if you really need the gory details of what the database is doing.&lt;/p&gt;

&lt;p&gt;With a little bit of eye training, you'll learn to spot N+1 query bugs because you can see certain queries repeated over and over and "cascading" like a waterfall.&lt;/p&gt;

&lt;p&gt;I'll often test with the debug toolbar when I'm trying to sprinkle in &lt;code&gt;select_related&lt;/code&gt; to visually confirm that I've reduced the query count on a page. The debug toolbar is open source and is a great free resource. The toolbar is totally worth the investment of configuring it for your next Django project.&lt;/p&gt;

&lt;h3&gt;
  
  
  hey / ab
&lt;/h3&gt;

&lt;p&gt;There are two tools that are very similar that I use when I need to get a crude measure of the performance of a site. These tools are &lt;a href="https://github.com/rakyll/hey"&gt;hey&lt;/a&gt; and &lt;a href="https://httpd.apache.org/docs/2.4/programs/ab.html"&gt;ab&lt;/a&gt; (Apache Bench). Both of these tools are &lt;em&gt;load generators&lt;/em&gt; that are meant to benchmark a site's basic performance characteristics.&lt;/p&gt;

&lt;p&gt;In practice, I prefer &lt;code&gt;hey&lt;/code&gt;, but I mention &lt;code&gt;ab&lt;/code&gt; because it is a well known tool in this space that you are likely to encounter if you research this load generator topic.&lt;/p&gt;

&lt;p&gt;Operating this kind of tool is trivial:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;hey https://www.example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, hey will try to open up a large number of concurrent connections to the URL and make a bunch of requests. When the tool is done, it will report how many of the requests were successful and some related timing information and statistics. Using a load generator like this lets you synthesize traffic to learn how your site is going to perform.&lt;/p&gt;

&lt;p&gt;I'd suggest you be careful where you tell these tools to operate. If you're not careful, &lt;em&gt;you could cause a Denial of Service attack on your own machines.&lt;/em&gt; The flood of requests might make your site unavailable to other users by consuming all your server's resources. Think twice before pointing this at your live site!&lt;/p&gt;

&lt;h3&gt;
  
  
  Locust
&lt;/h3&gt;

&lt;p&gt;The previous load generator tools that I mentioned act as somewhat crude measurements because you're limited to testing a single URL at a time. What should you do if you need to simulate traffic that matches real user usage patterns? Enter &lt;a href="https://locust.io/"&gt;Locust&lt;/a&gt;. Locust is not a tool that I would reach for casually, but it is super cool and worth knowing about.&lt;/p&gt;

&lt;p&gt;The goal of Locust is to do load testing on your site in a realistic way. This means that it's your job to model the expected behavior of your users in a machine understandable way. If you know your users well (and I hope you do), then you can imagine the flows that they might follow when using your site.&lt;/p&gt;

&lt;p&gt;In Locust, you codify the behavior patterns that you care about, then run Locust to simulate a large number of users that will act like you expect (with randomness to boot to really make the test like reality).&lt;/p&gt;

&lt;p&gt;Advanced load testing is something you may never need for your site, but it's pretty cool to know that Python has you covered if you need to understand performance and the limits of your site at that deep level.&lt;/p&gt;

&lt;h3&gt;
  
  
  Application Performance Monitoring (APM)
&lt;/h3&gt;

&lt;p&gt;Earlier in this article, I mentioned that Application Performance Monitoring tools can show you CPU and memory utilization of your site. That's usually just the tip of the iceberg.&lt;/p&gt;

&lt;p&gt;An APM tool often goes far beyond hardware resource measurement. I like to think of APMs as a supercharged version of the debug toolbar.&lt;/p&gt;

&lt;p&gt;First, an APM is used on live sites typically. The tool will collect data about real requests. This lets you learn about the &lt;em&gt;real&lt;/em&gt; performance problems on the site that affect &lt;em&gt;real&lt;/em&gt; users.&lt;/p&gt;

&lt;p&gt;For instance, New Relic will collect data on slow requests into "traces." These traces are aggregated into a set to show you which pages on your site are the worst performers. You can drill into that list, view an individual trace, and investigate the problem.&lt;/p&gt;

&lt;p&gt;Maybe you've got an N+1 bug. Maybe one of your database tables is missing an index on an important field, and the database is scanning too many records during &lt;code&gt;SELECT&lt;/code&gt; statements. These traces (or whatever they are called in other services) help you prioritize what to fix.&lt;/p&gt;

&lt;p&gt;In fact, an APM highlights the true value of measurement. If I can leave you with a parting thought about optimization, think about this: &lt;strong&gt;&lt;em&gt;optimize where it counts&lt;/em&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Here's a simple thought experiment to illustrate what I mean. You have an idealized system that does two things repeatedly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One task (A) is 90% of all activity on the site.&lt;/li&gt;
&lt;li&gt;The other task (B) is the remaining 10%.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you have a to pick a target to try to optimize because your system performance is inadequate, which one do you pick?&lt;/p&gt;

&lt;p&gt;Let's assume that you know an optimization for each type of task that could cause the task to execute in 50% of the time. If implementing each optimization is the same level of effort, then there is a clear winner as to which task you should optimize. You could either:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Optimize A for 90% * 50% for a total system saving of 45%.&lt;/li&gt;
&lt;li&gt;Optimize B for 10% * 50% for a total system saving of 5%.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In most circumstances, spend your optimization effort on the area that will have outsized impact (i.e., pick task A as much as you can). Sometimes the hard part is figuring out which task is A and which task is B. Monitoring tools like an APM can help you see where the worst offenders are so you can focus your limited time in the right spot.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;In this article, we looked into making Django apps go fast. We saw:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A mental model for thinking about performance optimization&lt;/li&gt;
&lt;li&gt;Different types of performance bottlenecks&lt;/li&gt;
&lt;li&gt;How to get your system to do more by either horizontal or vertical scaling&lt;/li&gt;
&lt;li&gt;How to get your app to do less work by optimizing database queries and caching&lt;/li&gt;
&lt;li&gt;Tools to aid you in all of this optimization work&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the next article, we'll look into security. You'll learn about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How Django helps you be more secure with some of it design features&lt;/li&gt;
&lt;li&gt;What those different warnings from &lt;code&gt;./manage.py check --deploy&lt;/code&gt; mean&lt;/li&gt;
&lt;li&gt;Fundamental things you should consider to help keep your site secure&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you'd like to follow along with the series, please feel free to sign up for my newsletter where I announce all of my new content. If you have other questions, you can reach me online on Twitter where I am &lt;a href="https://twitter.com/mblayman"&gt;@mblayman&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>python</category>
      <category>django</category>
      <category>performance</category>
    </item>
    <item>
      <title>Understand Django: Command Your App</title>
      <dc:creator>Matt Layman</dc:creator>
      <pubDate>Thu, 04 Nov 2021 14:35:19 +0000</pubDate>
      <link>https://dev.to/mblayman/command-your-app-32ad</link>
      <guid>https://dev.to/mblayman/command-your-app-32ad</guid>
      <description>&lt;p&gt;In the last &lt;a href="https://www.mattlayman.com/understand-django/"&gt;Understand Django&lt;/a&gt; article, we dug into file management. We saw how Django handles user uploaded files and how to deal with them safely.&lt;/p&gt;

&lt;p&gt;With this article, you'll learn about commands. Commands are the way to execute scripts that interact with your Django app. We'll see built-in commands and how to build your own.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Commands?
&lt;/h2&gt;

&lt;p&gt;Django makes it possible to run code from a terminal with &lt;code&gt;./manage.py&lt;/code&gt;, but why is this helpful or needed? Consider this script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# product_stat.py
&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;application.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Product&lt;/span&gt;

&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;which you could try running with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;python product_stat.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The problem with this script is that Django is not ready to run yet. If you tried to run this kind of code, you would get an &lt;code&gt;ImproperlyConfigured&lt;/code&gt; exception. There are a couple of modifications that you could make to get the script to run.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Call &lt;code&gt;django.setup()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Specify the &lt;code&gt;DJANGO_SETTINGS_MODULE&lt;/code&gt;.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# product_stat.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;django&lt;/span&gt;

&lt;span class="n"&gt;django&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;application.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Product&lt;/span&gt;

&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that &lt;code&gt;django.setup()&lt;/code&gt; must be before your Django related imports (like the &lt;code&gt;Product&lt;/code&gt; model in this example). Now the script can run if you supply where the settings are located too.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ DJANGO_SETTING_MODULE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;project.settings python product_stat.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This arrangement is less than ideal, but why else might we want a way to run commands through Django?&lt;/p&gt;

&lt;p&gt;Try running &lt;code&gt;./manage.py -h&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;What you'll likely see is more commands than what Django provides alone. This is where we begin to see more value from the command system. Because Django provides a standard way to run scripts, other Django applications can bundle useful commands and make them easily accessible to you.&lt;/p&gt;

&lt;p&gt;Now that you've had a chance to see why commands exist to run scripts for Django apps, let's back up and see &lt;em&gt;what&lt;/em&gt; commands are.&lt;/p&gt;

&lt;h2&gt;
  
  
  We Hereby Command
&lt;/h2&gt;

&lt;p&gt;Django gives us a tool to run commands before we've even started our project. That tool is the &lt;code&gt;django-admin&lt;/code&gt; script. We saw it all the way back in the first article where I provided a short set of setup instructions to get you started if you've never used Django before.&lt;/p&gt;

&lt;p&gt;After you've started a project, your code will have a &lt;code&gt;manage.py&lt;/code&gt; file, and the commands you've seen in most articles are in the form of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;./manage.py some_command
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What's the difference between &lt;code&gt;django-admin&lt;/code&gt; and &lt;code&gt;manage.py&lt;/code&gt;? In truth, &lt;strong&gt;not much!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;django-admin&lt;/code&gt; comes from Django's Python packaging. In Python packages, package developers can create scripts by defining an entry point in the packaging configuration. In Django, this configuration looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[options.entry_points]&lt;/span&gt;
&lt;span class="py"&gt;console_scripts&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="py"&gt;django-admin&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;django.core.management:execute_from_command_line&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Meanwhile, the entire &lt;code&gt;manage.py&lt;/code&gt; of one of my projects is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/usr/bin/env python
&lt;/span&gt;&lt;span class="s"&gt;"""Django's command-line utility for administrative tasks."""&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;sys&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setdefault&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'DJANGO_SETTINGS_MODULE'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'project.settings'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.core.management&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;execute_from_command_line&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;ImportError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nb"&gt;ImportError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"Couldn't import Django. Are you sure it's installed and "&lt;/span&gt;
            &lt;span class="s"&gt;"available on your PYTHONPATH environment variable? Did you "&lt;/span&gt;
            &lt;span class="s"&gt;"forget to activate a virtual environment?"&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;exc&lt;/span&gt;
    &lt;span class="n"&gt;execute_from_command_line&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;'__main__'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you look closely, you can see that the different scripts are both ways to invoke the &lt;code&gt;execute_from_command_line&lt;/code&gt; function to run a command. The primary difference is that the latter script will attempt to set the &lt;code&gt;DJANGO_SETTINGS_MODULE&lt;/code&gt; environment variable automatically. Since Django needs to have &lt;code&gt;DJANGO_SETTINGS_MODULE&lt;/code&gt; defined for most commands (note: &lt;code&gt;startproject&lt;/code&gt; does &lt;em&gt;not&lt;/em&gt; require that variable), &lt;code&gt;manage.py&lt;/code&gt; is a more convenient way to run commands.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;execute_from_command_line&lt;/code&gt; is able to present what commands are available to a project, whether a command comes from Django itself, an installed app, or is a custom command that you created yourself. How are the commands discovered? &lt;em&gt;The command system does discovery by following some packaging conventions.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Let's say your project has an app named &lt;code&gt;application&lt;/code&gt;. Django can find the command if you have the following packaging structure.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;application
├── __init__.py
├── management
│   ├── __init__.py
│   └── commands
│       ├── __init__.py
│       └── custom_command.py
├── models.py
└── views.py
... Other typical Django app files
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this structure, you could run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;./manage.py custom_command
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Django will create a command for a module found in &lt;code&gt;&amp;lt;app&amp;gt;/management/commands/&amp;lt;command name&amp;gt;.py&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Don't forget the &lt;code&gt;__init__.py&lt;/code&gt; files! Django can only discover the commands if &lt;code&gt;management&lt;/code&gt; and &lt;code&gt;commands&lt;/code&gt; are proper Python package directories.&lt;/li&gt;
&lt;li&gt;The example uses &lt;code&gt;custom_command&lt;/code&gt;, but you can name your command with whatever valid Python module name that you want.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Unfortunately, we can't slap some Python code into &lt;code&gt;custom_command.py&lt;/code&gt; and assume that Django will know how to run it. Within the &lt;code&gt;custom_command.py&lt;/code&gt; module, Django needs to find a &lt;code&gt;Command&lt;/code&gt; class that subclasses a &lt;code&gt;BaseCommand&lt;/code&gt; class that is provided by the framework. Django requires this structure to give command authors a consistent way to access features of the command system.&lt;/p&gt;

&lt;p&gt;With the &lt;code&gt;Command&lt;/code&gt; class, you can add a &lt;code&gt;help&lt;/code&gt; class attribute. Adding help can gives users a description of what your command does when running &lt;code&gt;./manage.py custom_command -h&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;Command&lt;/code&gt; class will also help you with handling arguments. If your command needs to work with user input, you'll need to parse that data. Thankfully, the class integrates with Python's built-in &lt;code&gt;argparse&lt;/code&gt; module. By including an &lt;code&gt;add_arguments&lt;/code&gt; method, a command can parse the data and pass the results to the command's handler method in a structured way. If you've had to write Python scripts before, then you may understand how much time this kind of parsing can save you (and for those who haven't, the answer is "a lot of time!").&lt;/p&gt;

&lt;p&gt;Other smaller features exist within the &lt;code&gt;Command&lt;/code&gt; class too. Perhaps you only want your command to run if your project has satisified certain pre-conditions. Commands can use the &lt;code&gt;requires_migration_checks&lt;/code&gt; or &lt;code&gt;requires_system_checks&lt;/code&gt; to ensure that the system is in the correct state before running.&lt;/p&gt;

&lt;p&gt;I hope it's clear that the goal of the &lt;code&gt;Command&lt;/code&gt; class is to help you with common actions that many commands will need to use. There is a small API to learn, but the system is a boon to making scripts quickly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Command By Example
&lt;/h2&gt;

&lt;p&gt;Let's consider a powerful use case to see a command in action. When you initially start a Django app, all of your app's interaction will probably be through web pages. After all, you were trying to use Django to make a web app, right? What do you when you need to do something that doesn't involve a browser?&lt;/p&gt;

&lt;p&gt;This kind of work for your app is often considered &lt;em&gt;background&lt;/em&gt; work. Background work is a pretty deep topic and will often involve special background task software like &lt;a href="https://docs.celeryproject.org/en/stable/getting-started/introduction.html"&gt;Celery&lt;/a&gt;. When your app is an early stage, Celery or similar software can be overkill and far more than you need.&lt;/p&gt;

&lt;p&gt;A simpler alternative for some background tasks could be a command paired with a scheduling tool like &lt;a href="https://en.wikipedia.org/wiki/Cron"&gt;cron&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;On one of my projects, I offer free trials for accounts. After 60 days, the free trial ends and users either need to pay for the service or discontinue using it. By using a command and pairing it with the &lt;a href="https://devcenter.heroku.com/articles/scheduler"&gt;Heroku Scheduler&lt;/a&gt;, I can move accounts from their trial status to expired with a daily check.&lt;/p&gt;

&lt;p&gt;The following code is very close to what my &lt;code&gt;expire_trials&lt;/code&gt; command looks like in my app. I've simplified things a bit, so that you can ignore the details that are specific to my service.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# application/management/commands/expire_trials.py
&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;datetime&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.core.management.base&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BaseCommand&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.utils&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;timezone&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;application.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Account&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseCommand&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;help&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Expire any accounts that are TRIALING beyond the trial days limit"&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Search for old trial accounts..."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;# Give an extra day to be gracious and avoid customer complaints.
&lt;/span&gt;        &lt;span class="n"&gt;cutoff_days&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;61&lt;/span&gt;
        &lt;span class="n"&gt;trial_cutoff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;days&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;cutoff_days&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;expired_trials&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TRIALING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;created__lt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;trial_cutoff&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;expired_trials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TRIAL_EXPIRED&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;"Expired &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; trial(s)"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I configured the scheduler to run &lt;code&gt;python manage.py expire_trials&lt;/code&gt; every day in the early morning. The command checks the current time and looks for &lt;code&gt;Account&lt;/code&gt; records in the trialing state that were created before the cutoff time. From that query set, the affected accounts are set to the expired account state.&lt;/p&gt;

&lt;p&gt;How can you test this command? There are a couple of approaches you can take when testing a command.&lt;/p&gt;

&lt;p&gt;If you need to simulate calling the command with command line arguments, then you can use &lt;code&gt;call_command&lt;/code&gt; from &lt;code&gt;django.core.management&lt;/code&gt;. Since the example command doesn't require arguments, I didn't take that approach.&lt;/p&gt;

&lt;p&gt;Generally, my preference is to create a command object and invoke the &lt;code&gt;handle&lt;/code&gt; method directly. In my example above, you can see that the command uses &lt;code&gt;self.stdout&lt;/code&gt; instead of calling &lt;code&gt;print&lt;/code&gt;. Django does this so that you could check your output if desired.&lt;/p&gt;

&lt;p&gt;Here is a test for this command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# application/tests/test_commands.py
&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;io&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;StringIO&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;application.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Account&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;application.tests.factories&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AccountFactory&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_expires_trials&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="s"&gt;"""Old trials are marked as expired."""&lt;/span&gt;
    &lt;span class="n"&gt;stdout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;StringIO&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;account&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AccountFactory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TRIALING&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;created&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;days&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;65&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;command&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;refresh_from_db&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;Account&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TRIAL_EXPIRED&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="s"&gt;"Expired 1 trial(s)"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getvalue&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this test, I constructed a command instance and checked the account state after the command invocation. Also, observe that the &lt;code&gt;StringIO&lt;/code&gt; instance is injected into the &lt;code&gt;Command&lt;/code&gt; constructor. By building the command this way, checking the output becomes a very achievable task via the &lt;code&gt;getvalue&lt;/code&gt; method.&lt;/p&gt;

&lt;p&gt;Overall, this scheme of making a command and running it on a schedule avoids all the work of setting up a background worker process. I've been extremely satisfied with how this technique has worked for me, and I think it's a great pattern when your app doesn't have to do a lot of complex background processing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Useful Commands
&lt;/h2&gt;

&lt;p&gt;Django is full of &lt;a href="https://docs.djangoproject.com/en/3.2/ref/django-admin/"&gt;useful commands&lt;/a&gt; that you can use for all kinds of purposes. Thus far in this series, we've discussed a bunch of them, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;check&lt;/code&gt; - Checks that your project is in good shape.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;collectstatic&lt;/code&gt; - Collects static files into a single directory.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;createsuperuser&lt;/code&gt; - Creates a super user record.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;makemigrations&lt;/code&gt; - Makes new migration files based on model changes.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;migrate&lt;/code&gt; - Runs any unapplied migrations to your database.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;runserver&lt;/code&gt; - Runs a development web server to check your app.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;shell&lt;/code&gt; - Starts a Django shell that allows you to use Django code on the command line.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;startapp&lt;/code&gt; - Makes a new Django app from a template.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;startproject&lt;/code&gt; - Makes a new Django project from a template.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;test&lt;/code&gt; - Executes tests that checks the validity of your app.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is a sampling of other commands that I find useful when working with Django projects.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;dbshell&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;dbshell&lt;/code&gt; command starts a different kind of shell. The shell is a database program that will connect to the same database that your Django app uses. This shell will vary based on your choice of database.&lt;/p&gt;

&lt;p&gt;For instance, when using PostgreSQL, &lt;code&gt;./manage.py dbshell&lt;/code&gt; will start &lt;code&gt;psql&lt;/code&gt;. From this shell, you can execute SQL statements directly to inspect the state of your database. I don't reach for this command often, but I find it very useful to connect to my database without having to remember database credentials.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;showmigrations&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;showmigrations&lt;/code&gt; command has a simple job. The command shows all the migrations for each Django app in your project. Next to each migration is an indicator of whether the migration is applied to your database.&lt;/p&gt;

&lt;p&gt;Here is an example of the &lt;code&gt;users&lt;/code&gt; app from one of my Django projects:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;./manage.py showmigrations &lt;span class="nb"&gt;users
users&lt;/span&gt;
 &lt;span class="o"&gt;[&lt;/span&gt;X] 0001_initial
 &lt;span class="o"&gt;[&lt;/span&gt;X] 0002_first_name_to_150_max
 &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; 0003_profile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In my real project, I've applied all the migrations, but for this example, I'm showing the third migration as it would appear if the migration wasn't applied yet.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;showmigrations&lt;/code&gt; is a good way to show the state of your database from Django's point of view.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;sqlmigrate&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;sqlmigrate&lt;/code&gt; command is &lt;em&gt;very&lt;/em&gt; handy. The command will show you what SQL statements Django would run for an individual migration file.&lt;/p&gt;

&lt;p&gt;Let's see an example. In Django 3.1, the team changed the &lt;code&gt;AbstractUser&lt;/code&gt; model so that the &lt;code&gt;first_name&lt;/code&gt; field could have a maximum length of 150 characters. Anyone using the &lt;code&gt;AbstractUser&lt;/code&gt; model (which includes me) had to generate a migration to apply that change.&lt;/p&gt;

&lt;p&gt;From my &lt;code&gt;showmigrations&lt;/code&gt; output above, you can see that the second migration of my &lt;code&gt;users&lt;/code&gt; app applied this particular framework change.&lt;/p&gt;

&lt;p&gt;To see the Postgres SQL statements that made the change, I can run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;./manage.py sqlmigrate &lt;span class="nb"&gt;users &lt;/span&gt;0002
BEGIN&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nt"&gt;--&lt;/span&gt;
&lt;span class="nt"&gt;--&lt;/span&gt; Alter field first_name on user
&lt;span class="nt"&gt;--&lt;/span&gt;
ALTER TABLE &lt;span class="s2"&gt;"users_user"&lt;/span&gt; ALTER COLUMN &lt;span class="s2"&gt;"first_name"&lt;/span&gt; TYPE varchar&lt;span class="o"&gt;(&lt;/span&gt;150&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
COMMIT&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From this, we can tell that Postgres executed an &lt;code&gt;ALTER COLUMN&lt;/code&gt; &lt;a href="https://en.wikipedia.org/wiki/Data_definition_language"&gt;DDL&lt;/a&gt; statement to modify the length of the &lt;code&gt;first_name&lt;/code&gt; field.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;squashmigrations&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Django migrations are a stack of separate database changes that produce a final desired schema state in your database. Over time, your Django apps will accumulate migration files, but those files have a shelf life. The &lt;code&gt;squashmigrations&lt;/code&gt; command is designed to let you tidy up an app's set of migration files.&lt;/p&gt;

&lt;p&gt;By running &lt;code&gt;squashmigrations&lt;/code&gt;, you can condense an app's migrations into a significantly smaller number. The reduced migrations can accurately represent your database schema, and make it easier to reason about what changes happened in the app's history. As a side benefit, migration squashing can make Django's migration handling faster because Django gets to process fewer files.&lt;/p&gt;

&lt;h2&gt;
  
  
  Even More Useful Commands
&lt;/h2&gt;

&lt;p&gt;The commands above come with the standard Django install. There's even more cool stuff out there to help with your project development!&lt;/p&gt;

&lt;p&gt;A package that I often reach for with my Django projects is the &lt;a href="https://django-extensions.readthedocs.io/en/latest/index.html"&gt;django-extensions&lt;/a&gt; package. This package is fully of goodies, including some great optional commands that you can use!&lt;/p&gt;

&lt;p&gt;A couple of my favorites include:&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;shell_plus&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;How often do you fire up a Django shell, import a model, then do some ORM queries to see the current state of the database? This is something I do &lt;em&gt;quite&lt;/em&gt; often.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;shell_plus&lt;/code&gt; command is like the regular shell, but the command will import all your models &lt;em&gt;automatically&lt;/em&gt;. For the five extra characters of &lt;code&gt;_plus&lt;/code&gt;, you can save your fingers a lot of typing to import your models and get directly to whatever you needed the shell for.&lt;/p&gt;

&lt;p&gt;The command will also import some commonly used Django functions and features like &lt;code&gt;reverse&lt;/code&gt;, &lt;code&gt;settings,&lt;/code&gt; &lt;code&gt;timezone&lt;/code&gt;, and more.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;graph_models&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;When I'm live streaming my side projects on &lt;a href="https://www.twitch.tv/mblayman"&gt;my Twitch channel&lt;/a&gt;, I will often want to show the model relationships of my Django project. With the &lt;code&gt;graph_models&lt;/code&gt; command, I can create an image of all my models and how those models relate to each other (using UML syntax). This is a great way to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Remind myself of the data modeling choices in my apps.&lt;/li&gt;
&lt;li&gt;Orient others to what I'm doing with my project.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This particular command requires some extra setup to install the right tools to create images, but the setup is manageable and the results are worth it.&lt;/p&gt;

&lt;p&gt;Aside from &lt;code&gt;shell_plus&lt;/code&gt; and &lt;code&gt;graph_models&lt;/code&gt;, there are 20 other commands that you can use that may be very useful to you. You should definitely check out django-extensions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;In this article, you saw Django commands. We covered:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Why commands exist in the Django framework&lt;/li&gt;
&lt;li&gt;How commands work&lt;/li&gt;
&lt;li&gt;How to create your own custom command and how to test it&lt;/li&gt;
&lt;li&gt;Useful commands from the core framework and the django-extensions package&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the next article, I'm going to describe internationalization. Internationalization is the process of making your app work for multiple spoken languages. You'll learn about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Enabling internationalization for your app&lt;/li&gt;
&lt;li&gt;Tools to help manage an internationalized app&lt;/li&gt;
&lt;li&gt;Extra considerations like handling time formatting&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you'd like to follow along with the series, please feel free to sign up for my newsletter where I announce all of my new content. If you have other questions, you can reach me online on Twitter where I am &lt;a href="https://twitter.com/mblayman"&gt;@mblayman&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>python</category>
      <category>django</category>
      <category>commands</category>
    </item>
    <item>
      <title>Understand Django: User File Use</title>
      <dc:creator>Matt Layman</dc:creator>
      <pubDate>Tue, 14 Sep 2021 14:47:30 +0000</pubDate>
      <link>https://dev.to/mblayman/understand-django-user-file-use-1am5</link>
      <guid>https://dev.to/mblayman/understand-django-user-file-use-1am5</guid>
      <description>&lt;p&gt;In the last &lt;a href="https://www.mattlayman.com/understand-django/"&gt;Understand Django&lt;/a&gt; article, you learned about Django settings and how to manage the configuration of your application. We also looked at tools to help you to be extra effective with settings.&lt;/p&gt;

&lt;p&gt;With this article, we're going to dig into file management. Unlike the static files that you create for the app yourself, you may want your app to accept files from your users. Profile pictures are a good example of user files. You'll see how Django handles those kinds of files and how to deal with them safely.&lt;/p&gt;

&lt;h2&gt;
  
  
  Files In Django Models
&lt;/h2&gt;

&lt;p&gt;As we saw in the models article, model fields in a Django model map to a column in a database table. When you want to access the &lt;em&gt;data&lt;/em&gt; for a model instance, Django will pull the data from a database row.&lt;/p&gt;

&lt;p&gt;Dealing with files in models is a bit different. While it &lt;em&gt;is&lt;/em&gt; possible to store file data directly in a database, you won't see that happen often. The reason is that storing the data in the database usually affects the performance of the database, especially with a large number of files.&lt;/p&gt;

&lt;p&gt;Instead, a common pattern in database usage is to store files separately from the database itself. Within the database, a column would store some kind of &lt;em&gt;reference&lt;/em&gt; to the stored file like a path if files are stored on a filesystem. &lt;strong&gt;This is the approach that Django takes with files.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Now that you know that Django takes this approach, you can remember:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Django models hold the &lt;em&gt;reference&lt;/em&gt; to a file (e.g., a file path)&lt;/li&gt;
&lt;li&gt;The file &lt;em&gt;data&lt;/em&gt; (i.e., the file itself) is stored somewhere else.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The "somewhere else" is called the "file storage," and we'll discuss storage in more depth in the next section.&lt;/p&gt;

&lt;p&gt;Let's focus on the first item. What do you use to reference the files? Like all other model data, we'll use a field! Django includes two fields that help with file management:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;FileField&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ImageField&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;FileField&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;What if you want to store a profile picture? You might do something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# application/models.py
&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Profile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;picture&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FileField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="c1"&gt;# Other fields like a OneToOneKey to User ...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the most basic version of using file fields. We can use this model very directly with a Django shell to illustrate file management.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="err"&gt;$&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;manage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt; &lt;span class="n"&gt;shell&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.core.files&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;File&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;application.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Profile&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'/Users/matt/path/to/image.png'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;profile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Profile&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;picture&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'my-image.png'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, I'm creating a profile instance manually. There are a few interesting notes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;File&lt;/code&gt; class is an important wrapper that Django uses to make Python file objects (i.e., the value returned from &lt;code&gt;open&lt;/code&gt;) work with the storage system.&lt;/li&gt;
&lt;li&gt;The name &lt;code&gt;image.png&lt;/code&gt; and &lt;code&gt;my-image.png&lt;/code&gt; do not have to match. Django can store the content of &lt;code&gt;image.png&lt;/code&gt; and use &lt;code&gt;my-image.png&lt;/code&gt; as the name to reference within the storage system.&lt;/li&gt;
&lt;li&gt;Saving the picture will automatically save the parent model instance by default.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;More often than not, you won't need to use these interfaces directly because Django has form fields and other tools that manage much of this for you.&lt;/p&gt;

&lt;p&gt;The current model example raises questions.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Where does that data go?&lt;/li&gt;
&lt;li&gt;What if we have a name conflict between two files like "&lt;code&gt;my-image.png&lt;/code&gt;"?&lt;/li&gt;
&lt;li&gt;What happens if we try to save something that isn't an image?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If we make no changes to the current setup, the data will go into the root of the media file storage. This will lead to a mess if you're trying to track many file fields, but we can fix this with the &lt;code&gt;upload_to&lt;/code&gt; field keyword argument. The simplest version of &lt;code&gt;upload_to&lt;/code&gt; can take a string that storage will use as a directory prefix to scope content into a different area.&lt;/p&gt;

&lt;p&gt;We're still left with potentially conflicting filenames. Thankfully, &lt;code&gt;upload_to&lt;/code&gt; can also accept a callable that gives us a chance to fix that issue. Let's rework the example.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# application/models.py
&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;uuid&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;profile_pic_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"profile_pics/{}{}"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uuid4&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;suffix&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Profile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;picture&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FileField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;upload_to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;profile_pic_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# Other fields like a OneToOneKey to User ...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this new version of the profile model, all of the images will be stored in a &lt;code&gt;profile_pics&lt;/code&gt; path within the file storage.&lt;/p&gt;

&lt;p&gt;This version also solves the duplicate filename problem. &lt;code&gt;profile_pic_path&lt;/code&gt; ignores most of the original filename provided. If two users both happen to upload &lt;code&gt;profile-pic.jpg&lt;/code&gt;, &lt;code&gt;profile_pic_path&lt;/code&gt; will assign those images random IDs and ignore the &lt;code&gt;profile-pic&lt;/code&gt; part of the filename.&lt;/p&gt;

&lt;p&gt;You can see that the function calls &lt;code&gt;uuid4()&lt;/code&gt;. These are effectively random IDs called &lt;a href="https://en.wikipedia.org/wiki/Universally_unique_identifier#Version_4_(random)"&gt;Universally Unique Identifiers (UUID)&lt;/a&gt;. UUIDs are likely something that you've seen before if you've worked with computers long enough, even if you didn't know their name. An example UUID would be &lt;code&gt;76ee4ae4-8659-4b50-a04f-e222df9a656a&lt;/code&gt;. In the storage area, you might find a file stored as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;profile_pics/76ee4ae4-8659-4b50-a04f-e222df9a656a.jpg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each call to &lt;code&gt;uuid4()&lt;/code&gt; is nearly certain to generate a unique value. Because of this feature, we can avoid filename conflicts by storing profile pictures with a unique name.&lt;/p&gt;

&lt;p&gt;There's one more problem to fix in this example. How do we know that a user provided a valid image file? This is important to check, because we want to avoid storing malicious files that bad actors might upload to our apps.&lt;/p&gt;

&lt;p&gt;This is where the &lt;code&gt;ImageField&lt;/code&gt; has value. This field type contains extra validation logic that can check the &lt;em&gt;content&lt;/em&gt; of the file to check that the file is, in fact, an image. To use &lt;code&gt;ImageField&lt;/code&gt;, you'll need to install the &lt;a href="https://pillow.readthedocs.io/en/latest/"&gt;Pillow&lt;/a&gt; library. Pillow is a package that let's Python work with image data.&lt;/p&gt;

&lt;p&gt;Our final example looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# application/models.py
&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;uuid&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;profile_pic_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"profile_pics/{}{}"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uuid4&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;suffix&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Profile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;picture&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ImageField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;upload_to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;profile_pic_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# Other fields like a OneToOneKey to User ...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that we've seen how Django will track files and images in your models, let's go deeper and try to understand the file storage features.&lt;/p&gt;

&lt;h2&gt;
  
  
  Files Under The Hood
&lt;/h2&gt;

&lt;p&gt;We now know that models store references to files and not the files themselves. The file storage task is delegated to a special Python class in the system.&lt;/p&gt;

&lt;p&gt;This Python class must implement &lt;a href="https://docs.djangoproject.com/en/3.1/ref/files/storage/"&gt;a specific API&lt;/a&gt;. Why? Like so many other parts of Django, the storage class can be swapped out for a different class. We've seen this swappable pattern already with templates, databases, authentication, static files, and sessions.&lt;/p&gt;

&lt;p&gt;The setting to control which type of file storage Django uses is &lt;code&gt;DEFAULT_FILE_STORAGE&lt;/code&gt;. This setting is a Python module path string to the specific class.&lt;/p&gt;

&lt;p&gt;So, what's the default? The default is a storage class that will store files locally on the server that runs the app. This is found at &lt;code&gt;django.core.files.storage.FileSystemStorage&lt;/code&gt;. The storage class uses a couple of important settings: &lt;code&gt;MEDIA_ROOT&lt;/code&gt; and &lt;code&gt;MEDIA_URL&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;MEDIA_ROOT&lt;/code&gt; setting defines where Django should look for files in the filesystem.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;MEDIA_ROOT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;BASE_DIR&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="s"&gt;"media"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On my computer, with the above setting and the &lt;code&gt;Profile&lt;/code&gt; class example from earlier, Django would store a file somewhere like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# This path is split to be easier to read.
/Users/matt/example-app/ \
    media/profile_pics/76ee4ae4-8659-4b50-a04f-e222df9a656a.jpg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The other setting important to &lt;code&gt;FileSystemStorage&lt;/code&gt; is &lt;code&gt;MEDIA_URL&lt;/code&gt;. This settings will determine how files are accessed by browsers when Django is running. Let's say &lt;code&gt;MEDIA_URL&lt;/code&gt; is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;MEDIA_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/media/"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our profile picture would have a URL like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;application.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Profile&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;profile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;last&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;profile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;picture&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;
&lt;span class="s"&gt;'/media/profile_pics/76ee4ae4-8659-4b50-a04f-e222df9a656a.jpg'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is the path that we can reference in templates. An image tag template fragment would like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jinja"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;img&lt;/span&gt; &lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;profile.picture.url&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Django documentation shows how file storage is a specific interface. &lt;code&gt;FileSystemStorage&lt;/code&gt; happens to be included with Django and implements this interface for the simplest storage mechanism, the file system of your server's operating system.&lt;/p&gt;

&lt;p&gt;We can also store files separately from the web server, and there are often really good reasons to do that. Up next, we'll look at another option for file storage aside from the provided default.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recommended Package
&lt;/h2&gt;

&lt;p&gt;What is a problem that can arise if you use the built-in &lt;code&gt;FileSystemStorage&lt;/code&gt; to store files for your application? There are actually many possible problems! Here are a few:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The web server can have too many files and run out of disk space.&lt;/li&gt;
&lt;li&gt;Users may upload malicious files to attempt to gain control of your server.&lt;/li&gt;
&lt;li&gt;Users can upload large files that can cause a Denial of Service (DOS) attack and make your site inaccessible.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you conclude that &lt;code&gt;FileSystemStorage&lt;/code&gt; will not work for your app, is there another good option? Absolutely!&lt;/p&gt;

&lt;p&gt;The most popular storage package to reach for is &lt;a href="https://django-storages.readthedocs.io/en/latest/"&gt;django-storages&lt;/a&gt;. django-storages includes a set of storage classes that can connect to a variety of cloud services. These cloud services are able to store an arbitrary number of files. With django-storages, your application can connect to services like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Amazon Simple Storage Service (S3)&lt;/li&gt;
&lt;li&gt;Google Cloud Storage&lt;/li&gt;
&lt;li&gt;Digital Ocean Spaces&lt;/li&gt;
&lt;li&gt;Or services you run separately like an SFTP server&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These services would have additional cost beyond the cost of running your web server in the cloud, but the services usually have shockingly low rates and some offer a generous free tier for lower levels of data storage.&lt;/p&gt;

&lt;p&gt;Why use django-storages?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You will never need to worry about disk space. The cloud services offer effectively unlimited storage space if you're willing to pay for it.&lt;/li&gt;
&lt;li&gt;The files will be separated from your Django web server. This can eliminate some categories of security problems like a malicious file trying to execute arbitrary code on the web server.&lt;/li&gt;
&lt;li&gt;Cloud storage can offer some caching benefits and be connected to Content Delivery Networks easily to optimize how files are served to your app's users.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As with all software choices, we have tradeoffs to consider when using different storage classes. On its face, django-storages seems to be nearly all positives. The benefits come with some setup complexity cost.&lt;/p&gt;

&lt;p&gt;For instance, I like to use Amazon S3 for file storage. You can see from the &lt;a href="https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html"&gt;Amazon S3 setup&lt;/a&gt; documentation that there is a fair amount of work to do beyond setting a different &lt;code&gt;DEFAULT_FILE_STORAGE&lt;/code&gt; class. This setup includes setting AWS private keys, access controls, regions, buckets, and a handful of other important settings.&lt;/p&gt;

&lt;p&gt;While the setup cost exists, you'll usually pay that cost at the beginning of a project and be mostly hands off after that.&lt;/p&gt;

&lt;p&gt;django-storages is a pretty fantastic package, so if your project has a lot of files to manage, you should definitely consider using it as an alternative to the &lt;code&gt;FileSystemStorage&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;In this article, you learned about Django file management. We covered:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How Django models maintain references to files&lt;/li&gt;
&lt;li&gt;How the files are managed in Django&lt;/li&gt;
&lt;li&gt;A Python package that can store files in various cloud services&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the next article, let's explore commands. Commands are the code that you can run with &lt;code&gt;./manage.py&lt;/code&gt;. You'll learn about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Built-in commands provided by Django&lt;/li&gt;
&lt;li&gt;How to build custom commands&lt;/li&gt;
&lt;li&gt;Extra commands from the community that are useful extensions for apps&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you'd like to follow along with the series, please feel free to sign up for my newsletter where I announce all of my new content. If you have other questions, you can reach me online on Twitter where I am &lt;a href="https://twitter.com/mblayman"&gt;@mblayman&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>python</category>
      <category>django</category>
      <category>files</category>
    </item>
    <item>
      <title>Vim 101: Basics of the Vim Text Editor</title>
      <dc:creator>Matt Layman</dc:creator>
      <pubDate>Mon, 09 Aug 2021 20:14:44 +0000</pubDate>
      <link>https://dev.to/mblayman/vim-101-basics-of-the-vim-text-editor-3m4a</link>
      <guid>https://dev.to/mblayman/vim-101-basics-of-the-vim-text-editor-3m4a</guid>
      <description>&lt;p&gt;At the Frederick Open Source meetup this month, I talked about the Vim text editor. I covered a lot of the fundamentals, including a mental framework to help demystify why Vim is challenging to so many people. I also covered some tips that people can use to work toward Vim mastery.&lt;/p&gt;

&lt;p&gt;The recording from the talk is available on YouTube. &lt;a href="https://www.youtube.com/embed/zxdQJlB65lA"&gt;Check it out!&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/zxdQJlB65lA"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Django Riffs, a podcast for learning Django</title>
      <dc:creator>Matt Layman</dc:creator>
      <pubDate>Mon, 02 Aug 2021 20:19:22 +0000</pubDate>
      <link>https://dev.to/mblayman/django-riffs-a-podcast-for-learning-django-29gk</link>
      <guid>https://dev.to/mblayman/django-riffs-a-podcast-for-learning-django-29gk</guid>
      <description>&lt;p&gt;I've started a podcast! The podcast is called Django Riffs, and my goal is to help beginners learn how to use &lt;a href="https://www.djangoproject.com/"&gt;Django&lt;/a&gt;. You can find the show at &lt;a href="https://djangoriffs.com/"&gt;djangoriffs.com&lt;/a&gt; or check iTunes, Spotify, or wherever you get podcasts.&lt;/p&gt;

&lt;p&gt;Each episode of the podcast will be a topical exploration of one facet of the Django web framework. With many years of Django under my belt, I believe I have the experience to help beginners on their journey into learning Django. More experienced Djangonauts may benefit from a refresher on the subjects that we cover.&lt;/p&gt;

&lt;p&gt;With this launch announcement, I am releasing the first two episodes.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The first episode, &lt;a href="https://www.mattlayman.com/django-riffs/get-to-know-django/"&gt;Get To Know Django&lt;/a&gt; gets us situated with where Django exists in the context of the internet and web application development.&lt;/li&gt;
&lt;li&gt;In the second episode, &lt;a href="https://www.mattlayman.com/django-riffs/enter-with-urls/"&gt;Enter With URLs&lt;/a&gt; we start our exploration of Django's features by focusing on how Django sets up URLs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These episodes take quite a while to produce because of the research, preparation, and production of each one. My aim is to get out a new show every couple of weeks if I can.&lt;/p&gt;

&lt;p&gt;If you would like, you can follow the show on &lt;a href="https://djangoriffs.com"&gt;djangoriffs.com&lt;/a&gt;. Or follow me or the show on Twitter at &lt;a href="https://twitter.com/mblayman"&gt;@mblayman&lt;/a&gt; or &lt;a href="https://twitter.com/djangoriffs"&gt;@djangoriffs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I hope you enjoy this. Please feel free to reach out to me with comments or feedback. My mission is to make a great Django learning experience for new developers and grow the Django community.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Serverless Python And Why You Should Try It Out</title>
      <dc:creator>Matt Layman</dc:creator>
      <pubDate>Mon, 26 Jul 2021 20:14:13 +0000</pubDate>
      <link>https://dev.to/mblayman/serverless-python-and-why-you-should-try-it-out-2ahj</link>
      <guid>https://dev.to/mblayman/serverless-python-and-why-you-should-try-it-out-2ahj</guid>
      <description>&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/SpgKwsPsqOU"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;At the January 2020 Python Frederick event, Patrick Pierson showed the group how you can use Python in different serverless services on AWS and GCP. He also showed a couple of serverless frameworks like Serverless and Chalice.&lt;/p&gt;

&lt;p&gt;The recording from the talk is available on YouTube. &lt;a href="https://www.youtube.com/watch?v=SpgKwsPsqOU"&gt;Check it out!&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Questions? Feel free to mention me on Twitter at &lt;a href="https://twitter.com/mblayman"&gt;@mblayman&lt;/a&gt; so I can try to respond to your question.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>A Failed SaaS Postmortem</title>
      <dc:creator>Matt Layman</dc:creator>
      <pubDate>Mon, 19 Jul 2021 20:18:22 +0000</pubDate>
      <link>https://dev.to/mblayman/a-failed-saas-postmortem-2ek5</link>
      <guid>https://dev.to/mblayman/a-failed-saas-postmortem-2ek5</guid>
      <description>&lt;p&gt;My Software as a Service failed. After three years of running College Conductor, I'm shutting it down. The service failed for a host of reasons, and this article details what I learned from the whole experience. This is a chance for me to reflect, and give you some ideas of what pitfalls can happen if you're planning to build a SaaS.&lt;/p&gt;

&lt;h2&gt;
  
  
  The vision
&lt;/h2&gt;

&lt;p&gt;Before getting to the lessons, let's look at my vision for the service so you have some context of what I was building.&lt;/p&gt;

&lt;p&gt;College Conductor was a tool for college counselors, and, more specifically, for independent education consultants, which is a group that included my wife. Back when I started, my wife lamented about the quality of the online tools available to help her run her business.&lt;/p&gt;

&lt;p&gt;She wanted a tool that could keep track of the students she worked with. Independent educational consultants focus on helping students find the right college or university for them. My wife observed that the existing tools for IECs (as the industry calls them) felt dated and clunky. She desired a tool that would keep track of all the schools that a student wanted to attend so she could manage college applications, deadlines, and all the other details that go with the job.&lt;/p&gt;

&lt;p&gt;As a software engineer, I saw the chance to create something in a narrow vertical that could serve this industry.&lt;/p&gt;

&lt;h2&gt;
  
  
  The initial plan
&lt;/h2&gt;

&lt;p&gt;My initial plan sounded simple: &lt;em&gt;build an app that would track schools and admissions deadlines.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In the spirit of &lt;a href="http://paulgraham.com/ds.html"&gt;doing things that don't scale&lt;/a&gt;, I planned to get as many US colleges and universities as I could into a database. From there, I would operate as the Wizard of Oz and manually look up the admissions deadlines for a school whenever my wife added the school to a student's list. (Adding the school to a list would trigger a "back office" task to inform me that I had work to do.) To me, this felt like the scrappy kind of attitude that I needed to build something quickly.&lt;/p&gt;

&lt;p&gt;For the technology side, I wanted to make choices that would be with me for the long haul (which you will see is ironic later). My short list of technology looked like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.djangoproject.com/"&gt;Django&lt;/a&gt; to power an API.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://emberjs.com/"&gt;Ember&lt;/a&gt; as the client app.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://semantic-ui.com/"&gt;Semantic UI&lt;/a&gt; for the look and feel.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.ansible.com/"&gt;Ansible&lt;/a&gt; for handling deployment.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ember and Semantic UI were new to me, but I figured that some technical risk on those choices would pay off in the long run. I was wrong, and we'll dig into why.&lt;/p&gt;

&lt;h2&gt;
  
  
  Not-so-boring technology choices
&lt;/h2&gt;

&lt;p&gt;When I started, I wanted to follow the advice to  &lt;a href="https://mcfunley.com/choose-boring-technology"&gt;choose boring technology&lt;/a&gt;. For me, this meant:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Deploy to a virtual machine.&lt;/li&gt;
&lt;li&gt;Use a relational database like &lt;a href="https://www.postgresql.org/"&gt;PostgreSQL&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Stick with a mature framework like Django.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the boring technology article, the author, Dan McKinley, writes:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Let’s say every company gets about three innovation tokens. You can spend these however you want, but the supply is fixed for a long while.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In my mind, if I used Ember and Semantic UI, then I've only used two innovation tokens so I should still be in good shape to make rapid progress. &lt;em&gt;Wow, was I wrong. So wrong.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Ember and Semantic were, indeed, innovation tokens that I spent. Ember wasn't new, but it was new to me, and it has a steep learning curve to be deeply productive with the framework. Semantic &lt;em&gt;was&lt;/em&gt; on the newer side so I was learning its patterns as the UI toolkit evolved.&lt;/p&gt;

&lt;p&gt;Where I was horribly wrong was in thinking that Ember and Semantic were the only pieces of new technology that I needed. The list was so much longer and included:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.datadoghq.com/"&gt;Datadog&lt;/a&gt; for infrastructure monitoring.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.digitalocean.com/"&gt;DigitalOcean&lt;/a&gt; for virtual machine hosting.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.django-rest-framework.org/"&gt;Django REST Framework&lt;/a&gt; for APIs.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.drip.com/"&gt;Drip&lt;/a&gt; for marketing email campaigns.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://jekyllrb.com/"&gt;Jekyll&lt;/a&gt; for the marketing site.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://jsonapi.org/"&gt;JSON API&lt;/a&gt; as a communication protocol.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://letsencrypt.org/"&gt;Let's Encrypt&lt;/a&gt; for TLS certificates.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.mailgun.com/"&gt;Mailgun&lt;/a&gt; for transactional emails.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://mixpanel.com/"&gt;Mixpanel&lt;/a&gt; for behavior analysis.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://rollbar.com/"&gt;Rollbar&lt;/a&gt; for error tracking.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://segment.com/"&gt;Segment&lt;/a&gt; for data aggregation.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://stripe.com/"&gt;Stripe&lt;/a&gt; for recurring payments.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/wal-e/wal-e"&gt;wal-e&lt;/a&gt; for database backups.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://webpack.js.org/"&gt;webpack&lt;/a&gt; for JavaScript asset bundling.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For some of those tools, I had some experience, but the depth of my knowledge was lacking since I am a company of one. Theses were all technologies that I had to learn and configure myself. While I succeeded at integrating each of these things, I did so at the cost of &lt;em&gt;a lot&lt;/em&gt; of time.&lt;/p&gt;

&lt;p&gt;So, here's a first lesson:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When starting out, keep your technology stack to a bare minimum and follow the &lt;strong&gt;YAGNI&lt;/strong&gt; (You Ain't Gonna Need It) principle.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The upgrade treadmill
&lt;/h2&gt;

&lt;p&gt;After assembling all of these tools together, I finally felt like I had a base to work from. My project was moving and I began to build the critical features needed to make a minimum viable product. Then I fell into a trap.&lt;/p&gt;

&lt;p&gt;The trap looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Time is ultra-limited because this is a nights and weekends side project.&lt;/li&gt;
&lt;li&gt;Software moves fast and new versions of tools constantly release (this is especially true in the JavaScript ecosystem).&lt;/li&gt;
&lt;li&gt;I'm eager to keep the software up-to-date to ensure that the large base I built stays "modern."&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;With these three conditions in place, I spent way too much time piddling with package upgrades. I'm guessing you can see how this might happen. I'd spend a long day working at my day job and doing a lot of software development there. Then I wouldn't have the energy to make &lt;strong&gt;real&lt;/strong&gt; progress on my app, so I'd tell myself that upgrading one of the packages that was out-of-date was a good use of my time.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Wrong. Again, I was so wrong.&lt;/em&gt; I wasted so many nights updating small packages for zero benefit. To make the issue worse, some of these minor updates would break things (because &lt;a href="https://twitter.com/dhh/status/1015206151384952834"&gt;SemVer is a lie&lt;/a&gt;). Upgrading these packages was a treadmill that got me nowhere. That brings me to lesson two:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Software package upgrades suck the wind out of your sail. Until you have a minimum viable product with customers, pick a package version and stick with it. Only upgrade &lt;strong&gt;when you have to.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Switching horses mid-race... twice!
&lt;/h2&gt;

&lt;p&gt;Remember how I mentioned picking Semantic UI and Ember? Check out the &lt;a href="https://github.com/mblayman/conductor"&gt;College Conductor repo&lt;/a&gt; and you'll be hard pressed to find either of them.&lt;/p&gt;

&lt;p&gt;At some point during development, I realized how hard I was fighting with my tool choices. Semantic UI wouldn't play nicely with Ember's build system, and the components and extensibility of Semantic were not working for me. This friction affected my ability to make UI elements that I needed for features.&lt;/p&gt;

&lt;p&gt;I won't blame Semantic because it was more likely my deep inexperience with the toolkit that was the problem. With Semantic troubling me and me having do all the other parts of the system development too, I pulled the ripcord and switched to &lt;a href="https://getbootstrap.com/"&gt;Bootstrap&lt;/a&gt;. I sacrificed more novel styling for a system I knew. And, guess what? I saw the immediate productivity boost from using a tool from my toolbox!&lt;/p&gt;

&lt;p&gt;Eventually, I made a similar choice to remove Ember. I tried to use Ember for nearly two years. I read a large amount of the documentation, wrote articles about what I learned on my journey, and stayed up-to-date with all the EmberConf content and news coming out of that community. I &lt;em&gt;really&lt;/em&gt; like Ember. So why would I cut it loose?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Development velocity&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;As a one person team, I had to develop the backend logic, database models, and everything that comes with backend development. I &lt;em&gt;also&lt;/em&gt; had to develop the entire user interface. I discovered that creating a backend API and rich client side single page app (SPA) came with a ton of overhead.&lt;/p&gt;

&lt;p&gt;My flow with Ember was like:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Figure out the data model for the next feature.&lt;/li&gt;
&lt;li&gt;Create models in Django.&lt;/li&gt;
&lt;li&gt;Create JSON APIs in Django.&lt;/li&gt;
&lt;li&gt;Create models &lt;em&gt;again&lt;/em&gt; on the client side with &lt;a href="https://guides.emberjs.com/release/models/"&gt;Ember Data&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Decide on the routes and controllers to finish the user interface for the feature.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Between step three and four, I would lose most of my motivation and jump back on the upgrade treadmill. As a team of one, it felt like drudgery to recreate models for a client side representation.&lt;/p&gt;

&lt;p&gt;The decision to remove Ember was really tough. The &lt;a href="https://en.wikipedia.org/wiki/Sunk_cost"&gt;sunk cost fallacy&lt;/a&gt; hit me hard. I delayed removing Ember for many months because I convinced myself that it would take a long time to redo all the work that I finished so far.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Then I removed it.&lt;/em&gt; What do you think happened? I removed so much extra stuff and simplified my technology stack so tremendously that I implemented &lt;em&gt;all&lt;/em&gt; of my original features within &lt;em&gt;two weeks.&lt;/em&gt; Two weeks of effort to replicate two years of previous development.&lt;/p&gt;

&lt;p&gt;Does this mean that Ember is bad and you shouldn't use it? &lt;strong&gt;No!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Ember was a bad fit for me, at that time, with my level of experience with it, and no support from other team members. Also, I switched to using Django for the user interface with server side rendering, and I was extremely comfortable with that because I use Django daily in my day job.&lt;/p&gt;

&lt;p&gt;Lesson three:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Use the tools that are already in your toolbox!&lt;/strong&gt; You'll need all your extra brain capacity for solving the business problem.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Ignoring customer development
&lt;/h2&gt;

&lt;p&gt;If you look back at the vision that I wanted for the project, you'll see that I hoped to make something useful for my wife. Up to this point, I've written about the technical failings of the project. This is an emblematic example of why the project failed. &lt;em&gt;I didn't help my customers and was too focused on the technology.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;At the start of the effort, I asked my wife questions about what she needed and what would help her and other educational consultants like her. She told me the things that she was looking for directly. But it took forever to have something tangible to show her.&lt;/p&gt;

&lt;p&gt;If someone shows up to a restaurant, expresses what they want, then the chef takes four hours to prepare the meal, will that customer be happy? I think not.&lt;/p&gt;

&lt;p&gt;I was the slow chef. All the time I invested in building the "right" thing was wasted because I didn't do customer development. &lt;strong&gt;I didn't produce the simplest version of the product that would provide value.&lt;/strong&gt; It did not take long before I completely lost my wife's interest. To stick with the four hour meal analogy, my patient customer left after the meal wasn't ready in the first hour.&lt;/p&gt;

&lt;p&gt;As developers and technologists, many of us enjoy steeping in the technology purely for the sake of it. There is so much to learn and so many ways to apply software that it is easy to lose yourself in that tinkering. While that's personally useful, especially if you need to grow your skills, staying in that space is not helping others.&lt;/p&gt;

&lt;p&gt;So, what did I take away from that? That would be the next lesson:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Get your product to be something useful for others &lt;strong&gt;as fast as possible&lt;/strong&gt;. Until you deliver something valuable, you only have a hobby.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Without serious interest from my first customer (and no other strong ties to her industry), I was missing a good voice to drive the product forward. Because she was disinterested, there was no external pressure on me to deliver something. My project got nowhere fast.&lt;/p&gt;

&lt;h2&gt;
  
  
  The end
&lt;/h2&gt;

&lt;p&gt;If there is anything redeeming about College Conductor, it was as a teaching tool. I've done a lot of Django code for work, and I brought that same thoroughness to this project. After listening to an episode of The Changelog about &lt;a href="https://changelog.com/podcast/288"&gt;live streaming software development on Twitch&lt;/a&gt;, I started to share my efforts on College Conductor live.&lt;/p&gt;

&lt;p&gt;Over a year later, I have &lt;a href="https://www.mattlayman.com/building-saas/"&gt;streamed nearly 40 lessons&lt;/a&gt; in an episodic format to teach people about Django and building real projects. My service may have had no real customers, but the techniques I apply to building things are very much the techniques that I use at work.&lt;/p&gt;

&lt;p&gt;Where does that leave me?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I've learned a lot.&lt;/li&gt;
&lt;li&gt;I've taught a lot.&lt;/li&gt;
&lt;li&gt;College Conductor was a financial failure.&lt;/li&gt;
&lt;li&gt;College Conductor was a successful teaching tool.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I hope you're able to learn a bit from my experience with building my first SaaS project.&lt;/p&gt;

&lt;p&gt;For those jumping straight to the end, here are the lessons that I learned.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;When starting out, keep your technology stack to a bare minimum and follow the &lt;strong&gt;YAGNI&lt;/strong&gt; (You Ain't Gonna Need It) principle.&lt;/li&gt;
&lt;li&gt;Software package upgrades suck the wind out of your sail. Until you have a minimum viable product with customers, pick a package version and stick with it. Only upgrade &lt;strong&gt;when you have to.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use the tools that are already in your toolbox!&lt;/strong&gt; You'll need all your extra brain capacity for solving the business problem.&lt;/li&gt;
&lt;li&gt;Get your product to be something useful for others &lt;strong&gt;as fast as possible&lt;/strong&gt;. Until you deliver something valuable, you only have a hobby.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you want to watch me build my second SaaS, please join me on &lt;a href="https://www.twitch.tv/mblayman"&gt;Twitch&lt;/a&gt; on Wednesdays at 9pm ET. Hopefully, I'll make fewer mistakes this time around.&lt;/p&gt;

&lt;p&gt;If you have questions or enjoyed this article, please feel free to message me on Twitter at &lt;a href="https://twitter.com/mblayman"&gt;@mblayman&lt;/a&gt; or share if others might be interested too.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Understand Django: Making Sense Of Settings</title>
      <dc:creator>Matt Layman</dc:creator>
      <pubDate>Thu, 15 Jul 2021 14:18:42 +0000</pubDate>
      <link>https://dev.to/mblayman/understand-django-making-sense-of-settings-f6m</link>
      <guid>https://dev.to/mblayman/understand-django-making-sense-of-settings-f6m</guid>
      <description>&lt;p&gt;In the last &lt;a href="https://www.mattlayman.com/understand-django/"&gt;Understand Django&lt;/a&gt; article, we looked at a storage concept in Django called sessions. Sessions help us answer questions like "How does Django know when a user is logged in?" or "Where can the framework store data for a visitor on your app?"&lt;/p&gt;

&lt;p&gt;With this article, you'll learn about Django settings and how to manage the configuration of your application. We'll also look at tools to help you to be extra effective with settings.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Is Django Configured?
&lt;/h2&gt;

&lt;p&gt;To run properly, Django needs to be configured. We need to understand where this configuration comes from. Django has the ability to use default configuration values or values set by developers like yourself, but where does it get those from?&lt;/p&gt;

&lt;p&gt;Early in the process of starting a Django application, Django will internally import the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.conf&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This &lt;code&gt;settings&lt;/code&gt; import is a module level object created in &lt;code&gt;django/conf/__init__.py&lt;/code&gt;. The &lt;code&gt;settings&lt;/code&gt; object has attributes added to it from two primary sources.&lt;/p&gt;

&lt;p&gt;The first source is a set of global default settings that come from the framework. These global settings are from &lt;code&gt;django/conf/global_settings.py&lt;/code&gt; and provide a set of initial values for configuration that Django needs to operate.&lt;/p&gt;

&lt;p&gt;The second source of configuration settings comes from user defined values. Django will accept a Python module and apply its module level attributes to the &lt;code&gt;settings&lt;/code&gt; object. To find the user module, Django searches for a &lt;code&gt;DJANGO_SETTINGS_MODULE&lt;/code&gt; environment variable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sidebar: Environment Variables
&lt;/h3&gt;

&lt;p&gt;Environment variables are not a Django concept. When any program runs on a computer, the operating system makes certain data available to the running program. This set of data is called the program's "environment," and each piece of data in that set is an environment variable.&lt;/p&gt;

&lt;p&gt;If you're starting Django from a terminal, you can view the environment variables that Django will receive from the operating system by running the &lt;code&gt;env&lt;/code&gt; command on macOS or Linux, or the &lt;code&gt;set&lt;/code&gt; command on Windows.&lt;/p&gt;

&lt;p&gt;We can add our own environment variables to the environment with the &lt;code&gt;export&lt;/code&gt; command on macOS or Linux, or the &lt;code&gt;set&lt;/code&gt; command on Windows. Environment variables are typically named in all capital letters.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;HELLO&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;world
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that we have a base understanding of environment variables, let's return to the &lt;code&gt;DJANGO_SETTINGS_MODULE&lt;/code&gt; variable. The variable's value should be the location of a Python module containing any settings that a developer wants to change from Django's default values.&lt;/p&gt;

&lt;p&gt;If you create a Django project with &lt;code&gt;startproject&lt;/code&gt; and use &lt;code&gt;project&lt;/code&gt; as the name, then you will find a generated file called &lt;code&gt;project/settings.py&lt;/code&gt; in the output. When Django runs, you could explicitly instruct Django with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;DJANGO_SETTINGS_MODULE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;project.settings
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of supplying the file path, the &lt;code&gt;DJANGO_SETTINGS_MODULE&lt;/code&gt; should be in a Python module dotted notation.&lt;/p&gt;

&lt;p&gt;You may not actually need to set &lt;code&gt;DJANGO_SETTINGS_MODULE&lt;/code&gt; explicitly. If you stick with the same settings file that is created by &lt;code&gt;startproject&lt;/code&gt;, you can find a line in &lt;code&gt;wsgi.py&lt;/code&gt; that looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setdefault&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'DJANGO_SETTINGS_MODULE'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'project.settings'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because of this line, Django will attempt to read from &lt;code&gt;project.settings&lt;/code&gt; (or whatever you named your project) without the need to explicitly set &lt;code&gt;DJANGO_SETTINGS_MODULE&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Once Django reads the global settings and any user defined settings, we can get any configuration from the &lt;code&gt;settings&lt;/code&gt; object via attribute access. This convention of keeping all configuration in the &lt;code&gt;settings&lt;/code&gt; object is a convenient pattern that the framework, third party library ecosystem, and &lt;em&gt;you&lt;/em&gt; can depend on.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="err"&gt;$&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;manage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt; &lt;span class="n"&gt;shell&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.conf&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SECRET_KEY&lt;/span&gt;
&lt;span class="s"&gt;'a secret to everybody'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;settings&lt;/code&gt; object is a shared item so it is generally thought to be a Really Bad Idea™ to edit and assign to the object directly. Keep your settings in your settings module!&lt;/p&gt;

&lt;p&gt;That's the core of Django configuration. We're ready to focus in on the user defined settings and our responsibilities as Django app developers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Settings Module Patterns
&lt;/h2&gt;

&lt;p&gt;There are multiple ways to deal with settings modules and how to populate those modules with the appropriate values for different environments. Let's look at some popular patterns.&lt;/p&gt;

&lt;h3&gt;
  
  
  Multiple Modules Per Environment
&lt;/h3&gt;

&lt;p&gt;A Django settings module is a Python module. Nothing is stopping us from using the full power of Python to configure that module the way we want.&lt;/p&gt;

&lt;p&gt;Minimally, you will probably have at least two environments where your Django app runs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;On your local machine while developing&lt;/li&gt;
&lt;li&gt;On the internet for your live site&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We should know by now that setting &lt;code&gt;DEBUG = True&lt;/code&gt; is a terrible idea for a live Django site, so how can we get the benefits of the debug mode without having &lt;code&gt;DEBUG&lt;/code&gt; set to &lt;code&gt;True&lt;/code&gt; in our module?&lt;/p&gt;

&lt;p&gt;One technique is to use separate settings modules. With this strategy, you can pick which environment your Django app should run for by switching the &lt;code&gt;DJANGO_SETTINGS_MODULE&lt;/code&gt; value to pick a different environment. You might have modules like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;project.settings.dev&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;project.settings.stage&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;project.settings.production&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These examples would be for a local development environment on your laptop, a staging environment (which is a commonly used pattern for testing a site that is as similar to the live site as possible without &lt;em&gt;being&lt;/em&gt; the live site), and a production environment. As a reminder from the deployment article, the software industry like to call the primary site for customers "production."&lt;/p&gt;

&lt;p&gt;This strategy has certain challenges to consider. Should you replicate settings in each file or use some common module between them?&lt;/p&gt;

&lt;p&gt;If you decide to replicate the settings across modules, you'll have the advantage that the settings module shows &lt;em&gt;all&lt;/em&gt; of the settings in a single place for that environment. The disadvantage is that keeping the common settings the same could be a challenge if you forget to update one of the modules.&lt;/p&gt;

&lt;p&gt;On the other hand, you could use a common module. The advantage to this form is that the common settings can be in a single location. The environment specific files only need to record the &lt;em&gt;differences&lt;/em&gt; between the environments. The disadvantage is that it is harder to get a clear picture of all the settings of that environment.&lt;/p&gt;

&lt;p&gt;If you decide to use a common module, this style is often implemented with a &lt;code&gt;*&lt;/code&gt; import. I can probably count on one hand the number of places where I'm ok with a &lt;code&gt;*&lt;/code&gt; import, and this is one of them. In most cases the Python community prefers explicit over implicit, and the idea extends to the treatment of imports. Explicit imports make it clear what a module is actually using. The &lt;code&gt;*&lt;/code&gt; import is very implicit, and it makes it unclear what a module uses. For the case of a common settings module, a &lt;code&gt;*&lt;/code&gt; import is actually positive because we want to use &lt;em&gt;everything&lt;/em&gt; in the common module.&lt;/p&gt;

&lt;p&gt;Let's make this more concrete. Assume that you have a &lt;code&gt;project.settings.base&lt;/code&gt; module. This module would hold your common settings for your app. I'd recommend that you try to make your settings safe and secure by default. For instance, use &lt;code&gt;DEBUG = False&lt;/code&gt; in the base settings and force other settings modules to opt-in to the more unsafe behavior.&lt;/p&gt;

&lt;p&gt;For your local development environment on your laptop, you could use &lt;code&gt;project.settings.dev&lt;/code&gt;. This settings module would look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# project/settings/dev.py
&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;project.settings.base&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;

&lt;span class="n"&gt;DEBUG&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;

&lt;span class="c1"&gt;# Define any other settings that you want to override.
&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By using the &lt;code&gt;*&lt;/code&gt; import in the &lt;code&gt;dev.py&lt;/code&gt; file, all the settings from &lt;code&gt;base.py&lt;/code&gt; are pulled into the module level scope. Where you want a setting to be different, you set the value in &lt;code&gt;dev.py&lt;/code&gt;. When Django starts using &lt;code&gt;DJANGO_SETTINGS_MODULE&lt;/code&gt; of &lt;code&gt;project.settings.dev&lt;/code&gt;, all the values from &lt;code&gt;base.py&lt;/code&gt; will be used via &lt;code&gt;dev.py&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This scheme gives you control to define common things once, but there is still a big challenge with this. What do we do about settings that need to be kept secret (e.g., API keys)?&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Don't commit secret data to your code repository!&lt;/em&gt; Adding secrets to your source control tool like Git is usually not a good idea. This is especially true if you have a public repository on GitHub. Think no one is paying attention to your repo? Think again! There are tools out there that scan &lt;em&gt;every public commit&lt;/em&gt; made to GitHub. These tools are specifically looking for secret data to exploit.&lt;/p&gt;

&lt;p&gt;If you can't safely add secrets to your code repo, where can we add them instead? You can use environment variables! Let's look at another scheme for managing settings with environment variables.&lt;/p&gt;

&lt;h3&gt;
  
  
  Settings Via Environment Variables
&lt;/h3&gt;

&lt;p&gt;In Python, you can access environment variables through the &lt;code&gt;os&lt;/code&gt; module. The module contains the &lt;code&gt;environ&lt;/code&gt; attribute, which functions like a dictionary.&lt;/p&gt;

&lt;p&gt;By using environment variables, your settings module can get configuration settings from the external environment that is running the Django app. This is a solid pattern because it can accomplish two things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Secret data can be kept out of your code&lt;/li&gt;
&lt;li&gt;Configuration differences between environments can be managed by changing environment variable values&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's an example of secret data management:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# project/settings.py
&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;

&lt;span class="n"&gt;SECRET_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'SECRET_KEY'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Django needs a secret key for a variety of safe hashing purposes. There is a warning in the default &lt;code&gt;startproject&lt;/code&gt; output that reads:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# SECURITY WARNING: keep the secret key used in production secret!
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By moving the secret key value to an environment variable that happens to have a matching name of &lt;code&gt;SECRET_KEY&lt;/code&gt;, we won't be committing the value to source control for some nefarious actor to discover.&lt;/p&gt;

&lt;p&gt;This pattern works really well for secrets, but it can also work well for &lt;em&gt;any&lt;/em&gt; configuration that we want to vary between environments.&lt;/p&gt;

&lt;p&gt;For instance, on one of my projects, I use the excellent &lt;a href="https://anymail.readthedocs.io/en/stable/"&gt;Anymail&lt;/a&gt; package to send emails via an email service provider (of the ESPs, I happen to use &lt;a href="https://sendgrid.com/"&gt;SendGrid&lt;/a&gt;). When I'm working with my development environment, I don't want to send real email. Because of that, I use an environment variable to set Django's &lt;code&gt;EMAIL_BACKEND&lt;/code&gt; setting. This let's me switch between the Anymail backend and Django's built-in &lt;code&gt;django.core.mail.backends.console.EmailBackend&lt;/code&gt; that prints emails to the terminal instead.&lt;/p&gt;

&lt;p&gt;If I did this email configuration with &lt;code&gt;os.environ&lt;/code&gt;, it would look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# project/settings.py
&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;

&lt;span class="n"&gt;EMAIL_BACKEND&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;'EMAIL_BACKEND'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"anymail.backends.sendgrid.EmailBackend"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="p"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I prefer to make my default settings closer to the live site context. This not only leads to safer behavior (because I have to explicitly opt-out of safer settings like switching to &lt;code&gt;DEBUG = False&lt;/code&gt;), but it also means that my live site has less to configure. That's good because there are fewer chances to make configuration mistakes on the site that matters most: the one where my customers are.&lt;/p&gt;

&lt;p&gt;We need to be aware of a big gotcha with using environment variables. &lt;em&gt;Environment variables&lt;/em&gt; are only available as a &lt;code&gt;str&lt;/code&gt; type. This is something to be aware because there will be times when you want a boolean settings value or some other &lt;em&gt;type&lt;/em&gt; of data. In a situation where you need a different type, you have to coerce a &lt;code&gt;str&lt;/code&gt; into the type you need. In other words, don't forget that every string except the empty string is truthy in Python:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;not_false&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"False"&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;not_false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="bp"&gt;True&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the next section, we will see tools that help alleviate this typing problem.&lt;/p&gt;

&lt;p&gt;Note: As you learn more about settings, you will probably encounter advice that says to avoid using environment variables. This is well intentioned advice that highlights that there &lt;em&gt;is&lt;/em&gt; some risk with using environment variables. With this kind of advice, you may read a recommendation for secrets management tools like &lt;a href="https://www.vaultproject.io/"&gt;HashiCorp Vault&lt;/a&gt;. These are good tools, but consider them a more advanced topic. In my opinion, using environment variables for secrets management is a reasonably low risk storage mechanism.&lt;/p&gt;

&lt;h2&gt;
  
  
  Settings Management Tools
&lt;/h2&gt;

&lt;p&gt;We can focus on two categories of tools that can help you manage your settings in Django: built-in tools and third party libraries.&lt;/p&gt;

&lt;p&gt;The built-in tool that is available to you is the &lt;code&gt;diffsettings&lt;/code&gt; command. This tool makes it easy to see the computed settings of your module. Since settings can come from multiple files (including Django's &lt;code&gt;global_settings.py&lt;/code&gt;) or environment variables, inspecting the settings output of &lt;code&gt;diffsettings&lt;/code&gt; is more convenient than thinking through how a setting is set.&lt;/p&gt;

&lt;p&gt;By default, &lt;code&gt;diffsettings&lt;/code&gt; will show a comparison of the settings module to the default Django settings. Settings that aren't in the defaults are marked with &lt;code&gt;###&lt;/code&gt; after the value to indicate that they are different.&lt;/p&gt;

&lt;p&gt;I find that the default output is not the most useful mode. Instead, you can instruct &lt;code&gt;diffsettings&lt;/code&gt; to output in a "unified" format. This format looks a lot more like a code diff. In addition, Django will colorize that output so that it's easier to see. Here's an example of some of the security settings by running &lt;code&gt;./manage.py diffsettings --output unified&lt;/code&gt; for one of my projects.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;- SECURE_HSTS_INCLUDE_SUBDOMAINS = False
&lt;/span&gt;&lt;span class="gi"&gt;+ SECURE_HSTS_INCLUDE_SUBDOMAINS = True
&lt;/span&gt;&lt;span class="gd"&gt;- SECURE_PROXY_SSL_HEADER = None
&lt;/span&gt;&lt;span class="gi"&gt;+ SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, I'll note that you can actually compare two separate settings modules. Let's say you wanted to compare settings between your development mode and your live site. Assuming your settings files are names like I described earlier, you could run something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;./manage.py diffsettings &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--default&lt;/span&gt; project.settings.dev &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--settings&lt;/span&gt; project.settings.prod &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--output&lt;/span&gt; unified
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By using the &lt;code&gt;--default&lt;/code&gt; flag, we instruct Django that &lt;code&gt;project.settings.dev&lt;/code&gt; is the baseline for comparison. This version of the command will show where the two settings modules are different.&lt;/p&gt;

&lt;p&gt;Django only includes this single tool for working with settings, but I hope you can see that it's really handy. Now let's talk about a useful third party library that can help you with settings.&lt;/p&gt;

&lt;p&gt;Earlier in the article, I noted that dealing with environment variables has the pitfall of working with string data for everything. Thankfully, there is a package that can help you work with environment variables. The project is called &lt;a href="https://django-environ.readthedocs.io/en/latest/"&gt;django-environ&lt;/a&gt;. django-environ primarily does two important things that I value:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The package allows you coerce strings into a desired data type.&lt;/li&gt;
&lt;li&gt;The package will read from a file to load environment variables into your environment.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What does type coercion look like? With &lt;code&gt;django-environ&lt;/code&gt;, you start with &lt;code&gt;Env&lt;/code&gt; object.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# project/settings.py
&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;environ&lt;/span&gt;

&lt;span class="n"&gt;env&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Env&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The keyword arguments to &lt;code&gt;Env&lt;/code&gt; describe the different environment variables that you expect the app to process. The key is the name of the environment variable. The value is a two element tuple. The first tuple element is the type you want, and the second element is a default value if the environment variable doesn't exist.&lt;/p&gt;

&lt;p&gt;If you want to be able to control &lt;code&gt;DEBUG&lt;/code&gt; from an environment variable, the settings would be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# project/settings.py
&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;environ&lt;/span&gt;

&lt;span class="n"&gt;env&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;DEBUG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;DEBUG&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"DEBUG"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this setup, your app will be safe by default with &lt;code&gt;DEBUG&lt;/code&gt; set to &lt;code&gt;False&lt;/code&gt;, but you'll be able to override that via the environment. &lt;code&gt;django-environ&lt;/code&gt; works with a handful of strings that it will accept as &lt;code&gt;True&lt;/code&gt; such as "on", "yes", "true", and others (see the documentation for more details).&lt;/p&gt;

&lt;p&gt;Once you start using environment variables, you'll want an convenient way to set them when your app runs. Manually calling &lt;code&gt;export&lt;/code&gt; for all your variables before running your app is a totally unsustainable way to run apps.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;Env&lt;/code&gt; class comes with a handy class method named &lt;code&gt;read_env&lt;/code&gt;. With this method, your app can read environment variables into &lt;code&gt;os.environ&lt;/code&gt; from a file. Conventionally, this file is named &lt;code&gt;.env&lt;/code&gt;, and the file contains a list of key/value pairs that you want as environment variables. Following our earlier example, here's how we could set our app to be in debug mode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# .env
DEBUG=on
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Back in the settings file, you'd include &lt;code&gt;read_env&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# project/settings.py
&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;environ&lt;/span&gt;

&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read_env&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;env&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;DEBUG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;DEBUG&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"DEBUG"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you use a &lt;code&gt;.env&lt;/code&gt; file, you will occasionally find a need to put secrets into this file for testing. Since the file can be a source for secrets, you should add this to &lt;code&gt;.gitignore&lt;/code&gt; or ignore it in whatever version control system you use. As time goes on, the list of variables and settings will likely grow, so it's also a common pattern to create a &lt;code&gt;.env.example&lt;/code&gt; file that you can use as a template in case you ever need to start with a fresh clone of your repository.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Preferred Settings Setup
&lt;/h2&gt;

&lt;p&gt;Now we've looked at multiple strategies and tools for managing settings. I've used many of these schemes on various Django projects, so what is my preferred setup?&lt;/p&gt;

&lt;p&gt;For the majority of uses, I find that working with &lt;code&gt;django-environ&lt;/code&gt; in a single file is the best pattern in my experience.&lt;/p&gt;

&lt;p&gt;When I use this approach, I make sure that all of my settings favor a safe default configuration. This minimizes the configuration that I have to do for a live site.&lt;/p&gt;

&lt;p&gt;I like the flexibility of the pattern, and I find that I can quickly set certain configurations when developing. For instance, when I want to do certain kinds of testing like checking email rendering, I'll call something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ EMAIL_TESTING&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;on ./manage.py runserver
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;My settings file has a small amount of configuration to alter the email settings to point emails to a local SMTP server tool called &lt;a href="https://github.com/mailhog/MailHog"&gt;MailHog&lt;/a&gt;. Because I set an environment variable directly on my command line call, I can easily switch into a mode that sends email to MailHog for quick review.&lt;/p&gt;

&lt;p&gt;Overall, I like the environment variable approach, but I do use more than one settings file for one important scenario: testing.&lt;/p&gt;

&lt;p&gt;When I run my unit tests, I want to guarantee that certain conditions are always true. There are things that a test suite should never do in the vast majority of cases. Sending real emails is a good example. If I happen to configure my &lt;code&gt;.env&lt;/code&gt; to test real emails for the local environment, I don't want my tests to send out an emails accidentally.&lt;/p&gt;

&lt;p&gt;Thus, I create a separate testing settings file and configure my test runner (pytest) to use those settings. This settings file &lt;em&gt;does&lt;/em&gt; mostly use the base environment, but I'll override some settings with explicit values. Here's how I protect myself from accidental live emails:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# project/testing_settings.py
&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;.settings&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;

&lt;span class="c1"&gt;# Make sure that tests are never sending real emails.
&lt;/span&gt;&lt;span class="n"&gt;EMAIL_BACKEND&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"django.core.mail.backends.locmem.EmailBackend"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even though my &lt;code&gt;Env&lt;/code&gt; will look for an &lt;code&gt;EMAIL_BACKEND&lt;/code&gt; environment variable to configure that setting dynamically, the testing setting is hardcoded to make email sending accidents impossible.&lt;/p&gt;

&lt;p&gt;The combination of a single file for most settings sprinkled with a testing settings file for safety is the approach that has worked the best for me.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;In this article, you learned about Django settings and how to manage the configuration of your application. We covered:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How Django is configured&lt;/li&gt;
&lt;li&gt;Patterns for working with settings in your projects&lt;/li&gt;
&lt;li&gt;Tools that help you observe and manage settings&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the next article, we will look at how to handle files and media provided by users (e.g., profile pictures). You'll learn about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How Django models maintain references to files&lt;/li&gt;
&lt;li&gt;How the files are managed in Django&lt;/li&gt;
&lt;li&gt;Packages that can store files in various cloud services&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you'd like to follow along with the series, please feel free to sign up for my newsletter where I announce all of my new content. If you have other questions, you can reach me online on Twitter where I am &lt;a href="https://twitter.com/mblayman"&gt;@mblayman&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>python</category>
      <category>django</category>
      <category>settings</category>
    </item>
    <item>
      <title>Python Tears Through Mass Spectrometry Data</title>
      <dc:creator>Matt Layman</dc:creator>
      <pubDate>Mon, 12 Jul 2021 20:17:28 +0000</pubDate>
      <link>https://dev.to/mblayman/python-tears-through-mass-spectrometry-data-21bf</link>
      <guid>https://dev.to/mblayman/python-tears-through-mass-spectrometry-data-21bf</guid>
      <description>&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/D5YQ3ZLESZM"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;At the November 2019 Python Frederick event, Conor Jenkins showed the group how mass spectrometry works and how Python saves huge amounts of time when processing the large amount of data produced by a mass spec analysis.&lt;/p&gt;

&lt;p&gt;The recording from the talk is available on YouTube. &lt;a href="https://www.youtube.com/watch?v=4o63V0AdrHk"&gt;Check it out!&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Questions? Feel free to mention me on Twitter at &lt;a href="https://twitter.com/mblayman"&gt;@mblayman&lt;/a&gt; so I can try to respond to your question.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Godot You Want To Make A Videogame</title>
      <dc:creator>Matt Layman</dc:creator>
      <pubDate>Mon, 05 Jul 2021 20:13:23 +0000</pubDate>
      <link>https://dev.to/mblayman/godot-you-want-to-make-a-videogame-3lg0</link>
      <guid>https://dev.to/mblayman/godot-you-want-to-make-a-videogame-3lg0</guid>
      <description>&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/4o63V0AdrHk"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;What is Godot? It's an open source game engine for making high quality 2D or 3D videogames! At this Frederick Linux User Group (KeyLUG) presentation, I explained how Godot works, compared it with other popular engines, and showed off some of its features.&lt;/p&gt;

&lt;p&gt;The recording from the talk is available on YouTube. &lt;a href="https://www.youtube.com/watch?v=4o63V0AdrHk"&gt;Check it out!&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this presentation, we looked into:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Other popular open source game engines like &lt;a href="https://www.pygame.org/news"&gt;Pygame&lt;/a&gt;, &lt;a href="https://love2d.org/"&gt;LÖVE&lt;/a&gt;, and &lt;a href="https://phaser.io/"&gt;Phaser&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;The proprietary options of &lt;a href="https://unity.com/"&gt;Unity&lt;/a&gt;, &lt;a href="https://www.yoyogames.com/gamemaker"&gt;GameMaker Studio 2&lt;/a&gt;, &lt;a href="https://www.unrealengine.com/en-US/"&gt;Unreal Engine&lt;/a&gt;, and &lt;a href="https://www.cryengine.com/"&gt;CRYENGINE&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;why Godot is a great choice.&lt;/li&gt;
&lt;li&gt;how to use the editor.&lt;/li&gt;
&lt;li&gt;sample projects that showcase different types of games.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Questions? Feel free to mention me on Twitter at &lt;a href="https://twitter.com/mblayman"&gt;@mblayman&lt;/a&gt; so I can try to respond to your question.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Publish to DEV automatically with GitHub Actions</title>
      <dc:creator>Matt Layman</dc:creator>
      <pubDate>Mon, 28 Jun 2021 20:13:19 +0000</pubDate>
      <link>https://dev.to/mblayman/publish-to-dev-automatically-with-github-actions-jm1</link>
      <guid>https://dev.to/mblayman/publish-to-dev-automatically-with-github-actions-jm1</guid>
      <description>&lt;p&gt;&lt;a href="https://dev.to/"&gt;DEV&lt;/a&gt; is a great community for developer content. If you have articles that you don't want live all at once, how can you publish on a schedule automatically? In this article, let's use GitHub Actions to get your content online on your timeline.&lt;/p&gt;

&lt;p&gt;GitHub Actions is a way to run code on GitHub's servers. The service is effectively a Continuous Integration (CI) service from GitHub. To use GitHub Actions, we create a &lt;em&gt;workflow&lt;/em&gt; in a Git repository. That workflow contains all the steps to run the code we care about.&lt;/p&gt;

&lt;p&gt;To publish to DEV, we can:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use a workflow...&lt;/li&gt;
&lt;li&gt;To read a publishing schedule we create...&lt;/li&gt;
&lt;li&gt;And check the current time...&lt;/li&gt;
&lt;li&gt;And publish an article to DEV if the publish date is in the past.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Before we get to the workflow, let's look at the code that will run.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a schedule
&lt;/h2&gt;

&lt;p&gt;The first step in this whole process is to create a publishing schedule that our script can read. Minimally, that requires two types of data from a list of articles:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The ID of the article&lt;/li&gt;
&lt;li&gt;The datetime to publish at&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So, where do we get those things?&lt;/p&gt;

&lt;p&gt;The first datum comes from your actual unpublished articles. From my observation, the DEV website does not expose this ID in an obvious fashion from the UI, but it's not hard to find.&lt;/p&gt;

&lt;p&gt;From your unpublished article page, view the source of the page. In both Firefox and Chrome, you can get to this by selecting "View Page Source" when you right click somewhere on the page.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F175tecyxa58vymk2q2ok.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F175tecyxa58vymk2q2ok.png" alt="View Page Source on Firefox"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the source page, search for &lt;code&gt;article-id&lt;/code&gt; with &lt;code&gt;Ctrl+F&lt;/code&gt; (or &lt;code&gt;Cmd+F&lt;/code&gt; on macOS). You should find a number value like &lt;code&gt;168727&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now we have what we need to build a schedule. We'll use the JSON format for the schedule because Python can read that format easily. You can name the file whatever you want, but I called mine &lt;code&gt;devto-schedule.json&lt;/code&gt; and stored it at the root of my repository.&lt;/p&gt;

&lt;p&gt;We also need a datetime when we want to publish the article. I used the ISO-8601 standard to make dates that we can compare to the current time. To continue our example, if I want to publish the article on October 22, 2019 at 2pm Eastern Time in the US, then the schedule looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;168727&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"publish_date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2019-10-22T18:00:00+00:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Ep 11"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;167526&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"publish_date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2019-10-15T18:00:00+00:00"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Ep 10"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We've got the ID as &lt;code&gt;id&lt;/code&gt; and the datetime as &lt;code&gt;publish_date&lt;/code&gt;. The &lt;code&gt;publish_date&lt;/code&gt; uses UTC to store the time. Timezones are hard so I would always recommend storing your datetimes in UTC.&lt;/p&gt;

&lt;p&gt;I also included a &lt;code&gt;title&lt;/code&gt; so I know what article the object refers to in the future. &lt;code&gt;title&lt;/code&gt; isn't necessary, but it's useful for tracking the articles.&lt;/p&gt;

&lt;p&gt;Notice that this is a JSON list. I included an extra article so you can see what this would look like if the script checks multiple articles (which is kind of the point).&lt;/p&gt;

&lt;h2&gt;
  
  
  Publishing to DEV
&lt;/h2&gt;

&lt;p&gt;With the schedule in hand, we're ready to look at the code that will do all the work. I'll start by showing the whole script, and we'll break it down into each section.&lt;/p&gt;

&lt;p&gt;First, the main file, &lt;code&gt;publish.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Publish scheduled articles to DEV.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dateutil.parser&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;parse&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;devto&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;DEVGateway&lt;/span&gt;

&lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;publish_scheduled_articles&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;dev_gateway&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;DEVGateway&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;published_articles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dev_gateway&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_published_articles&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;published_article_ids&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;article&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
                                &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;article&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;published_articles&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="n"&gt;scheduled_articles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_scheduled_articles&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;scheduled_article&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;scheduled_articles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;should_publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scheduled_article&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;published_article_ids&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;article_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;scheduled_article&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Publishing article with id: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;article_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;dev_gateway&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;article_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_scheduled_articles&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Get the schedule.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Read schedule.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;devto-schedule.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;should_publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;article&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;published_article_ids&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Check if the article should be published.

    This depends on if the date is in the past
    and if the article is already published.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;publish_date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;article&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;publish_date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;publish_date&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;article&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;published_article_ids&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;basicConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;%(levelname)s: %(message)s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;INFO&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;publish_scheduled_articles&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then the supporting DEV file, &lt;code&gt;devto.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DEVGateway&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://dev.to/api&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Session&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;api-key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DEV_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_published_articles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Get all the published articles.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/articles/me/published&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;per_page&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;article_id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Publish the article.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;article&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;published&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/articles/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;article_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                    &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The top down view
&lt;/h3&gt;

&lt;p&gt;Let's start our examination with the entry point at the bottom of the &lt;code&gt;publish.py&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;basicConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;%(levelname)s: %(message)s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;INFO&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;publish_scheduled_articles&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code follows Python's popular pattern of checking the &lt;code&gt;__name__&lt;/code&gt; attribute to see if it is &lt;code&gt;__main__&lt;/code&gt;. This will be true when the code runs with &lt;code&gt;python3 publish.py&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Since the script is going to run in Continuous Integration, we configure the logging to output to stdout. Running on stdout is necessary because we don't have easy access to files generated from GitHub Actions, including log files.&lt;/p&gt;

&lt;p&gt;The next call is to &lt;code&gt;publish_scheduled_articles&lt;/code&gt;. I could have called this &lt;code&gt;main&lt;/code&gt;, but I chose to give it a descriptive name. We'll continue working top down to see what &lt;code&gt;publish_scheduled_articles&lt;/code&gt; does.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;publish_scheduled_articles&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;dev_gateway&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;DEVGateway&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;published_articles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dev_gateway&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_published_articles&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;published_article_ids&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;article&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
                                &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;article&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;published_articles&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="n"&gt;scheduled_articles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_scheduled_articles&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;scheduled_article&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;scheduled_articles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;should_publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scheduled_article&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;published_article_ids&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;article_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;scheduled_article&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Publishing article with id: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;article_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;dev_gateway&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;article_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The overall flow of this function can be described as:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Get what is already published to DEV.&lt;/li&gt;
&lt;li&gt;Get what is scheduled for publishing.&lt;/li&gt;
&lt;li&gt;Publish a scheduled article if it should be published.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To see what is published on DEV, we need to dig into the next layer and see how we communicate with DEV.&lt;/p&gt;

&lt;h3&gt;
  
  
  A gateway to DEV
&lt;/h3&gt;

&lt;p&gt;DEV has a JSON API that developers can interact with. To keep a clean abstraction layer, I used the &lt;a href="https://martinfowler.com/eaaCatalog/gateway.html" rel="noopener noreferrer"&gt;Gateway Pattern&lt;/a&gt; to hide the details of how that API communication happens. By doing that, the script interacts with a high level interface (like &lt;code&gt;get_published_articles&lt;/code&gt;) rather than fiddling with the &lt;code&gt;requests&lt;/code&gt; module directly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DEVGateway&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://dev.to/api&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Session&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;api-key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;DEV_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_published_articles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Get all the published articles.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/articles/me/published&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;per_page&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When we create a gateway instance, we start a &lt;code&gt;requests.Session&lt;/code&gt; that is configured with the proper API key. We'll talk about how GitHub gets access to that API key later, but you'll need to create a key on the DEV website. You can find this under your Settings/Account section.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F7h34c181g1wj94baype1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F7h34c181g1wj94baype1.png" alt="Generating an API key"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With the key available, we can ask DEV about our own stuff. I used the &lt;code&gt;/articles/me/published&lt;/code&gt; API to get the list of published articles. The published articles are required to check that the script is idempotent (which means that we can safely run it repeatedly without suffering from weird side effects).&lt;/p&gt;

&lt;p&gt;Since the gateway is small, let's look at its other method before moving on.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;article_id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Publish the article.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;article&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;published&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/articles/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;article_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                    &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the script confirms that an article is ready to publish, we change the state of the article by setting the &lt;code&gt;published&lt;/code&gt; attribute to &lt;code&gt;True&lt;/code&gt;. We do this state modification with an HTTP PUT.&lt;/p&gt;

&lt;p&gt;The other bit of code worth discussing is &lt;code&gt;response.raise_for_status()&lt;/code&gt;. Truthfully, I was a bit lazy when writing this part of the script. &lt;code&gt;raise_for_status&lt;/code&gt; will raise an exception whenever the response is not a &lt;code&gt;200 OK&lt;/code&gt;. I put it in as a safeguard in case something about the API changes in the future. The part that's missing (and the laziness that I mentioned) is the handling of those exceptions.&lt;/p&gt;

&lt;p&gt;When a request failure occurs, this script is going to fail hard and dump out a traceback. From my point of view, &lt;strong&gt;that's fine&lt;/strong&gt;. Since this will be part of a CI job, the only person seeing the traceback will be me, and I will want whatever data the traceback reports. This is one of those scenarios where a developer tool can have a sharper edge than production-caliber code and be perfectly reasonable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Checking the schedule
&lt;/h3&gt;

&lt;p&gt;Now we've seen how the gateway works. We can move our attention to the schedule.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_scheduled_articles&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Get the schedule.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Read schedule.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;devto-schedule.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This might be the most vanilla code in the entire script. This code reads the JSON file which has our schedule of articles. The file handle is passed to &lt;code&gt;load&lt;/code&gt; to create the list of articles with a future publication date.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;scheduled_article&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;scheduled_articles&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;should_publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scheduled_article&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;published_article_ids&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;article_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;scheduled_article&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Publishing article with id: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;article_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;dev_gateway&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;article_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is the heart of the script. We:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Loop through the schedule.&lt;/li&gt;
&lt;li&gt;Check if we should publish an article.&lt;/li&gt;
&lt;li&gt;Publish the article if we should.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Why did I pull the list of published articles earlier? Now we get our answer when examining &lt;code&gt;should_publish&lt;/code&gt; which needs the published articles.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;should_publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;article&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;published_article_ids&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Check if the article should be published.

    This depends on if the date is in the past
    and if the article is already published.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;publish_date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;article&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;publish_date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;publish_date&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;article&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;published_article_ids&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are actually two factors to consider when checking a scheduled article.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Is the article's publication date in the past from now?&lt;/li&gt;
&lt;li&gt;Has the script already published the article?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That second question is less obvious until you see that this CI job is running regularly. Because we'll configure the script to run every hour, we want to ensure that we don't try to publish an article multiple times. &lt;code&gt;should_publish&lt;/code&gt; guards against multiple publishing by checking if the article's &lt;code&gt;id&lt;/code&gt; is in the list of published IDs.&lt;/p&gt;

&lt;p&gt;Now you've seen from top to bottom how this script can publish articles to DEV. Our next step is to hook the script into GitHub Actions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running on GitHub Actions
&lt;/h2&gt;

&lt;p&gt;GitHub Actions works by defining workflows in a &lt;code&gt;.github/workflows&lt;/code&gt; directory in your repository. The workflow file is a YAML file with the commands to do the work.&lt;/p&gt;

&lt;p&gt;We're calling our workflow &lt;code&gt;.github/workflows/devto-publisher.yaml&lt;/code&gt;, but you can name it whatever you want as long as the extension ends with &lt;code&gt;.yaml&lt;/code&gt; or &lt;code&gt;.yml&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;As before, let's look at the entire workflow file and break down the pieces to get an understanding of what's happening.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DEV Publisher&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;cron&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*'&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;publish&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Publish to DEV&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Get the code&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;fetch-depth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Set up Python&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-python@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;python-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.x'&lt;/span&gt;
          &lt;span class="na"&gt;architecture&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;x64'&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install packages&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pip install -r requirements/pub.txt&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run publisher&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python bin/publish.py&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;DEV_API_KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.DEV_API_KEY }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can optionally give your workflow a &lt;code&gt;name&lt;/code&gt;. I like adding a name because it's friendlier for me to read in the logs over the filename. If filenames suit you better, skip it!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;cron&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;on&lt;/code&gt; section is critical and defines when your action will occur. There are tons of different events you can use. For this workflow, we need the &lt;code&gt;schedule&lt;/code&gt; trigger. All the gory details about it are in the &lt;a href="https://help.github.com/en/articles/events-that-trigger-workflows#scheduled-events-schedule" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you're familiar with cron, this syntax won't shock you. Non-cron users might wonder what the heck is going on. The docs explains all the syntax, but this particular string of &lt;code&gt;0 * * * *&lt;/code&gt; means "run on the zeroth minute of every hour of every day." In other words, "run at the start of every hour."&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;publish&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Publish to DEV&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All workflows need at least one job. Without a job, there is nothing for the action to do. I've named this job &lt;code&gt;publish&lt;/code&gt;, given it a friendly name, and defined what operating system to run on (namely, Ubuntu).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Get the code&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;fetch-depth&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Set up Python&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-python@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;python-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.x'&lt;/span&gt;
          &lt;span class="na"&gt;architecture&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;x64'&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install packages&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pip install -r requirements/pub.txt&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run publisher&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python bin/publish.py&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;DEV_API_KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.DEV_API_KEY }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The job runs a series of &lt;code&gt;steps&lt;/code&gt;. Again, I tried to name each step so that we can get a readable flow of what is happening. If I extract the names, we get:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Get the code&lt;/li&gt;
&lt;li&gt;Set up Python&lt;/li&gt;
&lt;li&gt;Install packages&lt;/li&gt;
&lt;li&gt;Run publisher&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The first two steps in the process pull from pre-built actions developed by GitHub. GitHub gives us these actions to save time from figuring these things out ourselves. For instance, without the &lt;code&gt;actions/checkout@v1&lt;/code&gt; action, how would we get a local clone? It would be really challenging.&lt;/p&gt;

&lt;p&gt;The third step starts to use configuration specific to my job. In order for the script to run, it needs dependencies installed so we can import &lt;code&gt;requests&lt;/code&gt; and &lt;code&gt;dateutil&lt;/code&gt;. I defined a &lt;code&gt;requirements/pub.txt&lt;/code&gt; file that includes all the needed dependencies.&lt;/p&gt;

&lt;p&gt;Here's what the requirements file looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#
# This file is autogenerated by pip-compile
# To update, run:
#
#    pip-compile requirements/pub.in
#
certifi==2019.9.11        # via requests
chardet==3.0.4            # via requests
idna==2.8                 # via requests
python-dateutil==2.8.0
requests==2.22.0
six==1.12.0               # via python-dateutil
urllib3==1.25.3           # via requests
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, we get to the step that runs the publisher.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run publisher&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python bin/publish.py&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;DEV_API_KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.DEV_API_KEY }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is pretty vanilla except for the &lt;code&gt;DEV_API_KEY&lt;/code&gt; line. Look back at the &lt;code&gt;DEVGateway.__init__&lt;/code&gt; method. The method pulls from &lt;code&gt;os.environ&lt;/code&gt; to get the API key. In our YAML file, we're wiring the &lt;code&gt;DEV_API_KEY&lt;/code&gt; environment variable to the &lt;code&gt;secrets.DEV_API_KEY&lt;/code&gt;. We need to tell GitHub about our API key.&lt;/p&gt;

&lt;p&gt;In your GitHub repository settings, we can define secrets. These secrets are available to workflow files as we see from the "Run publisher" step. Create a secret named &lt;code&gt;DEV_API_KEY&lt;/code&gt; that contains your DEV API key.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fq2t1mnykltzvj89nt8v1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fq2t1mnykltzvj89nt8v1.png" alt="GitHub secrets"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now everything is in place to run the publisher. This workflow will execute every hour and run the &lt;code&gt;publish&lt;/code&gt; job. That job will setup the environment and execute the &lt;code&gt;publish.py&lt;/code&gt; script. The script will call DEV and publish any articles in your schedule that should be published.&lt;/p&gt;

&lt;h2&gt;
  
  
  What did we learn?
&lt;/h2&gt;

&lt;p&gt;In this article, I tried to show off a few different topics.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;How to communicate with the DEV API&lt;/li&gt;
&lt;li&gt;How to create a file that a computer can read and parse (i.e., the JSON schedule)&lt;/li&gt;
&lt;li&gt;How to use GitHub actions to run some code for your repository.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Want to see all this code in context? The &lt;a href="https://github.com/mblayman/mattlayman.com" rel="noopener noreferrer"&gt;repository&lt;/a&gt; for this website has all the files we covered.&lt;/p&gt;

&lt;p&gt;There are so many other things you can do with GitHub Actions. Now you can write about them all and put them on a schedule for other developers to read when you're ready.&lt;/p&gt;

&lt;p&gt;If you have questions or enjoyed this article, please feel free to message me on Twitter at &lt;a href="https://twitter.com/mblayman" rel="noopener noreferrer"&gt;@mblayman&lt;/a&gt; or share if others might be interested too.&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
