<?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: J.V. Zammit</title>
    <description>The latest articles on DEV Community by J.V. Zammit (@jvzammit).</description>
    <link>https://dev.to/jvzammit</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%2F426853%2Fa9fd087d-6de1-4df7-9fe9-52c791cd0267.jpg</url>
      <title>DEV Community: J.V. Zammit</title>
      <link>https://dev.to/jvzammit</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jvzammit"/>
    <language>en</language>
    <item>
      <title>A minimal Django testing style guide</title>
      <dc:creator>J.V. Zammit</dc:creator>
      <pubDate>Fri, 01 Oct 2021 15:15:42 +0000</pubDate>
      <link>https://dev.to/jvzammit/a-minimal-django-testing-style-guide-1np3</link>
      <guid>https://dev.to/jvzammit/a-minimal-django-testing-style-guide-1np3</guid>
      <description>&lt;p&gt;You started on a &lt;a href="https://en.wikipedia.org/wiki/Minimum_viable_product" rel="noopener noreferrer"&gt;MVP&lt;/a&gt;. Usage is increasing. The occasional bug.&lt;/p&gt;

&lt;p&gt;More users. Suddenly this MVP is not an MVP anymore. A core business process depends on this system your team is maintaining.&lt;/p&gt;

&lt;p&gt;You ship a user-reported bug fix. Users report another two. Sounds familiar?&lt;/p&gt;

&lt;p&gt;Since it was an MVP, you paid little attention to writing automated tests.&lt;/p&gt;

&lt;p&gt;What is the current codebase's test coverage? Wait. Do you even measure it?&lt;/p&gt;

&lt;p&gt;This post is not about setting the gold standard on how to write tests for your Django application.&lt;/p&gt;

&lt;p&gt;It's about setting a simple direction as possible. And getting started. Wherever you are.&lt;/p&gt;

&lt;p&gt;In this I also an attempt to document some of my practices around writing tests for Django projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Terminology
&lt;/h2&gt;

&lt;p&gt;As in this &lt;a href="https://blog.thea.codes/my-python-testing-style-guide/" rel="noopener noreferrer"&gt;Python testing style guide&lt;/a&gt; I followed in the past:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I do not make a distinction between &lt;strong&gt;unit tests&lt;/strong&gt; and &lt;strong&gt;integration tests&lt;/strong&gt;. I generally refer to these types of tests as unit tests. I do not go out of my way to isolate a single unit of code and test it without consideration of the rest of the project. I do try to isolate a single unit of &lt;em&gt;behaviour&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Where do I place tests?
&lt;/h2&gt;

&lt;p&gt;The greatest advantage of having tests is that they are &lt;em&gt;living docs&lt;/em&gt; about your code. The inputs/outputs tested either work as expected or they don't. They cannot go outdated. Unlike written docs.&lt;/p&gt;

&lt;p&gt;So it's important to have an obvious way to tell where the test for a piece of code resides in your codebase.&lt;/p&gt;

&lt;p&gt;pytest's &lt;a href="https://docs.pytest.org/en/latest/explanation/goodpractices.html#conventions-for-python-test-discovery" rel="noopener noreferrer"&gt;conventions for Python test discovery&lt;/a&gt; are simple to agree on and follow.&lt;/p&gt;

&lt;p&gt;Let's say you have an &lt;code&gt;Article&lt;/code&gt; model in app &lt;code&gt;articles&lt;/code&gt; at &lt;code&gt;articles/models.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="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Article&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="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tests for this model class would go in &lt;code&gt;articles/tests/test_models.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="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ArticleTest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TestCase&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In a similar fashion, if we have a function in &lt;code&gt;articles/utils.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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;do_something&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;its tests would go in &lt;code&gt;articles/tests/test_utils.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="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DoSomethingTest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TestCase&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;test_do_something&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="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why create a &lt;code&gt;DoSomethingTest&lt;/code&gt; extending a &lt;code&gt;TestCase&lt;/code&gt;? Rather than a standalone test function?&lt;/p&gt;

&lt;p&gt;The default way to write tests in Django is using Python's built-in &lt;a href="https://docs.python.org/3/library/unittest.html" rel="noopener noreferrer"&gt;unittest module&lt;/a&gt;. This defines tests using a class-based approach.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;django.test.TestCase&lt;/code&gt; is in fact a subclass of &lt;code&gt;unittest.TestCase&lt;/code&gt; that runs each test inside a transaction to provide database-oriented goodies. Such as transaction isolation. Django's &lt;a href="https://docs.djangoproject.com/en/dev/topics/testing/overview/" rel="noopener noreferrer"&gt;writing and running tests&lt;/a&gt; docs provide more details on this.&lt;/p&gt;

&lt;h3&gt;
  
  
  Naming test classes
&lt;/h3&gt;

&lt;p&gt;As in the case of "placing" a test in your codebase, a &lt;code&gt;TestCase&lt;/code&gt;'s name should be indicative of the class (or standalone function) being tested. So for classes:&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;Article&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;model&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="bp"&gt;...&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ArticleTest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TestCase&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and for functions:&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;do_something&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;

&lt;span class="bp"&gt;...&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DoSomethingTest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TestCase&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;test_do_something&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="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Naming test functions
&lt;/h3&gt;

&lt;p&gt;As mentioned above, your individual tests should test for a &lt;em&gt;single unit of behaviour&lt;/em&gt;. In the below case, behaviour depends on the data (state) the logic depends 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;class&lt;/span&gt; &lt;span class="nc"&gt;Article&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;title&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="nc"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;slug&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="nc"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;unique&lt;/span&gt;&lt;span class="o"&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;content&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="nc"&gt;TextField&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;__str__&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="k"&gt;return&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;title&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_short_title&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="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&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;title&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;15&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;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;return&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;title&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So we have to test &lt;code&gt;get_short_title&lt;/code&gt; based on the &lt;code&gt;title&lt;/code&gt;'s length. The more "explicit" the name the better. Have test function names that reflect the state relevant for the expected result:&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;ArticleTest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TestCase&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;test_get_short_title_title_is_longer_than_15_characters_yes&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="bp"&gt;...&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_get_short_title_title_is_longer_than_15_characters_no&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="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice how the whole class name to function name structure tells us:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;name of class &lt;code&gt;Article&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;name of function &lt;code&gt;get_short_title&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;state (input) determining behaviour (output) per test&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This also makes obvious what should be changed in case you move/rename classes/functions. Drawn out it looks like this:&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%2Funtangled.dev%2Fassets%2Fimg%2Fposts%2F2021-08-22-dj-testing-styleguide%2Fmodel-code-and-tests-layout.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%2Funtangled.dev%2Fassets%2Fimg%2Fposts%2F2021-08-22-dj-testing-styleguide%2Fmodel-code-and-tests-layout.png" alt="Model code tests and layout"&gt;&lt;/a&gt;url="" %}&lt;/p&gt;

&lt;p&gt;Below is a screenshot of my setup. With code on the left, and tests code on the right. No fancy screen required. I took this on a laptop screen:&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%2Funtangled.dev%2Fassets%2Fimg%2Fposts%2F2021-08-22-dj-testing-styleguide%2Fmodel-code-and-tests-layout.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%2Funtangled.dev%2Fassets%2Fimg%2Fposts%2F2021-08-22-dj-testing-styleguide%2Fmodel-code-and-tests-layout.png" alt="Code and tests in Visual Studio Code"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I find this a game changer when reading code. Seeing a function's inputs and expected outputs next to the function's code speeds things up. &lt;em&gt;A lot.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating test data
&lt;/h2&gt;

&lt;p&gt;Use &lt;a href="https://en.wikipedia.org/wiki/Factory_(object-oriented_programming)" rel="noopener noreferrer"&gt;factory&lt;/a&gt; libraries to standardise test data creation.&lt;/p&gt;

&lt;p&gt;The most popular two in Django are &lt;a href="https://factoryboy.readthedocs.io/en/stable/index.html" rel="noopener noreferrer"&gt;factory-boy&lt;/a&gt; and &lt;a href="https://model-bakery.readthedocs.io/en/latest/" rel="noopener noreferrer"&gt;model bakery&lt;/a&gt;. The latter is simpler, so we use it in this article's examples. But the former is more feature-rich.&lt;/p&gt;

&lt;p&gt;As a guiding principle keep your factory code &lt;em&gt;light&lt;/em&gt;. I.e. specify only the fields that need to be set for the state required by the test.&lt;/p&gt;

&lt;p&gt;For example, when creating the &lt;code&gt;article&lt;/code&gt; to test &lt;code&gt;__str__&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="n"&gt;article&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;baker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;articles.Article&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Test Title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;we are not passing &lt;code&gt;slug&lt;/code&gt; or &lt;code&gt;description&lt;/code&gt;. These would be noise for our test's purpose.&lt;/p&gt;

&lt;p&gt;The way we create data via factories should be about the data state expected by the test. No more, no less.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Why not just use fixtures?&lt;/em&gt; You may already have &lt;a href="https://docs.djangoproject.com/en/dev/howto/initial-data/#providing-data-with-fixtures" rel="noopener noreferrer"&gt;fixtures&lt;/a&gt;, with a lot of data as expected in production, lying around.&lt;/p&gt;

&lt;p&gt;Fixtures are an overkill for tests because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Maintaining them is hell. For example, what if you rename a model field?&lt;/li&gt;
&lt;li&gt;Maintaining fixtures is usually append-only. So the fixture's data increases over time, loading a lot of data the test doesn't need, &lt;em&gt;every time&lt;/em&gt; the test is run.&lt;/li&gt;
&lt;li&gt;Fixtures make it hard to tell what data is being loaded. It's even &lt;em&gt;harder&lt;/em&gt; to tell why a fixture is loading the data it is loading in the context of tests!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I find eliminating fixtures in tests is one of the most effective steps to speed up a test suite's runtime.&lt;/p&gt;

&lt;p&gt;Not convinced? Section &lt;code&gt;11.1&lt;/code&gt; in &lt;a href="(https://gumroad.com/a/233305203)"&gt;Speed up your Django Tests&lt;/a&gt; delves into more detail why fixture files should just be avoided.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing views
&lt;/h2&gt;

&lt;p&gt;The above basic examples were all about testing models. Models are the usual starting point in writing a Django app.&lt;/p&gt;

&lt;p&gt;But a lot of logic resides in views. And the forms and/or serializers that go with them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Unit vs integration, again
&lt;/h3&gt;

&lt;p&gt;Django and DRF provide the &lt;a href="https://docs.djangoproject.com/en/dev/topics/testing/tools/#the-test-client" rel="noopener noreferrer"&gt;test client&lt;/a&gt; and &lt;a href="https://www.django-rest-framework.org/api-guide/testing/#apiclient" rel="noopener noreferrer"&gt;APIClient&lt;/a&gt; to make HTTP calls against your application programmatically.&lt;/p&gt;

&lt;p&gt;The problem with making full HTTP calls against your application is that not only your view code wiill run. HTTP requests pass through the whole Django &lt;a href="https://docs.djangoproject.com/en/3.2/topics/http/middleware/" rel="noopener noreferrer"&gt;middleware&lt;/a&gt; machinery, including, for example, authentication.&lt;/p&gt;

&lt;p&gt;So when using the test client, you would be doing more of an integration test, rather than a unit test.&lt;/p&gt;

&lt;p&gt;The alternative would be instantiating views. And passing instances of &lt;a href="https://docs.djangoproject.com/en/dev/topics/testing/advanced/#django.test.RequestFactory" rel="noopener noreferrer"&gt;&lt;code&gt;RequestFactory&lt;/code&gt;&lt;/a&gt;, or &lt;a href="https://www.django-rest-framework.org/api-guide/testing/#apirequestfactory" rel="noopener noreferrer"&gt;&lt;code&gt;APIRequestFactory&lt;/code&gt;&lt;/a&gt; in DRF, directly to the view functions. Same approach applies for serializer (or form) functions.&lt;/p&gt;

&lt;p&gt;Which way do you go? Ask yourself a couple of questions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Is your current test suite running slow?&lt;/li&gt;
&lt;li&gt;Do you have a lot of added middleware a request needs to go through?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If both answers are "no" (especially if you have no tests!) then the test client is likely enough to start with.&lt;/p&gt;

&lt;p&gt;The upside of making HTTP requests is taht you are testing the whole URL/view binding. Rather than a standalone class-based view function like &lt;code&gt;get_queryset&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Let's say a FE engineer on your team asks you a question about how a particular URL is supposed to work. Having tests with the test client allows you to be 100% sure of what the view at that URL is supposed to do. This is an example of what I meant above by &lt;em&gt;living docs&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to structure your view tests
&lt;/h3&gt;

&lt;p&gt;When using the test client I find it helpful to think of my application as a "black box":&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Assuming there is this data set up (inputs)&lt;/li&gt;
&lt;li&gt;What should happen if I submit this request to this URL? (action)&lt;/li&gt;
&lt;li&gt;Assert response returned (output)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The pattern is the one when writing tests for functions. But in this case rather than calling the specific function under test, the test makes an HTTP request.&lt;/p&gt;

&lt;p&gt;Let's say we want CRUD views for our &lt;code&gt;Article&lt;/code&gt; model. This is what we'd find in &lt;code&gt;articles/serializers.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="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ArticleSerializer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;serializers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ModelSerializer&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;Meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Article&lt;/span&gt;
        &lt;span class="n"&gt;fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__all__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And in &lt;code&gt;articles/views.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="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ArticleListCreateAPIView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;generics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ListCreateAPIView&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;queryset&lt;/span&gt; &lt;span class="o"&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;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;serializer_class&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ArticleSerializer&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ArticleDetailAPIView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;generics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RetrieveUpdateDestroyAPIView&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;queryset&lt;/span&gt; &lt;span class="o"&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;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;serializer_class&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ArticleSerializer&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These are the tests we'd need to write to assert the API &lt;em&gt;behaves&lt;/em&gt; as expected:&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%2Fwww.untangled.dev%2Fassets%2Fimg%2Fposts%2F2021-08-22-dj-testing-styleguide%2Fviews-code-and-tests-layout.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%2Fwww.untangled.dev%2Fassets%2Fimg%2Fposts%2F2021-08-22-dj-testing-styleguide%2Fviews-code-and-tests-layout.png" alt="Views code and tests layout"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We do not need to write tests for the serializer, as it is used by the views.&lt;/p&gt;

&lt;h3&gt;
  
  
  Testing configuration
&lt;/h3&gt;

&lt;p&gt;In many cases, as shown above, and especially when using &lt;a href="https://docs.djangoproject.com/en/dev/topics/class-based-views/" rel="noopener noreferrer"&gt;class-based views&lt;/a&gt;, functionality is achieved through configuration of classes rather than coding. &lt;sup id="fnref1"&gt;1&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;How to test in this case? The quickest way, i.e. the  &lt;em&gt;minimal&lt;/em&gt; in terms of &lt;em&gt;time&lt;/em&gt;, is to test using the client.&lt;/p&gt;

&lt;p&gt;Let's extend our view to be able to order articles by &lt;code&gt;title&lt;/code&gt;. The easiest option would be to extend &lt;code&gt;ArticleListCreateAPIViewTest.test_list&lt;/code&gt; to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;create test data with different &lt;code&gt;title&lt;/code&gt;s to be able to assert results order&lt;/li&gt;
&lt;li&gt;call the URL with &lt;code&gt;ordering&lt;/code&gt; parameter&lt;/li&gt;
&lt;li&gt;test outcome&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;View code:&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;ArticleListCreateAPIView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;generics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ListCreateAPIView&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;queryset&lt;/span&gt; &lt;span class="o"&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;objects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;serializer_class&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ArticleSerializer&lt;/span&gt;
    &lt;span class="n"&gt;filter_backends&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;filters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OrderingFilter&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;ordering_fields&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;title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Test code:&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;ArticleListCreateAPIViewTest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;APITestCase&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;test_list&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;article1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;baker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;articles.Article&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Title A&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;article2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;baker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;articles.Article&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Title C&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;article3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;baker&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;articles.Article&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Title B&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# default ordering by id:
&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;client&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/api/articles/&lt;/span&gt;&lt;span class="sh"&gt;"&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="nf"&gt;assertEqual&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="n"&gt;status_code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTP_200_OK&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="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;article1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;article2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;article3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# ordering by title:
&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;client&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/api/articles/?ordering=title&lt;/span&gt;&lt;span class="sh"&gt;"&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="nf"&gt;assertEqual&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="n"&gt;status_code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTP_200_OK&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="nf"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;article1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;article3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;article2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&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;While testing "code owned by others" these tests ensure that the API provided works as expected.&lt;/p&gt;

&lt;h2&gt;
  
  
  Diving deeper
&lt;/h2&gt;

&lt;p&gt;This guide is intended to be minimal. As such it misses going into some important aspects of writing tests.&lt;/p&gt;

&lt;h3&gt;
  
  
  Test runners other than &lt;code&gt;TestCase&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Django's &lt;code&gt;TestCase&lt;/code&gt; extends &lt;code&gt;unittest&lt;/code&gt;'s &lt;code&gt;TestCase&lt;/code&gt;. And DRF's &lt;code&gt;APITestCase&lt;/code&gt; extends Django's &lt;code&gt;TestCase&lt;/code&gt;. This guide does not go into other test runners like &lt;code&gt;pytest&lt;/code&gt;. Which take a more functional approach.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mocking
&lt;/h3&gt;

&lt;p&gt;What if your code needs to read data from an external API? Or upload a file to S3?&lt;/p&gt;

&lt;p&gt;If the test needs to interact with external systems, it would become a "system" test. A test that depends on components "outside" the local environment where it runs.&lt;/p&gt;

&lt;p&gt;Your automated tests should &lt;em&gt;always&lt;/em&gt; &lt;strong&gt;mock&lt;/strong&gt; such interactions.&lt;/p&gt;

&lt;p&gt;Getting into mocking is out of this style guide's scope. The tool Python provides is &lt;a href="https://docs.python.org/3/library/unittest.mock.html" rel="noopener noreferrer"&gt;unittest.mock&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Coverage
&lt;/h3&gt;

&lt;p&gt;On its own, &lt;code&gt;coverage&lt;/code&gt; is a "dumb" measure of how many lines in your codebase are "covered by" tests. Why dumb? You can write a bunch HTTP GET requests in your tests, write &lt;em&gt;zero&lt;/em&gt; assertions, and increase coverage &lt;em&gt;a lot&lt;/em&gt;. That, of course, doesn't add any value.&lt;/p&gt;

&lt;p&gt;So do &lt;em&gt;not&lt;/em&gt; write tests for coverage.&lt;/p&gt;

&lt;p&gt;Rather use &lt;a href="https://coverage.readthedocs.io/en/latest/" rel="noopener noreferrer"&gt;coverage.py&lt;/a&gt; to identify which lines, i.e. &lt;em&gt;behaviour&lt;/em&gt;, your tests do not cover.&lt;/p&gt;

&lt;p&gt;Call me naive, but I still strive for &lt;a href="https://adamj.eu/tech/2019/04/30/getting-a-django-application-to-100-percent-coverage/" rel="noopener noreferrer"&gt;100% coverage&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;But how to get started?&lt;/p&gt;

&lt;p&gt;In any project/team I work with, I fight for a non-negative diff coverage on every PR on CI. Diff coverage &lt;a href="https://diff-cover.readthedocs.io/en/latest/README.html#overview" rel="noopener noreferrer"&gt;definition&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Diff coverage is the percentage of new or modified lines that are covered by tests. This provides a clear and achievable standard for code review: If you touch a line of code, that line should be covered. Code coverage is every developer’s responsibility!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Over time you'll get to 100% coverage 🚀&lt;/p&gt;

&lt;h3&gt;
  
  
  Speeding tests up
&lt;/h3&gt;

&lt;p&gt;In this guide we go over creating test data using factories as opposed to fixtures. But that's just it.&lt;/p&gt;

&lt;p&gt;In the "views" section I mentioned avoiding rendering responses to only test the behaviour "under test".&lt;/p&gt;

&lt;p&gt;This is scratching the surface. &lt;a href="https://gumroad.com/a/233305203" rel="noopener noreferrer"&gt;Speed Up Your Django Tests&lt;/a&gt; by &lt;a href="https://adamj.eu/" rel="noopener noreferrer"&gt;Adam Johnson&lt;/a&gt; is the best resource on this topic.&lt;/p&gt;

&lt;p&gt;And not only. It is in fact a great resource when starting to write tests from scratch. The below chapters, in particular, are an excellent guide:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Chapter 10, &lt;em&gt;Test Structure&lt;/em&gt;, on how to organise your tests' code.&lt;/li&gt;
&lt;li&gt;Chatper 11, &lt;em&gt;Test Data&lt;/em&gt;, on best practices in managing data for your tests, in the fastest way possible.&lt;/li&gt;
&lt;li&gt;Chapter 12, &lt;em&gt;Targetted Mocking&lt;/em&gt;, on how to mock behaviour the right way.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  References / Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.python.org/3/library/unittest.html" rel="noopener noreferrer"&gt;unittest module&lt;/a&gt; - Python's standard library.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.djangoproject.com/en/dev/topics/testing/" rel="noopener noreferrer"&gt;Testing in Django&lt;/a&gt; - Django docs.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://blog.thea.codes/my-python-testing-style-guide/" rel="noopener noreferrer"&gt;My Python testing style guide&lt;/a&gt; -  &lt;a href="https://thea.codes/" rel="noopener noreferrer"&gt;Thea "Stargirl" Flowers&lt;/a&gt;. Main inspiration for this article.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/octoenergy/conventions/blob/master/python.md#testing" rel="noopener noreferrer"&gt;Python Style Guide&lt;/a&gt; - &lt;a href="https://tech.octopus.energy/" rel="noopener noreferrer"&gt;Octopus Energy&lt;/a&gt;. This style guide is a gold mine for any Python project. As in my article's case, no need to treat it as dogma. But it provides insight into how a large organisation, with presumably a large codebase, handles its "style guide". Kudos for sharing!&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://web.archive.org/web/20190113090920/https://www.mxsasha.eu/blog/2014/09/11/why-and-how-test-coverage-100/" rel="noopener noreferrer"&gt;Why and how I get 100% test coverage for my Django projects, and you should too&lt;/a&gt;. &lt;a href="https://www.mxsasha.eu/" rel="noopener noreferrer"&gt;Sasha Romjin&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://gumroad.com/a/233305203" rel="noopener noreferrer"&gt;Speed Up Your Django Tests&lt;/a&gt; - &lt;a href="https://adamj.eu/" rel="noopener noreferrer"&gt;Adam Johnson&lt;/a&gt;. Can't recommend this book enough!&lt;/li&gt;
&lt;/ul&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;Aka &lt;a href="https://en.wikipedia.org/wiki/Convention_over_configuration" rel="noopener noreferrer"&gt;convention over configuration&lt;/a&gt;. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>django</category>
      <category>python</category>
      <category>testing</category>
    </item>
    <item>
      <title>Backend Engineer Interview Script</title>
      <dc:creator>J.V. Zammit</dc:creator>
      <pubDate>Tue, 08 Jun 2021 09:24:18 +0000</pubDate>
      <link>https://dev.to/jvzammit/backend-engineer-interview-script-122i</link>
      <guid>https://dev.to/jvzammit/backend-engineer-interview-script-122i</guid>
      <description>&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%2Fuploads%2Farticles%2F4w9kmxpjbjsogaqyyx22.jpeg" 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%2Fuploads%2Farticles%2F4w9kmxpjbjsogaqyyx22.jpeg" alt="Dwight Schrute, Assistant *to* the Regional Manager. Dunder Mifflin Inc. Credit: NBC."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Last month I've written a post with &lt;a href="https://www.untangled.dev/2021/05/22/django-engineer-interview-script/" rel="noopener noreferrer"&gt;Django-specific interview questions&lt;/a&gt;. That post is about verifying a candidate’s depth of knowledge with Django.&lt;/p&gt;

&lt;p&gt;This post is about less specific questions I ask a candidate. &lt;em&gt;Before&lt;/em&gt; proceeding to the Django specific ones. If I do proceed after all. In case the candidate does not have any Django-specific experience, what's the point?&lt;/p&gt;

&lt;p&gt;These questions do not look for specific answers. Rather the aim is to open up a discussion. So the questions are more in the form of &lt;em&gt;how&lt;/em&gt; or &lt;em&gt;why&lt;/em&gt; rather than &lt;em&gt;what&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;And no. &lt;em&gt;How do you describe yourself?&lt;/em&gt; is not one of these questions. If you ever get that question, the only correct answer is that by Dwight Schrute as shown above.&lt;/p&gt;

&lt;p&gt;The questions in this article revolve around:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;improving web application response time&lt;/li&gt;
&lt;li&gt;designing authentication&lt;/li&gt;
&lt;li&gt;handling asynchronous work / task queues&lt;/li&gt;
&lt;li&gt;HTTP REST API design&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  1. Server response time
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Q: In the context a dynamic "data driven" web application, how would you decrease a page's response time?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Asking this should allow the candidate to come up with multiple answers. Among the common answers you should note the below for further discussion:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;caching&lt;/li&gt;
&lt;li&gt;database query optimisation&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Caching
&lt;/h3&gt;

&lt;p&gt;Caching provides a form of "low hanging fruit" to speed up serving a response in web applications.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: Where can you cache results in a Web application?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Allow the candidate to go into the multiple levels of caching a Web application offers.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Brownie points&lt;/em&gt; for mentioning:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;caching HTTP response via HTTP headers, i.e. &lt;code&gt;cache-control&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;caching response via web server&lt;/li&gt;
&lt;li&gt;server-side in memory caching&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;cache-control&lt;/code&gt; &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control" rel="noopener noreferrer"&gt;HTTP header&lt;/a&gt; allows caching resources across various points on the network Reference docs on &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching" rel="noopener noreferrer"&gt;HTTP caching&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Web server caching caches resources on the server end of the network. The &lt;a href="https://docs.nginx.com/nginx/admin-guide/content-cache/content-caching/" rel="noopener noreferrer"&gt;Nginx cache&lt;/a&gt; is a fine example.&lt;/p&gt;

&lt;p&gt;Server-side caching using an in-memory cache as memcached or redis is another option.&lt;/p&gt;

&lt;p&gt;Caching can also be enabled at "database layer". Through "views" or outright database denormalisation. Ideally we do not go there during this interview.&lt;/p&gt;

&lt;p&gt;Caching though comes with its own challenges. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: What are the tradeoffs of caching?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Discuss expiration or "freshness" methods. For any of the caching methods the candidates brought up for the previous question. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: When would serving stale data be incorrect?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Some scenarios are better suited to caching. Think Wikipedia. Others are not. Think live football scores. How to optimise the latter?&lt;/p&gt;

&lt;p&gt;Segway the discussion into &lt;em&gt;query optimisation&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Database Query Optimisation
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Q: What makes a query "slow"?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Any query whose result can be retrieved faster.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Brownie points&lt;/em&gt; the candidate mentions redundant outer joins. &lt;a href="https://blog.codinghorror.com/a-visual-explanation-of-sql-joins/" rel="noopener noreferrer"&gt;A Visual Explanation of SQL Joins&lt;/a&gt; by Jeff Atwood provides a great revision on this topic.&lt;/p&gt;

&lt;p&gt;Once the candidate mentions &lt;em&gt;indexes&lt;/em&gt; the question is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: How to decide which columns to index?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Brownie points&lt;/em&gt; for mentioning:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Column &lt;em&gt;selectivity&lt;/em&gt;. For example a &lt;code&gt;gender&lt;/code&gt; column with a few values is not a good candidate for an index. Whereas a unique &lt;code&gt;id&lt;/code&gt; column is.&lt;/li&gt;
&lt;li&gt;The field type. For instance, a numeric integer type is index-friendly. A "text" field type is not. Because it will result in a large index which takes computing resources to scan.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2. Authentication
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Q: We have a monolith central web app where users data is stored and managed. And we have several microservices that need to authenticate those users. How would you go about it?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;At the time of writing &lt;a href="https://jwt.io/" rel="noopener noreferrer"&gt;JWT&lt;/a&gt; is the way to go.&lt;/p&gt;

&lt;p&gt;Still, any answer involving "tokens" and token-based authentication should be good. Such an approach allows not only authenticating requests for the microservices above. It is also a great way to authenticate frontend requests.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Task queues and async work
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Q: Our users need to generate reports on the fly. But an individual report takes several minutes to generate. How would you structure this in a web application?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Allow the candidate to distinguish between the synchronous part of this feature. And async parts.&lt;/p&gt;

&lt;p&gt;One such way would be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Web request queues task to generate report (syncronhous).&lt;/li&gt;
&lt;li&gt;Report generation task writes file to storage, e.g. AWS S3 (async).&lt;/li&gt;
&lt;li&gt;Once the task writes the file strage, task sends out email with link to file (async).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This should give a nice opener to discuss task queues with the candidate.&lt;/p&gt;

&lt;p&gt;A lot has been said and written about task queues. I have written an article myself: &lt;a href="https://www.untangled.dev/2020/07/02/web-app-task-queue/" rel="noopener noreferrer"&gt;Do you need a task queue in your web app?&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Allow the candidate explain how the task queue works in this context:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: What are the basic components for async task processing in a web context?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Answer should cover these basic building blocks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the web server process "producing" tasks&lt;/li&gt;
&lt;li&gt;the task queue component managing the tasks' lifecycle&lt;/li&gt;
&lt;li&gt;workers "consuming" or "executing" those tasks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Brownie points&lt;/em&gt; for getting the basics right.&lt;/p&gt;

&lt;p&gt;Do you think the candidate has several years' experience dealing with queues? You can dig deeper. How?&lt;/p&gt;

&lt;p&gt;David Yanacek at AWS has written on &lt;a href="https://aws.amazon.com/builders-library/avoiding-insurmountable-queue-backlogs/" rel="noopener noreferrer"&gt;strategies to deal with queue backlog scenarios&lt;/a&gt;. This provides some excellent talking points:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In a queue-based system, when processing stops but messages keep arriving, the message debt can accumulate into a large backlog, driving up processing time. Work can be completed too late for the results to be useful, essentially causing the availability hit that queueing was meant to guard against.&lt;/p&gt;
&lt;p&gt;Putting it another way, a queue-based system has two modes of operation, or bimodal behavior. When there is no backlog in the queue, the system’s latency is low, and the system is in fast mode. But if a failure or unexpected load pattern causes the arrival rate to exceed the processing rate, it quickly flips into a more sinister operating mode. In this mode, the end-to-end latency grows higher and higher, and it can take a great deal of time to work through the backlog to return to the fast mode.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The above should motivate the next question:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: How do you make sure that tasks are being executed quicker than they are being queued?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There is no one correct answer. The point of this is to allow the candidate to explore alternatives with you. Which is what you'd be doing together in case you go on with the candidate and they join the team.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Brownie points&lt;/em&gt; if the candidate mentions making tasks &lt;em&gt;idempotent&lt;/em&gt;. I.e. have the task achieve the same result, whether it runs 1 time or x times. This removes a lot of headaches in managing a task queue.&lt;/p&gt;

&lt;p&gt;Explore with the candidate &lt;em&gt;how&lt;/em&gt; they would go about achieving this.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. REST API design
&lt;/h2&gt;

&lt;p&gt;Most, if not all, web applications, provide some form of REST API backend component. A "rich" frontend consumes this REST API. Such rich frontend is usually implemented by the "hot" JS framework at the time. At the time of writing, among the hottest we &lt;em&gt;arguably&lt;/em&gt; find React and VueJS.&lt;/p&gt;

&lt;p&gt;Candidates submit some code as part of the recruitment process. This coding assignment has the candidates add functionality to an existing REST API. I.e. in the form of new URIs.&lt;/p&gt;

&lt;p&gt;Based on submitted assignment discuss their choices made in designing URLs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Endpoint names. Hint: use nouns rather than verbs. And name collections with plural nouns.&lt;/li&gt;
&lt;li&gt;Endpoint ability to allow filtering, sorting, and pagination.&lt;/li&gt;
&lt;li&gt;Request and response encoding. Hint: use JSON.&lt;/li&gt;
&lt;li&gt;Proper usage of &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods" rel="noopener noreferrer"&gt;HTTP methods&lt;/a&gt; or "verbs".&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Couple of excellent resources on REST API design best practices:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://stackoverflow.blog/2020/03/02/best-practices-for-rest-api-design/" rel="noopener noreferrer"&gt;Best practices for REST API design&lt;/a&gt; as written up by StackOverflow engineers.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://swagger.io/resources/articles/best-practices-in-api-design/" rel="noopener noreferrer"&gt;Best Practices in API Design&lt;/a&gt; as documented in &lt;a href="https://swagger.io/" rel="noopener noreferrer"&gt;Swagger&lt;/a&gt; docs.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  To Conclude
&lt;/h2&gt;

&lt;p&gt;The "brownie points" above pinpoint some "good answers". If the candidate does not mention those it's &lt;em&gt;not&lt;/em&gt; a problem.&lt;/p&gt;

&lt;p&gt;We are humans not search engines. We forget stuff. Especially during interviews.&lt;/p&gt;

&lt;p&gt;Keep in mind the candidate can answer questions well even when the answer is not the one you expect.&lt;/p&gt;

&lt;p&gt;You can use the comments box below to let me know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;about anything I'm missing&lt;/li&gt;
&lt;li&gt;how I can improve this interview script&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;May the interviews you're involved in lead to a better life!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>career</category>
    </item>
    <item>
      <title>Django Engineer Interview Script</title>
      <dc:creator>J.V. Zammit</dc:creator>
      <pubDate>Sat, 22 May 2021 13:29:58 +0000</pubDate>
      <link>https://dev.to/jvzammit/django-engineer-interview-script-591c</link>
      <guid>https://dev.to/jvzammit/django-engineer-interview-script-591c</guid>
      <description>&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%2Fuploads%2Farticles%2Fh5wc5khd4v5klgkisfdq.jpg" 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%2Fuploads%2Farticles%2Fh5wc5khd4v5klgkisfdq.jpg" alt="Michael Scott, Dunder Mifflin Inc. World's Best Boss. Credit: NBC."&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Unlike &lt;a href="https://en.wikipedia.org/wiki/Michael_Scott_(The_Office)" rel="noopener noreferrer"&gt;Michael Scott&lt;/a&gt; above I'm not a manager.&lt;/p&gt;

&lt;p&gt;Yet I have worked as a Python/Django backend engineer for more than a decade. This puts me in a "senior" role in any team I usually join.&lt;/p&gt;

&lt;p&gt;As a result, I get involved in the recruitment process. I.e. to look for someone who would then become a team mate.&lt;/p&gt;

&lt;p&gt;This post is about questions I ask a prospective Python/Django backend engineer. A prospect with 2+ years of experience doing Python/Django backend development full-time.&lt;/p&gt;

&lt;p&gt;The prospective engineer would be working in &lt;em&gt;my&lt;/em&gt; team. &lt;em&gt;With&lt;/em&gt; me. Not &lt;em&gt;for&lt;/em&gt; me.&lt;/p&gt;

&lt;p&gt;So the questions aim to help me to answer the fundamental "&lt;em&gt;Would I want to work with this person?&lt;/em&gt;" question.&lt;/p&gt;

&lt;p&gt;Below I go through what I look for in each question. Note that unlike a &lt;a href="https://en.wikipedia.org/wiki/HackerRank" rel="noopener noreferrer"&gt;"hackerrank" style&lt;/a&gt; assessment, these questions look for &lt;em&gt;qualitative&lt;/em&gt; attributes.&lt;/p&gt;

&lt;p&gt;Answers do not have a completely correct/wrong answer. The way in which the candidate answers is more important than the answer itself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Update 2021-06-06.&lt;/strong&gt; These questions are &lt;em&gt;Python/Django-specific&lt;/em&gt;. They intend to center discussion about Django-specific knowledge and experience. I have written another &lt;a href="https://www.untangled.dev/2021/06/06/backend-engineer-interview-script/" rel="noopener noreferrer"&gt;backend engineer interview script&lt;/a&gt; which is technology agnostic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Self-intro
&lt;/h2&gt;

&lt;p&gt;Allow the candidate to go over their career trajectory. Have them delve deep into some projects they are proud to have been part of. Allow them to expand on specific processes/tools used.&lt;/p&gt;

&lt;p&gt;This will provide you with loose ends to relate the below questions with.&lt;/p&gt;

&lt;h2&gt;
  
  
  Q1. How do you use the _ character in Python?
&lt;/h2&gt;

&lt;p&gt;Objective is to see how the candidate reacts.&lt;/p&gt;

&lt;p&gt;Among the correct answers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Used in snake case (i.e. function names and variable names)&lt;/li&gt;
&lt;li&gt;Used to store a function return value which is going to go unused.&lt;/li&gt;
&lt;li&gt;Used to "show" that a function is meant to be used as a "private" function.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Q1 Follow-up: in case "private function" use case is brought up
&lt;/h3&gt;

&lt;p&gt;The followup is a trick question:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;How does Python enforce private properties/functions?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The answer is: &lt;em&gt;It does not.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Probe the candidate to make sure she knows what she'd use it for, in Python.&lt;/p&gt;

&lt;p&gt;Look for honesty in the candidate's answer. An "I don't know" is more than acceptable. Better than making stuff up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Q2. Which test data generation toolkit have you used?
&lt;/h2&gt;

&lt;p&gt;Usually the candidate will have mentioned tests in CV or during their own self-intro. It's hard to have good tests without proper management of test data.&lt;/p&gt;

&lt;p&gt;This is an open ended question about how the candidate handled test data. Answer demonstrates whether the candidate has written tests or not. Look for familiarity with tools in the Python/Django testing landscape.&lt;/p&gt;

&lt;h3&gt;
  
  
  Q2 Follow-up: How to improve unit tests speed in Django?
&lt;/h3&gt;

&lt;p&gt;A mature application is one that grows beyond that ideal “micro service” or “side project” size. Along size and complexity, one common aspect is tests taking longer to run.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://adamj.eu/" rel="noopener noreferrer"&gt;Adam Johnson&lt;/a&gt; has written a whole &lt;a href="https://gumroad.com/a/233305203" rel="noopener noreferrer"&gt;Speed Up Your Django Tests&lt;/a&gt; book on topic. Which I fully recommend.&lt;/p&gt;

&lt;p&gt;Do not look for that level of detail though. A couple “quick ways” to improve speed is fine. I.e. low-hanging fruit. Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Avoid using fixtures in tests.&lt;/li&gt;
&lt;li&gt;Avoid creating more test data than the test requires.&lt;/li&gt;
&lt;li&gt;Change the default Django password hasher. Etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The way the candidate answers this shows their “battle scars” in writing tests for Django applications.&lt;/p&gt;

&lt;p&gt;Also look out for “quick hacks”. For example, changing the tests to run against sqlite rather than say Postgres or MariaDB. This should &lt;em&gt;not&lt;/em&gt; be one of those “quick ways”.&lt;/p&gt;

&lt;p&gt;Be ready, especially when it comes to allocated time, to discuss any disagreement. Not only in this question. If the candidate doesn't make it to the next round, they still would have learnt something.&lt;/p&gt;

&lt;h2&gt;
  
  
  Q3. Which packages you "carry around" in your Django projects?
&lt;/h2&gt;

&lt;p&gt;Djangonauts tend to have some favourite frameworks that they end up using in all their Django projects.&lt;/p&gt;

&lt;p&gt;Popular answers include &lt;a href="https://www.django-rest-framework.org/" rel="noopener noreferrer"&gt;DRF&lt;/a&gt;, &lt;a href="https://django-extensions.readthedocs.io/en/latest/#" rel="noopener noreferrer"&gt;django-extensions&lt;/a&gt;, &lt;a href="https://github.com/anymail/django-anymail" rel="noopener noreferrer"&gt;anymail&lt;/a&gt;, &lt;a href="https://django-allauth.readthedocs.io/en/latest/" rel="noopener noreferrer"&gt;allauth&lt;/a&gt; and the &lt;a href="https://django-debug-toolbar.readthedocs.io/en/latest/" rel="noopener noreferrer"&gt;DJDT&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Q3 Follow-up: How do you decide whether to use a package or reinvent the wheel?
&lt;/h3&gt;

&lt;p&gt;This again is an open ended question. Aim is to see the candidate's experience in making this sort of decision when building out a project. Good considerations include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does this package actually fit the use case? Or at least, is it very close to the use case?&lt;/li&gt;
&lt;li&gt;Set up/installation complexity, especially given architecture of existing application. And other installed moving parts which might not play well with the package.&lt;/li&gt;
&lt;li&gt;Security concerns.&lt;/li&gt;
&lt;li&gt;Activity around package on Github, i.e. recent commits, contributors, responses to issues, etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Q4. Which code standards do you follow for a Django project? Tools?
&lt;/h2&gt;

&lt;p&gt;Ideally any of these are mentioned: &lt;a href="https://www.python.org/dev/peps/pep-0008/" rel="noopener noreferrer"&gt;PEP 8&lt;/a&gt;, &lt;a href="https://flake8.pycqa.org/en/latest/" rel="noopener noreferrer"&gt;flake8&lt;/a&gt;, &lt;a href="https://github.com/psf/black" rel="noopener noreferrer"&gt;black&lt;/a&gt;, &lt;a href="https://docs.djangoproject.com/en/dev/internals/contributing/writing-code/coding-style/" rel="noopener noreferrer"&gt;Django style guide&lt;/a&gt;, &lt;a href="https://pypi.org/project/isort/" rel="noopener noreferrer"&gt;isort&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;There is no correct answer. Just checking for "awareness".&lt;/p&gt;

&lt;p&gt;Keeping in mind for example that &lt;code&gt;black&lt;/code&gt; and Django's own style guide are not 100% compatible. The specifics are subjective.&lt;/p&gt;

&lt;p&gt;Look for familiarity with a tool or a couple of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Q5. Which deployment tools have you used? To ship code to production?
&lt;/h2&gt;

&lt;p&gt;Probe the candidate to know more whether they have been exposed to “ops” kind of work. Or at least gauge whether there’s an interest.&lt;/p&gt;

&lt;p&gt;Feel free to discuss tools used at your current post, example AWS, Ansible, Python's Fabric library, &lt;a href="https://www.untangled.dev/2020/06/06/docker-django-local-dev/" rel="noopener noreferrer"&gt;Docker for local development&lt;/a&gt;, etc.&lt;/p&gt;

&lt;p&gt;The motivation behind Q5 is that I work in environments where some “ops” skills alongside Python/Django skills are appreciated.&lt;/p&gt;

&lt;p&gt;But you might be looking for a full-stack person. Then replace this question with another which shows the person's reaction about some FE-oriented work aspect.&lt;/p&gt;

&lt;p&gt;On open-ended questions as these the candidate being opinionated is &lt;em&gt;not&lt;/em&gt; a bad thing.&lt;/p&gt;

&lt;p&gt;Discussing topics you do not have full agreement on is revealing. For both sides of the interview process. Which is what you want.&lt;/p&gt;

&lt;h2&gt;
  
  
  To conclude
&lt;/h2&gt;

&lt;p&gt;Remember to encourage the candidate to ask questions throughout. This again enables the two-way information street we want.&lt;/p&gt;

&lt;p&gt;I hope you find these useful.&lt;/p&gt;

&lt;p&gt;Are there any other questions I should ask? Why? Feel free to add your suggestions as a comment.&lt;/p&gt;

&lt;p&gt;This post was inspired by &lt;a href="https://jacobian.org/" rel="noopener noreferrer"&gt;Jacob Kaplan-Moss&lt;/a&gt;'s article: &lt;a href="https://jacobian.org/2018/nov/29/annotated-interview-kickoff-script/" rel="noopener noreferrer"&gt;My interview kickoff script, annotated&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I suggest you give that article a look. Tread carefully. You might find yourself down in &lt;a href="https://jacobian.org/tags/interviewing/" rel="noopener noreferrer"&gt;Jacob's interviewing series rabbit hole&lt;/a&gt;. Some excellent nuggets of knowledge shared down there.&lt;/p&gt;

</description>
      <category>python</category>
      <category>django</category>
    </item>
    <item>
      <title>A minimal Websockets setup with Django in production</title>
      <dc:creator>J.V. Zammit</dc:creator>
      <pubDate>Tue, 04 Aug 2020 09:47:44 +0000</pubDate>
      <link>https://dev.to/jvzammit/a-minimal-websockets-setup-with-django-in-production-5gd3</link>
      <guid>https://dev.to/jvzammit/a-minimal-websockets-setup-with-django-in-production-5gd3</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--gru-kavU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.untangled.dev/assets/img/posts/2020-08-02-django-websockets/websockets.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--gru-kavU--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.untangled.dev/assets/img/posts/2020-08-02-django-websockets/websockets.jpg" alt="That's me wiring up my cool JS grid to autoload data changes via server side push. Yeah! Image credit: thedisgruntledsherpaproject.com"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Use Case
&lt;/h2&gt;

&lt;p&gt;I have a &lt;a href="https://handsontable.com/"&gt;Handsontable&lt;/a&gt; implementation for an underlying database table. I.e. a “JavaScript data grid that looks and feels like a spreadsheet”.&lt;/p&gt;

&lt;p&gt;A requirement came up. Obvious in hindsight.&lt;/p&gt;

&lt;p&gt;Changes made to cells in one sheet should be reflected in the same sheet for other users. When the sheet is open in other browser tabs/windows.&lt;/p&gt;

&lt;p&gt;This calls for "server side push" using &lt;a href="https://en.wikipedia.org/wiki/WebSocket"&gt;web sockets&lt;/a&gt;. I.e. the server needs to &lt;a href="https://en.wikipedia.org/wiki/Push_technology"&gt;push a notification to open "clients"&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Another way to do this would be to have client browsers Ajax-polling for changes. But that would be wasteful. Let's only update the sheet when valid changes are saved!&lt;/p&gt;

&lt;h2&gt;
  
  
  What does &lt;em&gt;minimal&lt;/em&gt; mean in this case?
&lt;/h2&gt;

&lt;p&gt;This application is used by a team internally. Usage not exceeding ten concurrent users. The implications of this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;One&lt;/em&gt; process to serve web socket requests is enough.&lt;/li&gt;
&lt;li&gt;No real performance testing was done.&lt;/li&gt;
&lt;li&gt;No consideration of alternatives to &lt;code&gt;daphne&lt;/code&gt; such as &lt;a href="https://www.uvicorn.org/"&gt;uvicorn&lt;/a&gt; or &lt;a href="https://github.com/encode/starlette"&gt;starlette&lt;/a&gt;. I picked up &lt;code&gt;daphne&lt;/code&gt; because it came up "first on the list of alternatives". That's it!&lt;/li&gt;
&lt;li&gt;No need to handle websocket interactions asynchronously in my case. You can read more about going sync vs async with websockets in Django channels &lt;a href="https://channels.readthedocs.io/en/latest/topics/consumers.html#basic-layout"&gt;here&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This configuration remains unchanged until problems crop up. Because “premature optimisation is the root of all evil”.&lt;/p&gt;

&lt;h2&gt;
  
  
  Blueprint: Before &amp;amp; After
&lt;/h2&gt;

&lt;p&gt;Note: &lt;code&gt;http&lt;/code&gt; request protocol below refers both to &lt;code&gt;http&lt;/code&gt; and &lt;code&gt;https&lt;/code&gt;. Same applies to &lt;code&gt;ws&lt;/code&gt; and &lt;code&gt;wss&lt;/code&gt;. Assume that for local development the protocol is unsecure, whilst secure in production.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Before&lt;/em&gt; introducing websockets, the web browser made an &lt;code&gt;http&lt;/code&gt; request to Nginx. At this point Nginx serves the request using &lt;code&gt;gunicorn&lt;/code&gt;, hitting Django&lt;sup id="fnref1"&gt;1&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;After&lt;/em&gt; adding websockets in the mix, Nginx still serves &lt;code&gt;http&lt;/code&gt; requests. But it's now able to serve &lt;code&gt;ws&lt;/code&gt; requests by talking to &lt;code&gt;daphne&lt;/code&gt;. In this case you can replace &lt;code&gt;daphne&lt;/code&gt; with any other Websocket termination server:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--p6SRFmk6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.untangled.dev/assets/img/posts/2020-08-02-django-websockets/blueprint.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--p6SRFmk6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://www.untangled.dev/assets/img/posts/2020-08-02-django-websockets/blueprint.png" alt="Blueprint: before &amp;amp; after web sockets"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The "new" item in the building blocks above is therefore &lt;a href="https://github.com/django/daphne/"&gt;daphne&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Daphne is a HTTP, HTTP2 and WebSocket protocol server for ASGI and ASGI-HTTP, developed to power Django Channels.&lt;/p&gt;

&lt;p&gt;It supports automatic negotiation of protocols; there's no need for URL prefixing to determine WebSocket endpoints versus HTTP endpoints.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The &lt;code&gt;daphne&lt;/code&gt; component can be replaced with alternatives as &lt;a href="https://www.uvicorn.org/"&gt;uvicorn&lt;/a&gt; or &lt;a href="https://github.com/encode/starlette"&gt;starlette&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The other item of note is that &lt;code&gt;ws://&lt;/code&gt; connections are an "open" connection. "Data" travels in both directions &lt;em&gt;along&lt;/em&gt; the same socket. As opposed to &lt;code&gt;http://&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code changes
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;daphne&lt;/code&gt; is part of the &lt;a href="https://github.com/django/channels"&gt;Django Channels&lt;/a&gt; effort:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Channels augments Django to bring WebSocket, long-poll HTTP, task offloading and other async support to your code, using familiar Django design patterns and a flexible underlying framework that lets you not only customize behaviours but also write support for your own protocols and needs.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I've followed &lt;a href="https://channels.readthedocs.io/en/latest/"&gt;the docs&lt;/a&gt;. The &lt;a href="https://channels.readthedocs.io/en/latest/installation.html"&gt;installation&lt;/a&gt; and excellent &lt;a href="https://channels.readthedocs.io/en/latest/tutorial/index.html"&gt;tutorial&lt;/a&gt; sections helped me get everything to work locally.&lt;/p&gt;

&lt;p&gt;For completeness' sake, these code changes are for an installation using these package versions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;channels==2.4.0
channels-redis==3.0.1
Django==3.0.8
redis==3.5.3
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;in a Python &lt;code&gt;3.7.5&lt;/code&gt; virtualenv.&lt;/p&gt;

&lt;p&gt;The changes needed:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;settings.py&lt;/code&gt; changes. These:

&lt;ul&gt;
&lt;li&gt;add &lt;code&gt;channels&lt;/code&gt; to &lt;code&gt;INSTALLED_APPS&lt;/code&gt;, and&lt;/li&gt;
&lt;li&gt;configure &lt;code&gt;channels&lt;/code&gt; to route websocket requests to the main channels entrypoint&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Routing code changes:

&lt;ul&gt;
&lt;li&gt;main project-level routing entrypoint&lt;/li&gt;
&lt;li&gt;app level entrypoint(s), in this example using just one example &lt;code&gt;myapp&lt;/code&gt; as app&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;The &lt;a href="https://channels.readthedocs.io/en/latest/topics/consumers.html"&gt;consumer&lt;/a&gt; that hosts all the event-handling and message sending logic our app needs to implement.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  1. settings module changes
&lt;/h3&gt;

&lt;p&gt;Added &lt;code&gt;channels&lt;/code&gt; as &lt;em&gt;the first&lt;/em&gt; app in my project's list of &lt;code&gt;INSTALLED_APPS&lt;/code&gt;. Why first?&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Please be wary of any other third-party apps that require an overloaded or replacement &lt;code&gt;runserver&lt;/code&gt; command. Channels provides a separate &lt;code&gt;runserver&lt;/code&gt; command and may conflict with it. An example of such a conflict is with &lt;code&gt;whitenoise.runserver_nostatic&lt;/code&gt; from whitenoise. In order to solve such issues, try moving channels to the top of your &lt;code&gt;INSTALLED_APPS&lt;/code&gt; or remove the offending app altogether.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Then added this new setting for &lt;code&gt;channels&lt;/code&gt; app to use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# CHANNELS
ASGI_APPLICATION = 'proj.routing.application'
CHANNEL_LAYERS = {
    'default': {
        'BACKEND': 'channels_redis.core.RedisChannelLayer',
        'CONFIG': {
            'hosts': [('127.0.0.1', 6379)],
        },
    },
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Note that I already had &lt;code&gt;redis&lt;/code&gt; installed for &lt;a href="https://docs.djangoproject.com/en/dev/topics/cache/"&gt;caching&lt;/a&gt; and the application's existing &lt;a href="https://www.untangled.dev/2020/07/01/huey-minimal-task-queue-django/"&gt;task queue with Huey&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Routing changes
&lt;/h3&gt;

&lt;p&gt;I usually call the "default" app &lt;code&gt;proj&lt;/code&gt;. This makes it obvious that the app is a container for project-wide items. As is the case with this new &lt;code&gt;routing&lt;/code&gt; module. It contains the &lt;a href="https://channels.readthedocs.io/en/latest/topics/routing.html#protocoltyperouter"&gt;ProtocolTypeRouter&lt;/a&gt; that serves as main entry point for the ASGI application. &lt;code&gt;proj/routing.py&lt;/code&gt; contents:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
import myapp.routing

application = ProtocolTypeRouter({
    # (http-&amp;gt;django views is added by default)
    'websocket': AuthMiddlewareStack(
        URLRouter(
            myapp.routing.websocket_urlpatterns
        )
    ),
})
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;code&gt;myapp&lt;/code&gt; is the test app used for this exmaple. The top-level router above contains a reference to a &lt;code&gt;myapp.routing&lt;/code&gt; module. This &lt;a href="https://channels.readthedocs.io/en/latest/topics/routing.html#urlrouter"&gt;URLRouter&lt;/a&gt; routes &lt;code&gt;http&lt;/code&gt; or &lt;code&gt;websocket&lt;/code&gt; type connections via their HTTP path. &lt;code&gt;myapp/routing.py&lt;/code&gt; contains the below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from django.urls import re_path

from myapp import consumers

websocket_urlpatterns = [
    re_path(r'ws/sheet/(?P&amp;lt;sheet_name&amp;gt;\w+)/$', consumers.SheetConsumer),
]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Note how &lt;code&gt;channels&lt;/code&gt; allows us to structure our web socket URLs in the already familiar format we're used for standard &lt;code&gt;urls.py&lt;/code&gt; configuration&lt;sup id="fnref2"&gt;2&lt;/sup&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. The consumer
&lt;/h3&gt;

&lt;p&gt;The final module that needs adding is the &lt;a href="https://channels.readthedocs.io/en/latest/topics/consumers.html"&gt;consumer&lt;/a&gt;. In &lt;code&gt;channels&lt;/code&gt;, consumers:&lt;/p&gt;

&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Structure your code as a series of functions to be called whenever an event happens, rather than making you write an event loop.&lt;/li&gt;
&lt;li&gt;Allow you to write synchronous or async code and deals with handoffs and threading for you.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;code&gt;myapp/consumers.py&lt;/code&gt; implements a &lt;code&gt;SheetConsumer&lt;/code&gt; class which extends &lt;a href="https://channels.readthedocs.io/en/latest/topics/consumers.html#websocketconsumer"&gt;WebsocketConsumer&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import json
from asgiref.sync import async_to_sync
from channels.generic.websocket import WebsocketConsumer


class SheetConsumer(WebsocketConsumer):

    def connect(self):
        self.sheet_name = self.scope['url_route']['kwargs']['sheet_name']
        self.sheet_group_name = 'sheet_%s' % self.sheet_name

        # Join sheet group
        async_to_sync(self.channel_layer.group_add)(
            self.sheet_group_name,
            self.channel_name
        )
        self.accept()

    def disconnect(self, close_code):
        # Leave sheet group
        async_to_sync(self.channel_layer.group_discard)(
            self.sheet_group_name,
            self.channel_name
        )

    # Receive message from WebSocket
    def receive(self, text_data):
        text_data_json = json.loads(text_data)

        # Send sheet_name to sheet group
        async_to_sync(self.channel_layer.group_send)(
            self.sheet_group_name,
            {
                'type': 'refresh_sheet',
                'sheet_name': text_data_json['sheet_name'],
                'object_id': text_data_json['object_id'],
                'column_index': text_data_json['column_index'],
                'new_value': text_data_json['new_value'],
                'broadcaster_id': text_data_json['broadcaster_id'],
            }
        )

    # Receive message from sheet group
    def refresh_sheet(self, event):
        # Send sheet_name to WebSocket
        self.send(text_data=json.dumps({
            'sheet_name': event['sheet_name'],
            'object_id': event['object_id'],
            'column_index': event['column_index'],
            'new_value': event['new_value'],
            'broadcaster_id': event['broadcaster_id'],
        }))
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The above is based on the &lt;a href="https://channels.readthedocs.io/en/latest/tutorial/part_2.html#write-your-first-consumer"&gt;Write your first consumer&lt;/a&gt; tutorial section. Instead of chat messages, the data is about a sheet's cell updates. Updates that need to be applied &lt;em&gt;for the same sheet&lt;/em&gt; open in other browser tabs/windows.&lt;/p&gt;

&lt;p&gt;I'm passing the user ID of the authenticated user in &lt;code&gt;broadcaster_id&lt;/code&gt;. To be able to tell which user "triggered" the websocket message being "broadcasted".&lt;/p&gt;

&lt;h3&gt;
  
  
  Give it a try locally
&lt;/h3&gt;

&lt;p&gt;This is another great feature of &lt;code&gt;channels&lt;/code&gt;. Not even your usual &lt;code&gt;manage.py runserver&lt;/code&gt; workflow needs to change. Just note the new item in your default &lt;code&gt;runserver&lt;/code&gt; output when it starts:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ ./manage.py runserver
Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).
August 01, 2020 - 16:07:41
Django version 3.0.8, using settings 'proj.settings.local'
Starting ASGI/Channels version 2.4.0 development server at http://127.0.0.1:8000/    &amp;lt;&amp;lt; THIS!
Quit the server with CONTROL-C.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The socket handshakes are also shown in the output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WebSocket HANDSHAKING /ws/sheet/sheet1/ [127.0.0.1:65181]
WebSocket CONNECT /ws/sheet/sheet1/ [127.0.0.1:65181]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Awesome! Let's deploy!&lt;/p&gt;

&lt;h2&gt;
  
  
  Deployment notes
&lt;/h2&gt;

&lt;p&gt;Not so fast 😊&lt;/p&gt;

&lt;p&gt;I followed the &lt;code&gt;channels&lt;/code&gt; documentation &lt;a href="https://channels.readthedocs.io/en/latest/deploying.html"&gt;here&lt;/a&gt; alongside Django's own documentation on deploying ASGI applications &lt;a href="https://docs.djangoproject.com/en/3.0/howto/deployment/asgi/"&gt;here&lt;/a&gt;. But I applied &lt;del&gt;two&lt;/del&gt; three tweaks that I'd rather explain.&lt;/p&gt;

&lt;h3&gt;
  
  
  daphne command tweak
&lt;/h3&gt;

&lt;p&gt;I experienced the &lt;code&gt;CRITICAL Listen failure: [Errno 88] Socket operation on non-socket&lt;/code&gt; exception &lt;a href="https://github.com/django/daphne/issues/263"&gt;described here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This was fixed by following &lt;a href="https://github.com/django/daphne/issues/263#issuecomment-645860099"&gt;the suggestion to remove&lt;/a&gt; the &lt;code&gt;-fd 0&lt;/code&gt; switch. This switch is suggested &lt;a href="https://channels.readthedocs.io/en/latest/deploying.html#nginx-supervisor-ubuntu"&gt;by default in the channels docs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In the current use case I do not need to use this switch. Because I do not need to bind multiple Daphne instances to the same port my production instance. In case I do, I will need to change my structure (see next section) to have &lt;code&gt;daphne&lt;/code&gt; called directly from supervisor. Rather than via bash script.&lt;/p&gt;

&lt;h3&gt;
  
  
  asgi.py tweak
&lt;/h3&gt;

&lt;p&gt;I had implemented &lt;code&gt;proj/asgi.py&lt;/code&gt; as described in the &lt;a href="https://channels.readthedocs.io/en/latest/deploying.html#run-protocol-servers"&gt;channels docs here&lt;/a&gt;. This works fine locally. But it led to &lt;a href="https://stackoverflow.com/q/59908273/1211429"&gt;this exception described here&lt;/a&gt; when executing web socket requests in production. I changed the &lt;code&gt;proj/asgi.py&lt;/code&gt; as described in &lt;a href="https://stackoverflow.com/a/59909118/1211429"&gt;this stackoverflow answer&lt;/a&gt; which makes it have this content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import os
import django
from channels.routing import get_default_application

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'proj.settings')

django.setup()

application = get_default_application()
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;This change replaces usage of &lt;code&gt;django.core.asgi.get_asgi_application&lt;/code&gt; with &lt;code&gt;channels.routing.get_default_application&lt;/code&gt;. The stackoverflow answer above is supported as per &lt;code&gt;channels&lt;/code&gt;' docs &lt;a href="https://channels.readthedocs.io/en/latest/deploying.html#run-protocol-servers"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I do not know whether this fixes things &lt;em&gt;properly&lt;/em&gt;. Should I should have done something else? It appears to be a small incompatibility between &lt;code&gt;channels&lt;/code&gt; and Django docs. &lt;code&gt;channels&lt;/code&gt; docs suggest creating &lt;code&gt;asgi.py&lt;/code&gt; from scratch. While Django 3.0.8 auto-created &lt;code&gt;asgi.py&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you have a better resolution to this please let me know (comment below).&lt;/p&gt;

&lt;h3&gt;
  
  
  redis version
&lt;/h3&gt;

&lt;p&gt;Recall that my configuration is using &lt;a href="https://github.com/django/channels_redis/"&gt;channels_redis&lt;/a&gt; as backing store.&lt;/p&gt;

&lt;p&gt;Since my production application runs on Ubuntu 18.04 LTS, default &lt;code&gt;apt-get&lt;/code&gt; redis version was &lt;code&gt;4.0.1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This resulted in this weird &lt;code&gt;BZPOPMIN - "ERR unknown command 'BZPOPMIN'"&lt;/code&gt; error. This is because &lt;a href="https://github.com/redis/hiredis/issues/649#issuecomment-472095487"&gt;redis version 5 or higher is needed&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Therefore please upgrade redis for your configuration. In my case I've followed the &lt;a href="https://redis.io/topics/quickstart"&gt;quickstart docs&lt;/a&gt;, especially the &lt;a href="https://redis.io/topics/quickstart#installing-redis-more-properly"&gt;"Installing Redis more properly" section&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;On to the production config files!&lt;/p&gt;

&lt;h2&gt;
  
  
  Deployment - resulting configuration
&lt;/h2&gt;

&lt;p&gt;My configuration's components:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An executable bash script runs &lt;code&gt;daphne&lt;/code&gt;. I use this to be able to run and test &lt;code&gt;daphne&lt;/code&gt; directly in the Django project's virtualenv.&lt;/li&gt;
&lt;li&gt;A &lt;a href="http://supervisord.org/"&gt;supervisor&lt;/a&gt; &lt;code&gt;conf&lt;/code&gt; file to have this bash script process managed by supervisor.&lt;/li&gt;
&lt;li&gt;Nginx, of course.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Bash script
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;start_daphne.bash&lt;/code&gt; contents. Remember to &lt;code&gt;chmod +x&lt;/code&gt; your bash script.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/bin/bash

NAME="myproject-daphne"  # Name of the application
DJANGODIR=/home/ubuntu/webapp/myproject/proj  # Django project directory
DJANGOENVDIR=/home/ubuntu/webapp/myprojectenv  # Django project env

echo "Starting $NAME as `whoami`"

# Activate the virtual environment
cd $DJANGODIR
source /home/ubuntu/webapp/myprojectenv/bin/activate
source /home/ubuntu/webapp/myproject/proj/.env
export PYTHONPATH=$DJANGODIR:$PYTHONPATH

# Start daphne
exec ${DJANGOENVDIR}/bin/daphne -u /home/ubuntu/webapp/myprojectenv/run/daphne.sock --access-log - --proxy-headers proj.asgi:application
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  Supervisor
&lt;/h3&gt;

&lt;p&gt;File located at: &lt;code&gt;/etc/supervisor/conf.d/daphne.conf&lt;/code&gt;. Remember to create the log file directories.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;; ================================
;  daphne supervisor
; ================================

[program:daphne]
command = /home/ubuntu/webapp/start_daphne.bash  ; Command to start app

user = ubuntu   ; User to run as
numprocs=1

autostart=true
autorestart=true

redirect_stderr=true
stdout_logfile = /home/ubuntu/webapp/logs/daphne/access.log  ; Where to write access log messages
stderr_logfile = /home/ubuntu/webapp/logs/daphne/error.log  ; Where to write error log messages
stdout_logfile_maxbytes=50MB
stderr_logfile_maxbytes=50MB
stdout_logfile_backups=10
stderr_logfile_backups=10
environment=LANG=en_US.UTF-8,LC_ALL=en_US.UTF-8  ; Set UTF-8 as default encoding
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  Nginx
&lt;/h3&gt;

&lt;p&gt;Nginx configuration references I used: &lt;a href="https://channels.readthedocs.io/en/latest/deploying.html#nginx-supervisor-ubuntu"&gt;channels deployment docs&lt;/a&gt; and &lt;a href="https://stackoverflow.com/a/51566074/1211429"&gt;this answer on stackoverflow&lt;/a&gt;. Follow those links to understand what I did.&lt;/p&gt;

&lt;p&gt;Relevant Nginx config contents:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;upstream ws_server {
  server unix:/home/ubuntu/webapp/myprojectenv/run/daphne.sock fail_timeout=0;
}

upstream gunicorn_server {
  server unix:/home/ubuntu/webapp/myprojectenv/run/gunicorn.sock fail_timeout=0;
}

...

server {
  ...

  location /ws/ {
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_redirect off;
    proxy_pass http://ws_server;
  }

  location / {
    ...

    if (!-f $request_filename) {
        proxy_pass http://gunicorn_server;
        break;
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Note the newly-added &lt;code&gt;ws_server&lt;/code&gt;-related parts.&lt;/p&gt;

&lt;h3&gt;
  
  
  A note Markup/Javascript code
&lt;/h3&gt;

&lt;p&gt;This is not configuration as such. But as you can see the whole tutorial did not tackle &lt;code&gt;ws&lt;/code&gt; and &lt;code&gt;wss&lt;/code&gt; usage. One reason is that in this project's case the SSL certificate part is not handled by Nginx. Since the project is using Cloudflare SSL, Cloudflare takes care of it even "before Nginx".&lt;/p&gt;

&lt;p&gt;The only &lt;code&gt;ws&lt;/code&gt; vs &lt;code&gt;wss&lt;/code&gt; logic I have is done at client-side level. This allows the same code to use the correct protocol locally and in production. The code sets up the connection depending on the current &lt;code&gt;http&lt;/code&gt; protocol in use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...

if (window.location.protocol == 'https:') {
  wsProtocol = 'wss://'
} else {wsProtocol = 'ws://'}

sheetSocket = new WebSocket(
  wsProtocol + window.location.host
  + '/ws/sheet/' + sheetName + '/'
);

...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Please let me know (in the comments below) whether anything I've done is wrong or I can improve it.&lt;/p&gt;

&lt;p&gt;This was my first experience with Websockets and Django together. And it was pleasant one. The few "conflicting docs" issues described above, although blocking, were kinda expected.&lt;/p&gt;

&lt;p&gt;Credits: Diagram above drawn using &lt;a href="https://www.excalidraw.com"&gt;excalidraw.com&lt;/a&gt;.&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;In practice, in some cases, Nginx is able to serve the request itself. Example, for static files or resources cached an Nginx level. Avoiding going into this to keep things simple. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;At the time of writing &lt;code&gt;re_path()&lt;/code&gt; is used due to limitations in URLRouter. ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>django</category>
      <category>python</category>
      <category>nginx</category>
    </item>
    <item>
      <title>Huey as a minimal task queue for Django</title>
      <dc:creator>J.V. Zammit</dc:creator>
      <pubDate>Wed, 08 Jul 2020 10:23:27 +0000</pubDate>
      <link>https://dev.to/jvzammit/huey-as-a-minimal-task-queue-for-django-33hm</link>
      <guid>https://dev.to/jvzammit/huey-as-a-minimal-task-queue-for-django-33hm</guid>
      <description>&lt;p&gt;Are you considering &lt;a href="https://www.untangled.dev/2020/07/02/web-app-task-queue/"&gt;adding a task queue&lt;/a&gt; to your Django project? Then this article should be useful to you.&lt;/p&gt;

&lt;p&gt;When I was "younger" &lt;em&gt;task queue with Django project&lt;/em&gt; meant &lt;em&gt;celery task queue&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Now that I'm "older" there are simpler alternatives. The simplest I found was &lt;em&gt;Huey&lt;/em&gt;. But the ideas presented here apply to evaluating all task queues for your Django project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Frustrated with celery and django-celery
&lt;/h3&gt;

&lt;p&gt;In December 2019 I was taking a Django project from Python 2 to 3. This project relied on &lt;a href="https://docs.celeryproject.org/en/stable/index.html"&gt;celery&lt;/a&gt; and its &lt;a href="http://celery.github.io/django-celery/"&gt;integration for Django&lt;/a&gt; for asynchronous task processing. Github project link &lt;a href="https://github.com/celery/django-celery"&gt;here&lt;/a&gt;. This codebase's work was mostly done back in 2012-2015.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;django-celery&lt;/code&gt; was the first problematic part. "Problematic" because it was not being actively maintained. Python 3 support was only "planned" at the time.&lt;/p&gt;

&lt;p&gt;Besides, by testing celery with my Python3 setup I realised how "heavy" it is. "Heavy" in terms of dependencies. &lt;code&gt;billiard&lt;/code&gt;, &lt;code&gt;kombu&lt;/code&gt;, etc.&lt;/p&gt;

&lt;p&gt;I had used &lt;code&gt;celery&lt;/code&gt; for a very long time. A decade! But this nudged me into looking for alternatives.&lt;/p&gt;

&lt;p&gt;My use cases for using an async task queue were:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Handle tasks async. For example, sending an email out. Tasks that you should not handle within the request-response cycle.&lt;/li&gt;
&lt;li&gt;Scheduled tasks.&lt;/li&gt;
&lt;li&gt;Retrying of failed tasks. For example, reading from an API fails due to a network error. I want that task retried for a few times.&lt;/li&gt;
&lt;li&gt;Simple locking. More on what I mean by "simple" further down.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The above were (are) handled nicely by &lt;code&gt;celery&lt;/code&gt;. The &lt;em&gt;scheduled tasks&lt;/em&gt; part relied entirely on &lt;code&gt;django-celery&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Another factor that pushed me "off the celery train" was something in my last long-term gig. The increasing reputation that celery is "heavyweight".&lt;/p&gt;

&lt;p&gt;Finally, celery provides a whole lot more than the above basic set of use cases I need.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is celery heavyweight?
&lt;/h3&gt;

&lt;p&gt;A colleague achieved significant gains in task execution time by moving off celery. To dramatiq. This was on a Python 3 project I didn't work directly on. By significant I mean ~50% throughput. In this case, “tasks” were about handling a simple message that wrote one row to the database, at most. With no real processing of that message.&lt;/p&gt;

&lt;p&gt;At about the same time the above happened, I was listening to the &lt;a href="https://djangochat.com/"&gt;DjangoChat podcast&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The below is a transcript from the “Caching” &lt;a href="https://djangochat.com/episodes/caching"&gt;episode&lt;/a&gt; from November 2019. Transcript &lt;a href="https://djangochat.com/episodes/caching-wAhBfulR/transcript"&gt;here&lt;/a&gt;. The podcast folks were discussing caches. And brought up the usual suspects; &lt;a href="https://memcached.org/"&gt;Memcached&lt;/a&gt; and &lt;a href="https://redis.io/"&gt;Redis&lt;/a&gt;. On mentioning Redis, &lt;a href="https://twitter.com/carltongibson"&gt;Carlton Gibson&lt;/a&gt; calls out how easy it is to add a queue when you have Redis in place. And how much of an “overkill” celery is. Emphaisis in the below quote is mine:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;have Redis? Yeah. You want to you want to use a queue. So let's take a good queue package. So, you know, everyone always talks about celery, but celery is &lt;em&gt;overkill for, you know, the majority of use cases&lt;/em&gt;. So what's a good package? Well, there's one called django-q, which I love and have fun with. That's nice and simple. And that's got a Redis back end. So you pip install, right or, you know, apt install Redis. And then you pip install django-q into your project, you know, a little bit of settings, magic, and you're up and running [..]&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Packages Considered
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Gw_pHhO8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/qb2f5ycdh4mvwmqjxrr3.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Gw_pHhO8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/qb2f5ycdh4mvwmqjxrr3.jpg" alt='British Library digitised image from page 409 of "An illustrated and descriptive guide to the great railways of England, and their connections with the continent"&amp;lt;br&amp;gt;
'&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I did not compare packages. To compare would mean installing each one, run the same task and measure. I had what was left of a "free" day to switch over from celery to another package and have things deployed by day's end.&lt;/p&gt;

&lt;p&gt;I considered these packages to see if I could adopt a more lightweight alternative to celery. Before I continue, by "lightweight" I mean, "lightweight in terms of":&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;package size and dependencies&lt;/li&gt;
&lt;li&gt;code that I would need to rework to transition from celery to this task queue&lt;/li&gt;
&lt;li&gt;works with Redis, a pre-existing component in my stack&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The packages I &lt;del&gt;compared&lt;/del&gt; considered:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Package&lt;/th&gt;
&lt;th&gt;Link&lt;/th&gt;
&lt;th&gt;How I got to know about it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;dramatiq&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://dramatiq.io/"&gt;Link&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Ex-colleague's experience, described above.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;django-q&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://django-q.readthedocs.io/en/latest/"&gt;Link&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Recommended during the &lt;a href="https://djangochat.com/"&gt;DjangoChat podcast&lt;/a&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;huey&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://huey.readthedocs.io/en/latest/"&gt;Link&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Recommended by &lt;a href="https://adamj.eu/tech/"&gt;Adam Johnson&lt;/a&gt;, Django blogger I follow.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I decided to move on with &lt;code&gt;Huey&lt;/code&gt;. It was not a clear-cut decision.&lt;/p&gt;

&lt;p&gt;My mindset was not about installing &lt;em&gt;the best&lt;/em&gt;. It was about installing a &lt;em&gt;minimal&lt;/em&gt; package. That removes my dependency on celery/django-celery. And allows me to continue taking that project's codebase to Python 3.&lt;/p&gt;

&lt;p&gt;Why not &lt;code&gt;dramatiq&lt;/code&gt;? I did not go with &lt;code&gt;dramatiq&lt;/code&gt; because of two reasons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It required installation of another package. The "Advanced Python Scheduler", &lt;a href="https://apscheduler.readthedocs.io/en/stable/"&gt;APS&lt;/a&gt;, to allow scheduling of tasks.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/Bogdanp/django_dramatiq"&gt;&lt;code&gt;django-dramatiq&lt;/code&gt;&lt;/a&gt;, while maintained by the author of &lt;code&gt;dramatiq&lt;/code&gt; itself, is "yet another package".&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After my experience with &lt;code&gt;django-celery&lt;/code&gt; any extra package scared me. I did not want to end up being unable to use a main library due to a smaller accompanying library not being maintained.&lt;/p&gt;

&lt;p&gt;In comparison, for &lt;code&gt;Huey&lt;/code&gt; I would only need to &lt;code&gt;pip install huey&lt;/code&gt;. The Django integration part is part of the package. Docs &lt;a href="https://huey.readthedocs.io/en/latest/django.html"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In addition, dramatiq is intended for rabbitmq. I see no need for advanced messaging such as that enabled by &lt;a href="https://www.rabbitmq.com/"&gt;rabbitmq&lt;/a&gt; right now.&lt;sup id="fnref1"&gt;1&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;Why not &lt;code&gt;django-q&lt;/code&gt;? It is actively maintained. And targeted for Django. And offers a lot of features. But by the looks of it, it offered many features I was not going to be using. At the cost of being less lightweight than &lt;code&gt;Huey&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The above does not mean &lt;code&gt;dramatiq&lt;/code&gt; or &lt;code&gt;django-q&lt;/code&gt; are not great packages. Far from it. I have them in mind in case the use case changes.&lt;/p&gt;
&lt;h2&gt;
  
  
  Huey
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://huey.readthedocs.io/en/latest/django.html"&gt;Huey's Django integration&lt;/a&gt; provides:&lt;/p&gt;

&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;Configuration of huey via the Django settings module.&lt;/li&gt;
&lt;li&gt;Running the consumer as a Django management command.&lt;/li&gt;
&lt;li&gt;Auto-discovery of &lt;code&gt;tasks.py&lt;/code&gt; modules to simplify task importing.&lt;/li&gt;
&lt;li&gt;Properly manage database connections.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;

&lt;p&gt;Sweet.&lt;/p&gt;
&lt;h3&gt;
  
  
  Code changes
&lt;/h3&gt;

&lt;p&gt;Installed &lt;code&gt;huey&lt;/code&gt;? The &lt;a href="https://huey.readthedocs.io/en/latest/django.html#setting-things-up"&gt;Setting Things Up&lt;/a&gt; section in Huey’s Django integration guide covers what you need to do from then on.&lt;/p&gt;

&lt;p&gt;Instead of importing the &lt;code&gt;task&lt;/code&gt; or &lt;code&gt;periodic_task&lt;/code&gt; decorators from the main Huey package, import from &lt;code&gt;huey.contrib.djhuey&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from huey.contrib.djhuey import periodic_task, task
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Huey also &lt;a href="https://huey.readthedocs.io/en/latest/django.html#tasks-that-execute-queries"&gt;offers function decorators for tasks that execute queries&lt;/a&gt;, which automatically close the database connection:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from huey.contrib.djhuey import db_periodic_task, db_task
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;To set a task performing a &lt;code&gt;db_task&lt;/code&gt; therefore:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from huey.contrib import djhuey as huey

@huey.db_task()
def save_data_and_send_email():
    ...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;What about settings files? Main settings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# HUEY
HUEY = {
    'name': 'mydjangoproject',
    'url': 'redis://localhost:6379/?db=1',
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Seriously, that's enough to have things running!&lt;/p&gt;

&lt;p&gt;A note about how things run locally by default:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When &lt;code&gt;settings.DEBUG = True&lt;/code&gt;, tasks will be executed synchronously just like regular function calls. The purpose of this is to avoid running both Redis and an additional consumer process while developing or running tests.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is probably the reason why &lt;code&gt;run_huey&lt;/code&gt; doesn't autoreload.&lt;/p&gt;

&lt;p&gt;To have Huey use redis to broker tasks locally:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;ensure &lt;code&gt;redis&lt;/code&gt; service is available locally (duh!)&lt;/li&gt;
&lt;li&gt;set &lt;code&gt;immediate&lt;/code&gt; and &lt;code&gt;immediate_use_memory&lt;/code&gt; to &lt;code&gt;False&lt;/code&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;HUEY['immediate_use_memory'] = False
HUEY['immediate'] = False
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;More details on debug and synchronous execution with Huey &lt;a href="https://huey.readthedocs.io/en/latest/django.html#debug-and-synchronous-execution"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Passing Parameters
&lt;/h3&gt;

&lt;p&gt;Note that, like in celery's case, Huey uses &lt;code&gt;pickle&lt;/code&gt; to serialize messages (&lt;a href="https://docs.python.org/3/library/pickle.html"&gt;docs&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;So pay attention to pass variable types that play well with &lt;code&gt;pickle&lt;/code&gt; when passing parameters.&lt;/p&gt;

&lt;p&gt;Thus avoid passing a Django model instance or queryset as parameter, as shown below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from huey.contrib import djhuey as huey

@huey.task()
def notify_user(user):
    send_email(user.email, ...)

my_user = User.objects.get(id=user_id)
send_email(my_user)()  # enqueue task
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Instead pass the object &lt;code&gt;id&lt;/code&gt;, which is an &lt;code&gt;int&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from huey.contrib import djhuey as huey

@huey.task()
def notify_user(user_id):
    user = User.objects.get(id=user_id)
    send_email(user.email, ...)

send_email(my_user.id)()  # enqueue task
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The serializer can be overridden. But going into that takes us beyond &lt;em&gt;minimal&lt;/em&gt; for today.&lt;/p&gt;

&lt;h3&gt;
  
  
  Periodic tasks
&lt;/h3&gt;

&lt;p&gt;This is an example of a periodic task. It calls a Django management command to clear expired user sessions every 2 hours:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from huey import crontab
from huey.contrib import djhuey as huey

@huey.periodic_task(crontab(hour='*/2'))
def clear_expired_sessions():
    from django.core.management import call_command
    return call_command('clearsessions')
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;One &lt;em&gt;minimal&lt;/em&gt; aspect of Django+Huey is when it comes to deployment (tackled further down).&lt;/p&gt;

&lt;p&gt;&lt;code&gt;manage.py run_huey&lt;/code&gt; takes care of executing both:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;tasks that your code adds to the queue while it's running, e.g. send an email triggered by a user action&lt;/li&gt;
&lt;li&gt;scheduled (aka "periodic") tasks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both the task &lt;code&gt;consumer&lt;/code&gt; and &lt;code&gt;scheduler&lt;/code&gt; are run by the same &lt;code&gt;run_huey&lt;/code&gt; process.&lt;/p&gt;

&lt;h3&gt;
  
  
  Retries
&lt;/h3&gt;

&lt;p&gt;Example: I want to fetch API data on the first of the month at 2:30PM:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from huey import crontab
from huey.contrib import djhuey as huey

@huey.db_periodic_task(
    crontab(day='1', hour='14', minute='30'),
    retries=2, retry_delay=10)
@huey.lock_task('sync_gsuite_data')
def fetch_api_data():
    ...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;The above retries the function twice, with an interval of 10 seconds.&lt;/p&gt;

&lt;p&gt;The one aspect that's missing in this setup is a hook to retry only in case of specific exception. I want an exception caused by a network error to be retried. But not an exception due to a &lt;code&gt;ZeroDivisionError&lt;/code&gt;, for example.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;huey&lt;/code&gt; provides &lt;a href="https://huey.readthedocs.io/en/latest/signals.html"&gt;signals&lt;/a&gt; that allow you to inspect an exception on various types of events. For example, you could use the below code to notify admins when a Huey task fails without being retried:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import traceback

from django.core.mail import mail_admins

from huey import signals
from huey.contrib import djhuey as huey


@huey.signal(signals.SIGNAL_ERROR)
def task_error(signal, task, exc):
    if task.retries &amp;gt; 0:
        return  # do not notify when task is to be retried
    subject = f'Task [{task.name}] failed'
    message = f"""Task ID: {task.id}
Args: {task.args}
Kwargs: {task.kwargs}
Exception: {exc}
{traceback.format_exc()}"""
    mail_admins(subject, message)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Sidenote 1:&lt;/em&gt; Keep in mind signals are executed synchronously by the consumer as it processes tasks.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Sidenote 2:&lt;/em&gt; this is just to demonstrate what can be done. A more standard way to do this is to attach an email handler at loglevel &lt;code&gt;ERROR&lt;/code&gt; to the huey consumer logger.&lt;/p&gt;

&lt;p&gt;But I would prefer the hook that &lt;code&gt;dramatiq&lt;/code&gt; &lt;a href="https://dramatiq.io/guide.html#message-retries"&gt;provides&lt;/a&gt; to determine whether a task should be retried in this style:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def should_retry(retries_so_far, exception):
    return retries_so_far &amp;lt; 3 and isinstance(exception, HttpTimeout)


@dramatiq.actor(retry_when=should_retry)
def count_words(url):
    ...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;See? I'm veering off "minimal" and into better features. Let's move on.&lt;/p&gt;

&lt;h3&gt;
  
  
  Simple locking
&lt;/h3&gt;

&lt;p&gt;To quote &lt;code&gt;huey&lt;/code&gt;'s author &lt;a href="https://github.com/coleifer/huey/issues/413#issuecomment-488794415"&gt;himself&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A simple lock ensures that one task cannot be executed in parallel.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Example use case: a report generation task that runs every 10 minutes, but occasionally it can take 15 minutes to complete. You want to ensure that it does not start a stampede. So you use a lock to ensure that only one instance of the task can run at a time.&lt;/p&gt;

&lt;p&gt;Example code from &lt;code&gt;huey&lt;/code&gt;'s docs on &lt;a href="https://huey.readthedocs.io/en/latest/guide.html#locking-tasks"&gt;locking tasks&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@huey.periodic_task(crontab(minute='*/10'))
@huey.lock_task('reports-lock')  # Goes *after* the task decorator.
def generate_report():
    run_report()
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  Take a look around
&lt;/h3&gt;

&lt;p&gt;A nice thing about Huey is its &lt;a href="https://huey.readthedocs.io/en/latest/index.html"&gt;docs&lt;/a&gt;. Being a relatively small package, you don't easily get lost.&lt;/p&gt;

&lt;p&gt;The docs feel "cohesive". Unlike how I used to feel with celery's docs. So feel free to take a look around for other features not mentioned in this tutorial.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://huey.readthedocs.io/en/latest/troubleshooting.html"&gt;troubleshooting and common pitfalls&lt;/a&gt; section saved me a headscratch or two.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deployment
&lt;/h2&gt;

&lt;p&gt;In my Ubuntu setup I use &lt;a href="http://supervisord.org/"&gt;supervisor&lt;/a&gt; for process management. For example supervisor manages the &lt;code&gt;gunicorn&lt;/code&gt; which binds the Django application to Nginx.&lt;/p&gt;

&lt;h3&gt;
  
  
  The bash scipt
&lt;/h3&gt;

&lt;p&gt;To run Huey, supervisor runs a bash script that runs Huey:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;start_huey.bash&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/bin/bash

NAME="mydjangoproject-huey"  # Name of the application
DJANGODIR=/home/ubuntu/webapp/mydjangoproject/proj  # Django project directory
DJANGOENVDIR=/home/ubuntu/webapp/mydjangoproject_env  # Django project virtualenv

echo "Starting $NAME as `whoami`"

# Activate the virtual environment
cd $DJANGODIR
source /home/ubuntu/webapp/mydjangoproject_env/bin/activate
source /home/ubuntu/webapp/mydjangoproject/proj/.env
export PYTHONPATH=$DJANGODIR:$PYTHONPATH

# Start Huey
exec ${DJANGOENVDIR}/bin/python manage.py run_huey
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;Test the bash script above by running it.&lt;/p&gt;

&lt;p&gt;It has to be executable, i.e. &lt;code&gt;chmod +x start_huey.bash&lt;/code&gt; if it's not.&lt;/p&gt;

&lt;p&gt;Output should be similar to this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Starting mydjangoproject-huey as ubuntu
[2020-07-01 16:26:54,455] INFO:huey.consumer:MainThread:Huey consumer started with 1 thread, PID 12113 at 2020-07-01 14:26:54.455715
[2020-07-01 16:26:54,456] INFO:huey.consumer:MainThread:Scheduler runs every 1 second(s).
[2020-07-01 16:26:54,456] INFO:huey.consumer:MainThread:Periodic tasks are enabled.
[2020-07-01 16:26:54,456] INFO:huey.consumer:MainThread:The following commands are available:
+ myapp.tasks.send_email
[...]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;h3&gt;
  
  
  supervisor conf file
&lt;/h3&gt;

&lt;p&gt;File located at: &lt;code&gt;/etc/supervisor/conf.d/huey.conf&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight"&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;; ================================
;  huey supervisor
; ================================

[program:huey]
command = /home/ubuntu/webapp/start_huey.bash  ; Command to start huey

user=ubuntu
numprocs=1
stdout_logfile=/home/ubuntu/webapp/logs/huey/worker.log
stderr_logfile=/home/ubuntu/webapp/logs/huey/error.log
stdout_logfile_maxbytes=50MB
stderr_logfile_maxbytes=50MB
stdout_logfile_backups=10
stderr_logfile_backups=10 
autostart=true
autorestart=true
startsecs=10

; Need to wait for currently executing tasks to finish at shutdown.
; Increase this if you have very long running tasks.
stopwaitsecs = 2

; Causes supervisor to send the termination signal (SIGTERM) to the whole process group.
stopasgroup=true
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;



&lt;p&gt;A lot of the &lt;code&gt;conf&lt;/code&gt; file above is supervisor-specific.&lt;/p&gt;

&lt;p&gt;The point of this section is to show you how simple it is to have &lt;code&gt;huey&lt;/code&gt; with Django run reliably on an Ubuntu instance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;What do you think about this? Can it be better? Can it be more &lt;em&gt;minimal&lt;/em&gt;?&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;If you want to know more on dramatiq, its author was interviewed in December 2017 on the &lt;a href="https://www.pythonpodcast.com/dramatiq-with-bogdan-popa-episode-141/"&gt;Python podcast&lt;/a&gt;. Great episode! ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>python</category>
      <category>django</category>
    </item>
  </channel>
</rss>
