<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Haki Benita - Django</title><link href="https://hakibenita.com/" rel="alternate"></link><link href="https://hakibenita.com/feeds/tag/django.atom.xml" rel="self"></link><id>https://hakibenita.com/</id><updated>2025-10-30T00:00:00+02:00</updated><entry><title>Reliable Django Signals</title><link href="https://hakibenita.com/django-reliable-signals" rel="alternate"></link><published>2025-10-30T00:00:00+02:00</published><updated>2025-10-30T00:00:00+02:00</updated><author><name>Haki Benita</name></author><id>tag:hakibenita.com,2025-10-30:/django-reliable-signals</id><summary type="html">&lt;p&gt;Django signals are extremely useful for decoupling modules and implementing complicated workflows. However, the underlying transport for signals makes them unreliable and subject to unexpected failures.In this article, I present an alternative transport implementation for Django signals using background tasks which makes them reliable and safer to use in mission critical workflows.&lt;/p&gt;</summary><content type="html">&lt;hr&gt;
&lt;p&gt;Django signals are extremely useful for decoupling modules and implementing complicated workflows. However, the underlying transport for signals makes them unreliable and subject to unexpected failures.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;In this article, I present an alternative transport implementation for Django signals using background tasks which makes them reliable and safer to use in mission critical workflows.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;details class="toc-container" open&gt;
    &lt;summary&gt;Table of Contents&lt;/summary&gt;&lt;/p&gt;
&lt;div class="toc"&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#a-common-workflow"&gt;A Common Workflow&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#creating-a-payment-process"&gt;Creating a Payment Process&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#placing-an-order"&gt;Placing an Order&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#decoupling-modules"&gt;Decoupling Modules&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#circular-dependency"&gt;Circular Dependency&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#polling-changes"&gt;Polling Changes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#django-signals"&gt;Django Signals&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#robust-django-signals"&gt;Robust Django Signals&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#django-signals-and-database-transactions"&gt;Django Signals and Database Transactions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#fault-tolerance"&gt;Fault Tolerance&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#simulating-failures"&gt;Simulating Failures&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#atomicity"&gt;Atomicity&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#reliable-execution"&gt;Reliable Execution&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#django-tasks"&gt;Django Tasks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#using-django-tasks"&gt;Using Django Tasks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#execute-receivers-as-django-tasks"&gt;Execute Receivers as Django Tasks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#testing-reliable-django-signals"&gt;Testing Reliable Django Signals&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#reliable-signals-limitations"&gt;Reliable Signals Limitations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#future-work"&gt;Future Work&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#final-thoughts"&gt;Final Thoughts&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;p&gt;&lt;/details&gt;&lt;/p&gt;
&lt;h2 id="a-common-workflow"&gt;&lt;a class="toclink" href="#a-common-workflow"&gt;A Common Workflow&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Say you have an application that accept payments from users. Usually, you don't go and implement your own payment solution. Instead, you integrate with some 3rd-party provider.&lt;/p&gt;
&lt;h3 id="creating-a-payment-process"&gt;&lt;a class="toclink" href="#creating-a-payment-process"&gt;Creating a Payment Process&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;This is a common workflow for integrating with a 3rd-party payment provider:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;You create some payment process in the provider's system&lt;/li&gt;
&lt;li&gt;You redirect the user to some URL, or you get something to pass to the provider's client SDK&lt;/li&gt;
&lt;li&gt;Sometime in the future you get notified about the status of the payment, usually by webhook or redirect&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;A simple state machine for a payment process can look like this:&lt;/p&gt;
&lt;div class="dark--invert"&gt;
&lt;figure&gt;&lt;img alt="payment process state machine" src="images/01-reliable-django-signals-payment-process-fsm.png"&gt;&lt;figcaption&gt;payment process state machine&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/div&gt;
&lt;p&gt;To keep track of payments you create a simple Django module:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# payment/models.py&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Literal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Self&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ne"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Abstract.&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;StateError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PaymentProcess&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BigAutoField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;primary_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BigIntegerField&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;Literal&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;initiated&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;succeeded&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;failed&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&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;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nd"&gt;@classmethod&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;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;assert&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;initiated&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nd"&gt;@classmethod&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;set_status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;cls&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="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;succeeded&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;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;with&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;atomic&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="n"&gt;payment_process&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;select_for_update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;of&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;self&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;no_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;payment_process&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;initiated&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;}:&lt;/span&gt;
                &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;StateError&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;succeeded&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;payment_process&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;succeeded&amp;#39;&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;payment_process&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;failed&amp;#39;&lt;/span&gt;
            &lt;span class="n"&gt;payment_process&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&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;payment_process&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;To create a new payment process you provide an amount:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;payment.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;PaymentProcess&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;pp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PaymentProcess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100_00&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;vars&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pp&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;amount&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;status&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;initiated&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The initial status is "initiated". At some point you'll make an API call to your payment provider, get some ID and pass it over to the client - this is outside the scope of this article.&lt;/p&gt;
&lt;p&gt;Next, the user interacts with the 3rd-party to provide their payment details. When the user is done, you get an update on the outcome of the payment, usually a webhook or a redirect, and you set the status of the payment process in your local database:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;pp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PaymentProcess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;succeeded&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;vars&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pp&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;amount&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;status&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;succeeded&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The payment succeeded and the status is set to "succeeded". So far so good!&lt;/p&gt;
&lt;p&gt;Now that you can process payment, you can move on to handling orders.&lt;/p&gt;
&lt;h3 id="placing-an-order"&gt;&lt;a class="toclink" href="#placing-an-order"&gt;Placing an Order&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;In your website, the user browse around until they find something they want, and proceed to checkout. At this point, you calculate the amount to be paid and create an order with a payment process. The user then interacts with the payment process to complete the payment. Based on the outcome of the payment, you decide if the order should be filled or cancelled.&lt;/p&gt;
&lt;p&gt;A state machine for an order can look like this:&lt;/p&gt;
&lt;div class="dark--invert"&gt;
&lt;figure&gt;&lt;img alt="order state machine" src="images/02-reliable-django-signals-order-fsm.png"&gt;&lt;figcaption&gt;order state machine&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/div&gt;
&lt;p&gt;To keep track of orders you create a new "orders" module:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;payment.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;PaymentProcess&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Literal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Self&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BigAutoField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;primary_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;payment_process&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PaymentProcess&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;on_delete&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PROTECT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BigIntegerField&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;Literal&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;pending_payment&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;completed&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;cancelled&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&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;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nd"&gt;@classmethod&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;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;assert&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;atomic&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="n"&gt;payment_process&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PaymentProcess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;payment_process&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;payment_process&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;pending_payment&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&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;order&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;To create the order you provide an amount to charge. The module then goes and create a payment process for the same amount and associates it with your order via a foreign key:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;120_00&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;vars&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;payment_process_id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;amount&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;12000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;status&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;pending_payment&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;vars&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;payment_process&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;amount&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;12000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;status&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;initiated&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;In real life, when you create an order you keep a lot more information such as the user who placed the order, the items, shipping information and so on. All of this is not important for this article, so we ignore it.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="decoupling-modules"&gt;&lt;a class="toclink" href="#decoupling-modules"&gt;Decoupling Modules&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The initial state of an order is "pending_payment" and the current state of the payment is "initiated". The next step is for the user to complete the payment.&lt;/p&gt;
&lt;p&gt;When a payment is updated, we need to update the state of the order. Here is a function that given a payment process, sets the status of the order:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# order/models.py&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# ...&lt;/span&gt;

    &lt;span class="nd"&gt;@classmethod&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;on_payment_completed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payment_process_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Self&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Update the order status based on the payment process status.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;atomic&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;select_related&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;payment_process&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;select_for_update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;of&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;self&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;no_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payment_process_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;payment_process_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;pending_payment&amp;#39;&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;order&lt;/span&gt;

            &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;payment_process&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;succeeded&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;completed&amp;#39;&lt;/span&gt;
                &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;failed&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;cancelled&amp;#39;&lt;/span&gt;
                &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;initiated&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Unexpected payment process status &amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;payment_process&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;&amp;quot;&amp;#39;&lt;/span&gt;
                &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;ever&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="n"&gt;assert_never&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ever&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&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;order&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The function first looks for the order that references the payment process. If the status of the order is not "pending_payment", we assume this function was already called, and we return the order. This provides some level of idempotency. In real life, you probably should verify that the current state of the order matches the state of the provided payment process.&lt;/p&gt;
&lt;p&gt;Next, update the status of the order based on the status of the payment process, save to the database, and return the updated order.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;This is where it gets hairy...&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Who's in charge of calling this function? The order is not aware of changes to the payment process, so what's triggering this function?&lt;/p&gt;
&lt;h3 id="circular-dependency"&gt;&lt;a class="toclink" href="#circular-dependency"&gt;Circular Dependency&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;When you create an order, the order creates a payment process. The order module is referencing the payment module using a foreign key, therefore, &lt;strong&gt;the order module depends on the payment module&lt;/strong&gt;:&lt;/p&gt;
&lt;div class="dark--invert"&gt;
&lt;figure&gt;&lt;img alt="module dependencies" src="images/03-reliable-django-signals-dependencies.png"&gt;&lt;figcaption&gt;module dependencies&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/div&gt;
&lt;p&gt;In our workflow, after the user completes the payment, the payment module receives a webhook with the outcome of the payment, and the status of the payment process is updated. Our order is not aware of changes to the payment process, so at what point do we trigger a change in the order?&lt;/p&gt;
&lt;p&gt;A naive way of doing this is to simply update the order directly from the payment process using the reverse relation:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gh"&gt;diff --git i/payment/models.py w/payment/models.py&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;from typing import Literal, Self
&lt;span class="w"&gt; &lt;/span&gt;from django.db import models, transaction
&lt;span class="gi"&gt;+from order.models import Order&lt;/span&gt;

&lt;span class="gu"&gt;@@ -13,23 +13,25 @@  class PaymentProcess(models.Model):&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;    @classmethod
&lt;span class="w"&gt; &lt;/span&gt;    def set_status(cls, id: int, *, succeeded: bool) -&amp;gt; Self:
&lt;span class="w"&gt; &lt;/span&gt;        with transaction.atomic():
&lt;span class="w"&gt; &lt;/span&gt;            payment_process = cls.objects.select_for_update(of=(&amp;#39;self&amp;#39;, ), no_key=True).get(id=id)
&lt;span class="w"&gt; &lt;/span&gt;            if payment_process.status not in {&amp;#39;initiated&amp;#39;}:
&lt;span class="w"&gt; &lt;/span&gt;                raise StateError()
&lt;span class="w"&gt; &lt;/span&gt;            if succeeded:
&lt;span class="w"&gt; &lt;/span&gt;                payment_process.status = &amp;#39;succeeded&amp;#39;
&lt;span class="w"&gt; &lt;/span&gt;            else:
&lt;span class="w"&gt; &lt;/span&gt;                payment_process.status = &amp;#39;failed&amp;#39;
&lt;span class="w"&gt; &lt;/span&gt;            payment_process.save()

&lt;span class="gi"&gt;+            Order.on_payment_completed(payment_process_id=payment_process.id)&lt;/span&gt;

&lt;span class="w"&gt; &lt;/span&gt;        return payment_process
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Now, when the payment process is updated, it explicitly goes to the order and attempts to update it as well. However, if you try to execute this, you'll get an exception:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$./manage.py&lt;span class="w"&gt; &lt;/span&gt;check
Traceback&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;most&lt;span class="w"&gt; &lt;/span&gt;recent&lt;span class="w"&gt; &lt;/span&gt;call&lt;span class="w"&gt; &lt;/span&gt;last&lt;span class="o"&gt;)&lt;/span&gt;:
&lt;span class="w"&gt;    &lt;/span&gt;...&lt;span class="w"&gt; &lt;/span&gt;snipped&lt;span class="w"&gt; &lt;/span&gt;...
ImportError:&lt;span class="w"&gt; &lt;/span&gt;cannot&lt;span class="w"&gt; &lt;/span&gt;import&lt;span class="w"&gt; &lt;/span&gt;name&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;PaymentProcess&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;from&lt;span class="w"&gt; &lt;/span&gt;partially&lt;span class="w"&gt; &lt;/span&gt;initialized
module&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;payment.models&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;most&lt;span class="w"&gt; &lt;/span&gt;likely&lt;span class="w"&gt; &lt;/span&gt;due&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;a&lt;span class="w"&gt; &lt;/span&gt;circular&lt;span class="w"&gt; &lt;/span&gt;import&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;payment/models.py&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Python is warning us about a circular dependency! An order currently references a payment process. With this change, the payment process is referencing the order back - this creates a circular dependency:&lt;/p&gt;
&lt;div class="dark--invert"&gt;
&lt;figure&gt;&lt;img alt="circular dependency" src="images/04-reliable-django-signals-circular-dependency.png"&gt;&lt;figcaption&gt;circular dependency&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/div&gt;
&lt;p&gt;There is a way to make this work - you can import the order inside the function - but you should really avoid that. &lt;strong&gt;A circular dependency is usually a symptom of bad design!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Another reason this is a bad approach is that payment process is a low level module - it can potentially be used by other modules other than order. Should a low level module like payment be aware of all the modules that are using it? This won't scale well and will cause a web of dependencies within the application.&lt;/p&gt;
&lt;h3 id="polling-changes"&gt;&lt;a class="toclink" href="#polling-changes"&gt;Polling Changes&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To avoid circular dependency we can't have the payment process reference the order directly. Another approach, is for the order to periodically check for changes in relevant payment process:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.core.management.base&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BaseCommand&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;...models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Order&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseCommand&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;help&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Check all orders with pending payment and update their status if needed.&amp;#39;&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;payment_process_id&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;pending_payment&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;payment_process__status__in&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;succeeded&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;failed&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;values_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;payment_process_id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;flat&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;on_payment_completed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payment_process_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;payment_process_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SUCCESS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;order &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt; status changed to &amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;&amp;quot;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This Django management command is looking for orders pending payment with a payment process that reached either "failed" or "succeeded" state, and triggers a status update for the order.&lt;/p&gt;
&lt;p&gt;To demonstrate, create an order and mark the payment as successful:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;120_00&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;payment_process_id&lt;/span&gt;
&lt;span class="mi"&gt;3&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;pp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PaymentProcess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;succeeded&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;vars&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pp&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;payment_process_id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;amount&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;12000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;status&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;pending_payment&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;refresh_from_db&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;vars&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;amount&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;12000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;status&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;initiated&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Notice that the payment completed successfully, but the order is pending payment. Let's use the management command to sync the state:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;./manage.py&lt;span class="w"&gt; &lt;/span&gt;sync_orders_pending_payment
order&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;status&lt;span class="w"&gt; &lt;/span&gt;changed&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;completed&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Great! You can now execute this task on a schedule and your orders will eventually reach the correct state.&lt;/p&gt;
&lt;p&gt;This approach has several advantages and disadvantages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Extremely reliable&lt;/strong&gt;: As long as you can keep the command running on a schedule, your orders are going to get synced. This approach is extremely reliable and resilient to a wide variety of unexpected failures.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Duplicated business logic&lt;/strong&gt;: To identify the orders that needs to be synced you had to duplicate some of the business logic. This means that as you add statuses and make the workflow more complicated, you'll have to constantly maintain this logic in more than one place. There are ways to keep things DRY, such as using a custom &lt;code&gt;QuerySet&lt;/code&gt; or an access method, but that requires a certain level of discipline which is not always easy to maintain.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Delays the process&lt;/strong&gt;: If for example you run the task every hour, it means an order can take at most one hour to reach completed state. In some situations this is acceptable, but in other cases, such as in user-facing workflows, this is unacceptable. You can reduce the interval, but this also comes at a cost.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Load on the system&lt;/strong&gt;: Running a task on a schedule means it's running even if it has nothing to do. With a long interval you introduce longer delays in the process, but potentially less unnecessary load on the system. With a short interval you reduce the delay on the process, but add potentially more unnecessary load on the system.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Cumbersome&lt;/strong&gt;: The implementation is fairly straightforward and running tasks on a schedule is something most systems already have. However, as the number of workflows increase, it becomes hard to track and debug.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Using scheduled tasks is a good and reliable solution. However, for user-facing workflows that require quick response it's often not a good fit.&lt;/p&gt;
&lt;h3 id="django-signals"&gt;&lt;a class="toclink" href="#django-signals"&gt;Django Signals&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;So far we tried to trigger a change in the order from the payment it references. This caused circular dependencies so we decided it's a bad idea. We then tried polling for changes which proved to be reliable and simple, but introduced unacceptable delays in the workflow.&lt;/p&gt;
&lt;p&gt;To address these challenges, Django provides &lt;a href="https://docs.djangoproject.com/en/5.2/topics/signals/" rel="noopener"&gt;signals dispatcher&lt;/a&gt; as a way to communicate between modules in the system:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Django includes a “signal dispatcher” which helps decoupled applications get notified when actions occur elsewhere in the framework. In a nutshell, signals allow certain senders to notify a set of receivers that some action has taken place.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Using signals dispatcher, we can dispatch a signal and have one or more receivers subscribe to it. In our case, the payment process can send a signal when it completes, and the order can subscribe to it and update its status. &lt;strong&gt;Using signals the payment module can communicate with other modules in the system without explicitly depending on them!&lt;/strong&gt;&lt;/p&gt;
&lt;div class="dark--invert"&gt;
&lt;figure&gt;&lt;img alt="decouple modules using signals" src="images/05-reliable-django-signals-dispatch-signal.png"&gt;&lt;figcaption&gt;decouple modules using signals&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/div&gt;
&lt;p&gt;First, define the signal:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# payment/signals.py.&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.dispatch&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Signal&lt;/span&gt;

&lt;span class="n"&gt;payment_process_completed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Signal&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Next, send the signal when the payment completes:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gh"&gt;diff --git i/payment/models.py w/payment/models.py&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;from typing import Literal, Self
&lt;span class="w"&gt; &lt;/span&gt;from django.db import models, transaction

&lt;span class="gi"&gt;+from . import signals&lt;/span&gt;

&lt;span class="gu"&gt;@@ -13,23 +15,28 @@ class PaymentProcess(models.Model):&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;    @classmethod
&lt;span class="w"&gt; &lt;/span&gt;    def set_status(cls, id: int, *, succeeded: bool) -&amp;gt; Self:
&lt;span class="w"&gt; &lt;/span&gt;        with transaction.atomic():
&lt;span class="w"&gt; &lt;/span&gt;            payment_process = cls.objects.select_for_update(of=(&amp;#39;self&amp;#39;, ), no_key=True).get(id=id)
&lt;span class="w"&gt; &lt;/span&gt;            if payment_process.status not in {&amp;#39;initiated&amp;#39;}:
&lt;span class="w"&gt; &lt;/span&gt;                raise StateError()
&lt;span class="w"&gt; &lt;/span&gt;            if succeeded:
&lt;span class="w"&gt; &lt;/span&gt;                payment_process.status = &amp;#39;succeeded&amp;#39;
&lt;span class="w"&gt; &lt;/span&gt;            else:
&lt;span class="w"&gt; &lt;/span&gt;                payment_process.status = &amp;#39;failed&amp;#39;
&lt;span class="w"&gt; &lt;/span&gt;            payment_process.save()

&lt;span class="gi"&gt;+            signals.payment_process_completed.send(&lt;/span&gt;
&lt;span class="gi"&gt;+                sender=None,&lt;/span&gt;
&lt;span class="gi"&gt;+                payment_process_id=payment_process.id,&lt;/span&gt;
&lt;span class="gi"&gt;+            )&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;        return payment_process
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The order can now register a receiver that will be executed when the "payment_process_completed" signal is sent. This requires some minor adjustments on the receiving end:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gh"&gt;diff --git a/order/models.py b/order/models.py&lt;/span&gt;
&lt;span class="gi"&gt;+from __future__ import annotations&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;from django.db import models, transaction
&lt;span class="w"&gt; &lt;/span&gt;from payment.models import PaymentProcess
&lt;span class="w"&gt; &lt;/span&gt;from typing import Literal, Self, assert_never
&lt;span class="gi"&gt;+from django.dispatch import receiver&lt;/span&gt;

&lt;span class="gi"&gt;+import payment.signals&lt;/span&gt;

&lt;span class="gu"&gt;@@ -21,16 +24,22 @@ class Order(models.Model):&lt;/span&gt;
&lt;span class="gd"&gt;-    @classmethod&lt;/span&gt;
&lt;span class="gd"&gt;-    def on_payment_completed(cls, *, payment_process_id: int) -&amp;gt; Self:&lt;/span&gt;
&lt;span class="gi"&gt;+    @staticmethod&lt;/span&gt;
&lt;span class="gi"&gt;+    @receiver(payment.signals.payment_process_completed, dispatch_uid=&amp;#39;1da6190f-0cf1-45e1-8481-0d1e27bf6e6f&amp;#39;)&lt;/span&gt;
&lt;span class="gi"&gt;+    def on_payment_completed(payment_process_id: int, *args, **kwargs) -&amp;gt; Order | None:&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;        &amp;quot;&amp;quot;&amp;quot;Update the order status based on the payment process status.&amp;quot;&amp;quot;&amp;quot;
&lt;span class="w"&gt; &lt;/span&gt;        with transaction.atomic():
&lt;span class="gd"&gt;-            order = (&lt;/span&gt;
&lt;span class="gd"&gt;-                cls.objects&lt;/span&gt;
&lt;span class="gd"&gt;-                .select_related(&amp;#39;payment_process&amp;#39;)&lt;/span&gt;
&lt;span class="gd"&gt;-                .select_for_update(of=(&amp;#39;self&amp;#39;, ), no_key=True)&lt;/span&gt;
&lt;span class="gd"&gt;-                .get(payment_process_id=payment_process_id)&lt;/span&gt;
&lt;span class="gd"&gt;-            )&lt;/span&gt;
&lt;span class="gi"&gt;+            try:&lt;/span&gt;
&lt;span class="gi"&gt;+                order = (&lt;/span&gt;
&lt;span class="gi"&gt;+                    Order.objects&lt;/span&gt;
&lt;span class="gi"&gt;+                    .select_related(&amp;#39;payment_process&amp;#39;)&lt;/span&gt;
&lt;span class="gi"&gt;+                    .select_for_update(of=(&amp;#39;self&amp;#39;, ), no_key=True)&lt;/span&gt;
&lt;span class="gi"&gt;+                    .get(payment_process_id=payment_process_id)&lt;/span&gt;
&lt;span class="gi"&gt;+                )&lt;/span&gt;
&lt;span class="gi"&gt;+            except Order.DoesNotExist:&lt;/span&gt;
&lt;span class="gi"&gt;+                # Not related to order.&lt;/span&gt;
&lt;span class="gi"&gt;+                return None&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;            if order.status not in {&amp;#39;pending_payment&amp;#39;}:
&lt;span class="w"&gt; &lt;/span&gt;                return order
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Let's break it down:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Use the &lt;code&gt;@receiver&lt;/code&gt; decorator the register &lt;code&gt;on_payment_completed&lt;/code&gt; for execution when the &lt;code&gt;payment_process_completed&lt;/code&gt; signal is sent.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Receiver functions require that you accept args and kwargs, so add that.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Setting a unique &lt;code&gt;dispatch_uid&lt;/code&gt; to &lt;a href="https://docs.djangoproject.com/en/5.2/topics/signals/#preventing-duplicate-signals" rel="noopener"&gt;prevent duplicate signals&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Signal receiver functions cannot be class or instance methods. To keep the function namespaced to the &lt;code&gt;Order&lt;/code&gt; model, we switch to using a &lt;code&gt;@staticmethod&lt;/code&gt; instead.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Static methods don't operate in the context of an enclosed class, so we can't use the &lt;a href="https://docs.python.org/3/library/typing.html#typing.Self" rel="noopener"&gt;&lt;code&gt;Self&lt;/code&gt; type&lt;/a&gt; to indicate that the function is returning an &lt;code&gt;Order&lt;/code&gt; instance. Instead, we explicitly use &lt;code&gt;Order&lt;/code&gt; as the return type.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Payment processes can potentially be used by modules other than order, and a signal can have many receivers. If we don't find a matching order for a payment process, it probably means it was intended for another model. Since this is now a valid scenario, we don't fail in this case.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We can now see it in action:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;150_00&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;vars&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;payment_process_id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;amount&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;15000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;status&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;pending_payment&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;PaymentProcess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;succeeded&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;refresh_from_db&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;vars&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;payment_process_id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;amount&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;15000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;status&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;completed&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Notice how the state of the order changes to "completed" even though we did not explicitly call &lt;code&gt;Order.on_payment_completed&lt;/code&gt;. This function was invoked implicitly when &lt;code&gt;PaymentProcess.set_status&lt;/code&gt; dispatched the signal.&lt;/p&gt;
&lt;p&gt;Using signals we can trigger changes in other modules without creating direct dependencies between them. As the documentation promised, signals allow us to keep modules decoupled. In our scenario, using signals, payment processes can trigger changes in orders without explicitly depending on them - problem solved!&lt;/p&gt;
&lt;p&gt;This principle of keeping modules decoupled should also extend to how we name signals. It's tempting to name our signal something like "complete_order", but that creates an implicit dependency between the modules because this name implies intent - the payment process should not be aware of how its signal is being used. Instead, we name signals in a way that only reflects what happened, in our case "payment completed". Each receiver can then make whatever they want from that!&lt;/p&gt;
&lt;p&gt;Another advantage of signals is that they can have many receivers. If for example we have an "analytics" module and we want to keep track of how many payment processes succeeded or failed, we can simply register another receiver for the same signal and increment some counter.&lt;/p&gt;
&lt;p&gt;In the next sections we are going to challenge the signals approach and demonstrate when it falls short of its promise!&lt;/p&gt;
&lt;h3 id="robust-django-signals"&gt;&lt;a class="toclink" href="#robust-django-signals"&gt;Robust Django Signals&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;In the previous section we used signals as a way to communicate between two modules without creating an explicit dependency between them. But, did we really achieve that?&lt;/p&gt;
&lt;p&gt;Consider what happens when a receiver encounters an error and raises an exception:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;160_00&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;PaymentProcess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;payment_process_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;succeeded&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;---------------------------------------------------------------------------&lt;/span&gt;
&lt;span class="ne"&gt;Exception&lt;/span&gt;                                 &lt;span class="n"&gt;Traceback&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;most&lt;/span&gt; &lt;span class="n"&gt;recent&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt; &lt;span class="n"&gt;last&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Cell&lt;/span&gt; &lt;span class="n"&gt;In&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="o"&gt;----&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="n"&gt;PaymentProcess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;payment_process_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;succeeded&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;File&lt;/span&gt; &lt;span class="n"&gt;payment&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;37&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;PaymentProcess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;cls&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;succeeded&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;File&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;venv&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;python3&lt;/span&gt;&lt;span class="mf"&gt;.13&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;site&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;packages&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;django&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;dispatch&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;dispatcher&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;209&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;Signal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;named&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;File&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;on_payment_completed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payment_process_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
     &lt;span class="mi"&gt;27&lt;/span&gt; &lt;span class="nd"&gt;@staticmethod&lt;/span&gt;
     &lt;span class="mi"&gt;28&lt;/span&gt; &lt;span class="nd"&gt;@receiver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;signals&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;payment_process_completed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dispatch_uid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;1da6190f-0cf1-45e1-8481-0d1e27bf6e6f&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
     &lt;span class="mi"&gt;29&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;on_payment_completed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payment_process_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Order&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
     &lt;span class="mi"&gt;30&lt;/span&gt;     &lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;Update the order status based on the payment process status.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="o"&gt;---&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;31&lt;/span&gt;     &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="ne"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;on_payment_completed FAILED!!&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
     &lt;span class="mi"&gt;32&lt;/span&gt;     &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;atomic&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
     &lt;span class="mi"&gt;33&lt;/span&gt;         &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

&lt;span class="ne"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;on_payment_completed&lt;/span&gt; &lt;span class="n"&gt;FAILED&lt;/span&gt;&lt;span class="err"&gt;!!&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Oh no! An error in the order caused the payment process to fail. We thought payment process has nothing to do with orders any more, but we were wrong! To keep modules truly decoupled we can't have exceptions from signal receivers propagate to the signal sender.&lt;/p&gt;
&lt;p&gt;Django provides another way of sending a signal, in a way that does not propagate errors to the sender:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gd"&gt;--- a/payment/models.py&lt;/span&gt;
&lt;span class="gi"&gt;+++ b/payment/models.py&lt;/span&gt;
&lt;span class="gu"&gt;@@ -34,7 +34,7 @@ class PaymentProcess(models.Model):&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;                payment_process.status = &amp;#39;failed&amp;#39;
&lt;span class="w"&gt; &lt;/span&gt;            payment_process.save()

&lt;span class="gd"&gt;-            signals.payment_process_completed.send(&lt;/span&gt;
&lt;span class="gi"&gt;+            signals.payment_process_completed.send_robust(&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;                sender=None,
&lt;span class="w"&gt; &lt;/span&gt;                payment_process_id=payment_process.id,
&lt;span class="w"&gt; &lt;/span&gt;            )
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The documentation for &lt;a href="https://docs.djangoproject.com/en/5.2/topics/signals/#django.dispatch.Signal.send_robust" rel="noopener"&gt;&lt;code&gt;Signal.send_robust&lt;/code&gt;&lt;/a&gt; explain the difference very well:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;send()&lt;/code&gt; differs from &lt;code&gt;send_robust()&lt;/code&gt; in how exceptions raised by receiver functions are handled. &lt;code&gt;send()&lt;/code&gt; does not catch any exceptions raised by receivers; it simply allows errors to propagate. Thus not all receivers may be notified of a signal in the face of an error.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;send_robust()&lt;/code&gt; catches all errors derived from Python’s Exception class, and ensures all receivers are notified of the signal. [...]&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Using &lt;code&gt;send_robust()&lt;/code&gt; we can make sure that our modules remain decoupled even when errors happen.&lt;/p&gt;
&lt;p&gt;So, are we finally &lt;em&gt;truly&lt;/em&gt; decoupled?&lt;/p&gt;
&lt;h3 id="django-signals-and-database-transactions"&gt;&lt;a class="toclink" href="#django-signals-and-database-transactions"&gt;Django Signals and Database Transactions&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;In the previous section we found that we weren't decoupled as we thought when the receiver raises an exception. We switched from &lt;code&gt;Signal.send&lt;/code&gt; to &lt;code&gt;Signal.send_robust&lt;/code&gt; which doesn't propagate errors. So now we are no longer affected by anything the receiver is doing, right? Not really!&lt;/p&gt;
&lt;p&gt;Imagine we have another module, "analytics", to keep track of metrics in our system. To keep count of how many successful and failed payment processes we set up this simple receiver:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# analytics/handlers.py&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;urllib.request&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.dispatch&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;receiver&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;payment.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;PaymentProcess&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;payment.signals&lt;/span&gt;

&lt;span class="nd"&gt;@receiver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;signals&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;payment_process_completed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dispatch_uid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;a4e3cd9c-1314-40c1-8251-955c20dd5d93&amp;#39;&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;on_payment_process_completed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payment_process_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;PaymentProcess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;values_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;status&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;flat&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;payment_process_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;urlopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;https://myanalytics.com/metric/inc&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;key&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;payment_process:&lt;/span&gt;&lt;span class="si"&gt;{status}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="ne"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Failed to increase metric&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The function fetches the status of the payment process and reports to some 3rd-party analytics service. We already know that if this fails the sender will not be affected. But what will happen if this request takes a very long time?&lt;/p&gt;
&lt;p&gt;Receiver functions are called immediately by the signals framework when the signal is sent. This means &lt;em&gt;where and when&lt;/em&gt; we send the signal is significant. This is where we send the &lt;code&gt;payment_completed&lt;/code&gt; signal:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PaymentProcess&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# ...&lt;/span&gt;
    &lt;span class="nd"&gt;@classmethod&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;set_status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;cls&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="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;succeeded&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Self&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="hll"&gt;        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;atomic&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;            &lt;span class="n"&gt;payment_process&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;select_for_update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;of&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;self&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;no_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;payment_process&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;initiated&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;}:&lt;/span&gt;
                &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;StateError&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;succeeded&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;payment_process&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;succeeded&amp;#39;&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;payment_process&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;failed&amp;#39;&lt;/span&gt;
            &lt;span class="n"&gt;payment_process&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="hll"&gt;            &lt;span class="n"&gt;signals&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;payment_process_completed&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send_robust&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;                &lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;                &lt;span class="n"&gt;payment_process_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;payment_process&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;            &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;payment_process&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The signal is sent inside a database transaction. This can cause some problems:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Prolonged database transaction&lt;/strong&gt;: the receiver function is making a network call to an external API. We don't have any control over the 3rd-party service and it's possible for a request to take a long time. Since receiver functions are executed immediately when the signal is sent, this can affect the overall execution time of the caller and make the transaction longer. Long running transactions can cause contention and locking issues, and increase the likelihood of failure or rollback. Ideally, we want to keep database transactions as short as possible.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Unhandled side-effects&lt;/strong&gt;: what happens if the receiver was executed and the caller ended up rolling back? In this case, the payment process is not marked as completed, yet the call to the external API has already been made. This can cause inconsistent data between the system and the remote service.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Unintentionally affecting receiver transactions&lt;/strong&gt;: if the receivers are using transactions of their own, they are being executed as sub-transactions of the sender transactions. If the caller rollback, they are also being rolled back. Some may consider this a feature, but in fact, this is implicit and arguably unexpected.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The most straight forward solution here is to simply send the signal outside of the transaction:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gd"&gt;--- a/payment/models.py&lt;/span&gt;
&lt;span class="gu"&gt;@@ -15,28 +15,28 @@ class PaymentProcess(models.Model):&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;    @classmethod
&lt;span class="w"&gt; &lt;/span&gt;    def set_status(cls, id: int, *, succeeded: bool) -&amp;gt; Self:
&lt;span class="w"&gt; &lt;/span&gt;        with transaction.atomic():
&lt;span class="w"&gt; &lt;/span&gt;            payment_process = cls.objects.select_for_update(of=(&amp;#39;self&amp;#39;, ), no_key=True).get(id=id)
&lt;span class="w"&gt; &lt;/span&gt;            if payment_process.status not in {&amp;#39;initiated&amp;#39;}:
&lt;span class="w"&gt; &lt;/span&gt;                raise StateError()
&lt;span class="w"&gt; &lt;/span&gt;            if succeeded:
&lt;span class="w"&gt; &lt;/span&gt;                payment_process.status = &amp;#39;succeeded&amp;#39;
&lt;span class="w"&gt; &lt;/span&gt;            else:
&lt;span class="w"&gt; &lt;/span&gt;                payment_process.status = &amp;#39;failed&amp;#39;
&lt;span class="w"&gt; &lt;/span&gt;            payment_process.save()

&lt;span class="gd"&gt;-            signals.payment_process_completed.send_robust(&lt;/span&gt;
&lt;span class="gd"&gt;-                sender=None,&lt;/span&gt;
&lt;span class="gd"&gt;-                payment_process_id=payment_process.id,&lt;/span&gt;
&lt;span class="gd"&gt;-            )&lt;/span&gt;
&lt;span class="gi"&gt;+        signals.payment_process_completed.send_robust(&lt;/span&gt;
&lt;span class="gi"&gt;+            sender=None,&lt;/span&gt;
&lt;span class="gi"&gt;+            payment_process_id=payment_process.id,&lt;/span&gt;
&lt;span class="gi"&gt;+        )&lt;/span&gt;

&lt;span class="w"&gt; &lt;/span&gt;        return payment_process
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Sending the signal outside of the database transaction prevents prolonged transactions and issues that can be caused by unexpected side-effects, however,
the solution is still not 100% reliable!&lt;/p&gt;
&lt;div class="admonition info"&gt;
&lt;p class="admonition-title"&gt;transaction.on_commit&lt;/p&gt;
&lt;p&gt;Django provides a nice way of executing something only after the database transaction completed successfully, without having to move the call down. Using &lt;a href="https://docs.djangoproject.com/en/5.2/topics/db/transactions/#django.db.transaction.on_commit" rel="noopener"&gt;&lt;code&gt;on_commit&lt;/code&gt;&lt;/a&gt; we can trust that the signal is only being sent after the transaction was successfully committed. If the transaction rolls-back, the callable in &lt;code&gt;on_commit&lt;/code&gt; will not be executed, and the signal will not be sent.&lt;/p&gt;
&lt;/div&gt;
&lt;hr&gt;
&lt;h2 id="fault-tolerance"&gt;&lt;a class="toclink" href="#fault-tolerance"&gt;Fault Tolerance&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To understand how reliable our approach really is, we need to evaluate what happens when it fails at different points in the process. The easiest way of thinking about it is to imagine the server crashing while your process is running.&lt;/p&gt;
&lt;h3 id="simulating-failures"&gt;&lt;a class="toclink" href="#simulating-failures"&gt;Simulating Failures&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Consider the following places where the server might crash during the execution of the function:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nd"&gt;@classmethod&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;set_status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;cls&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="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;succeeded&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Self&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="hll"&gt;    &lt;span class="c1"&gt;# 💥 Before the transaction&lt;/span&gt;
&lt;/span&gt;    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;atomic&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;payment_process&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;select_for_update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;of&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;self&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;no_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;payment_process&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;initiated&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;}:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;StateError&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;succeeded&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;payment_process&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;succeeded&amp;#39;&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;payment_process&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;failed&amp;#39;&lt;/span&gt;

        &lt;span class="n"&gt;payment_process&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="hll"&gt;        &lt;span class="c1"&gt;# 💥 Inside the transaction&lt;/span&gt;
&lt;/span&gt;
&lt;span class="hll"&gt;    &lt;span class="c1"&gt;# 💥 After the transaction, before the signal is sent&lt;/span&gt;
&lt;/span&gt;
    &lt;span class="n"&gt;signals&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;payment_process_completed&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send_robust&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;payment_process_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;payment_process&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&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;payment_process&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Let's analyze what happens if the server crashes in each of these points:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;✅ Before the transaction&lt;/strong&gt;: At this point nothing really happened yet so no changes were made to any objects. Whoever called this function will have to try again, but the process is in the same state as it were - payment process is "initiated" and order is "pending payment".&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;✅ Inside the transaction&lt;/strong&gt;: At this point some changes were made to the payment but the transaction is not commited yet. If the server crash the database transaction will rollback and the process is at the same state as it were before any changes were made - payment process is "initiated" and order is "pending payment".&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;❌ After the transaction, before the signal is sent&lt;/strong&gt;: At this point the transaction was commited to the database so the payment is either "succeeded" or "failed". However, since we crashed before we sent the signal, the state of the order is not updated and remains "pending_payment". This is very bad! unless we somehow sync the states, the order will never complete even though the payment succeeded.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Our approach is not resilient to server crash at any point in the process so we have to consider it unreliable!&lt;/p&gt;
&lt;h3 id="atomicity"&gt;&lt;a class="toclink" href="#atomicity"&gt;Atomicity&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Database transactions provide atomicity - changes to multiple rows inside a single transaction are commited &lt;em&gt;all at once&lt;/em&gt; or &lt;em&gt;not at all&lt;/em&gt;. Ideally, we want the change to the payment and the following change to the order to be executed "all or nothing", otherwise, we risk leaving the process in an inconsistent state.&lt;/p&gt;
&lt;p&gt;In our case, the change to the order is triggered by the signal which is sent outside the database transaction so we cannot guarantee atomic execution of both these changes. As a result, if the payment is updated and we crash before we sent the signal, the system will charge the user but the order will never be marked as completed! You'll end up with very angry users, and for a good reason.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="reliable-execution"&gt;&lt;a class="toclink" href="#reliable-execution"&gt;Reliable Execution&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We started by using Django signals to decouple modules. We then refined our implementation to minimize the impact of receivers on callers by sending signals outside the database transaction. As a result, we introduced scenarios that can leave the process in an inconsistent state.&lt;/p&gt;
&lt;p&gt;Despite our best efforts so far, we are still left with a few significant problems:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Receivers may have significant effect on the caller execution time&lt;/strong&gt;: receivers are executed serially by the signals framework when a signal is sent. The sender has no control over what receivers are doing, and no control over how many receives are registered to it. This makes it very hard to maintain consistent performance.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;No guarantee of at-least-once delivery&lt;/strong&gt;: a signal sender has no way of making sure all receivers will execute eventually at least once. This breaks the contract between the sender and the receiver. Imagine the server crashed while receivers were being sent - these receivers are not going to be delivered, ever! In our case, this means the order would never complete, even though the payment succeeded.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;No built-in mechanism for handling failed receivers&lt;/strong&gt;: when using &lt;code&gt;Signal.send_robust&lt;/code&gt;, if a receiver fails to execute, the sender is not aware of that and there is no built-in process for retry. It's up to the developer to track failed receivers and put in-place a process for retrying. This makes signals unreliable for mission critical workflows.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All of these problems are not new, but they are rooted in the way Django signals work. An ideal solution should provide the following guarantees:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Receivers should be executed at-least-once if the sender committed  &lt;/li&gt;
&lt;li&gt;Receivers should not execute if the sender's transaction rolled back (due to failure or otherwise) &lt;/li&gt;
&lt;li&gt;Receivers should have minimal effect on the sender&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A lot of work and careful thought went into the Django signals framework, and the API is actually quite nice! So with that in mind, we'll try to adjust the execution mechanism for Django signals so that it's reliable and compatible as possible with the existing framework.&lt;/p&gt;
&lt;h3 id="django-tasks"&gt;&lt;a class="toclink" href="#django-tasks"&gt;Django Tasks&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Django 6.0 introduces a new &lt;a href="https://docs.djangoproject.com/en/dev/topics/tasks/" rel="noopener"&gt;"Tasks Framework"&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Background Tasks can offload work to be run outside the request-response cycle, to be run elsewhere, potentially at a later date. This keeps requests fast, reduces latency, and improves the user experience.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Django tasks in its initial release is &lt;a href="https://docs.djangoproject.com/en/dev/ref/tasks/" rel="noopener"&gt;mostly an interface&lt;/a&gt; - it comes with two built-in backends, dummy and immediate, which are mostly intended for debug and development. The idea behind this approach is that developers can implement their own backends, and have seamless integration with other applications using Django tasks.&lt;/p&gt;
&lt;p&gt;One prominent backend that has been developed in parallel with the tasks framework is the &lt;a href="https://github.com/RealOrangeOne/django-tasks/tree/master/django_tasks/backends/database" rel="noopener"&gt;&lt;code&gt;DatabaseBackend&lt;/code&gt;&lt;/a&gt; of &lt;a href="https://github.com/RealOrangeOne/django-tasks" rel="noopener"&gt;django-tasks&lt;/a&gt;. The database backend maintains a queue in a database table, and provides a worker implementation to dequeue and execute tasks. It also comes with a built-in retry mechanism and a nice admin panel.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Using a database queue we can make changes to database objects and enqueue tasks atomically.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This is the idea:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We send a signal in the database transaction of the sender.&lt;/li&gt;
&lt;li&gt;When a signal is sent, we enqueue a task in the database queue for each receiver.&lt;/li&gt;
&lt;li&gt;The receiver tasks are not visible to workers until the sender transaction is commited.&lt;/li&gt;
&lt;li&gt;Once the sender transaction commits, worker processes can start executing receiver tasks.&lt;/li&gt;
&lt;li&gt;If the sender transaction rollback, the enqueued receiver tasks are also rolled-back and therefor won't execute.&lt;/li&gt;
&lt;li&gt;If a receiver happens to fail, the worker can retry it.&lt;/li&gt;
&lt;li&gt;Depending on the number of workers, we can execute multiple receiver tasks at the same time, minimizing delays in the overall workflow.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This approach checks all of our requirements!&lt;/p&gt;
&lt;h3 id="using-django-tasks"&gt;&lt;a class="toclink" href="#using-django-tasks"&gt;Using Django Tasks&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;First, since we're using the new tasks framework and the database backend, we need to install Django version 6 and &lt;a href="https://pypi.org/project/django-tasks/" rel="noopener"&gt;django-tasks&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;uv&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Django&amp;gt;=6&amp;quot;&lt;/span&gt;
$&lt;span class="w"&gt; &lt;/span&gt;uv&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;django-tasks
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Next, configure &lt;code&gt;django-tasks&lt;/code&gt; and set the default backend to be the &lt;code&gt;DatabaseBackend&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gi"&gt;+++ settings.py&lt;/span&gt;
&lt;span class="gu"&gt;@@ -38,6 +38,8 @@ INSTALLED_APPS = [&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;    &amp;#39;order.apps.OrderConfig&amp;#39;,
&lt;span class="w"&gt; &lt;/span&gt;    &amp;#39;payment.apps.PaymentConfig&amp;#39;,
&lt;span class="gi"&gt;+    &amp;#39;django_tasks&amp;#39;,&lt;/span&gt;
&lt;span class="gi"&gt;+    &amp;#39;django_tasks.backends.database&amp;#39;,&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;]

&lt;span class="gu"&gt;@@ -117,3 +119,10 @@ USE_TZ = True&lt;/span&gt;
&lt;span class="gi"&gt;+&lt;/span&gt;
&lt;span class="gi"&gt;+TASKS = {&lt;/span&gt;
&lt;span class="gi"&gt;+    &amp;quot;default&amp;quot;: {&lt;/span&gt;
&lt;span class="gi"&gt;+        &amp;quot;BACKEND&amp;quot;: &amp;quot;django_tasks.backends.database.DatabaseBackend&amp;quot;,&lt;/span&gt;
&lt;span class="gi"&gt;+        &amp;quot;ENQUEUE_ON_COMMIT&amp;quot;: False,&lt;/span&gt;
&lt;span class="gi"&gt;+    }&lt;/span&gt;
&lt;span class="gi"&gt;+}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Make sure you are using a PostgreSQL database backend:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;DATABASES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;default&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="hll"&gt;        &lt;span class="s1"&gt;&amp;#39;ENGINE&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;django.db.backends.postgresql&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;        &lt;span class="s1"&gt;&amp;#39;NAME&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;djangoreliablesignals&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;USER&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;djangoreliablesignals&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Run the migrations to create the queue tables:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;./manage.py&lt;span class="w"&gt; &lt;/span&gt;migrate
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Tasks are executed by a worker process. This means in addition to the processes that run Django itself, you also need a worker process running in the background. In another shell:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;./manage.py&lt;span class="w"&gt; &lt;/span&gt;db_worker
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Make sure to check the options for the worker if this ever makes it to production!&lt;/p&gt;
&lt;p&gt;Great, on to the actual implementation...&lt;/p&gt;
&lt;h3 id="execute-receivers-as-django-tasks"&gt;&lt;a class="toclink" href="#execute-receivers-as-django-tasks"&gt;Execute Receivers as Django Tasks&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;A Django signal is essentially a registry of receiver functions. When you use the &lt;code&gt;receiver&lt;/code&gt; decorator, the wrapped function is added to a list of receivers on the signal instance. When you send the signal, the signal is iterating over the internal list of receivers and executes them.&lt;/p&gt;
&lt;p&gt;One of the limitations of a database queue is that to enqueue a task, you must save all of the necessary information for executing it to the database. This means you need to serialize all the information to JSON - this includes the arguments as well.&lt;/p&gt;
&lt;p&gt;In our case, to execute a receiver function we need to be able to tell the worker what function to execute. Since we can't persist a function object to the database, we need to find another way of referencing it. One way to reference a function is to generate a string with the name of the module and the fully qualified name of the function:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Callable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;callable_to_qualname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Callable&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Return the &amp;lt;module&amp;gt;::&amp;lt;qualname&amp;gt; identifier of a function.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__module__&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;::&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__qualname__&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The function produces a string that includes the module name and the fully qualified name of the function we want to reference:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;callable_to_qualname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;on_payment_completed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="s1"&gt;&amp;#39;order.models::Order.on_payment_completed&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;To get the function from the string, we implement the opposite function:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;importlib&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;qualname_to_callable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;qualname&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Callable&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Get a callable from its &amp;lt;module&amp;gt;::&amp;lt;qualname&amp;gt; identifier.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;module_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;func_qualname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;qualname&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;::&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;module&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;importlib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;import_module&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;module_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Handle nested attributes (e.g., &amp;#39;ClassName.method_name&amp;#39;)&lt;/span&gt;
    &lt;span class="n"&gt;obj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;module&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;attr&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;func_qualname&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;.&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;obj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attr&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;obj&lt;/span&gt;  &lt;span class="c1"&gt;# type: ignore[return-value]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Given the fully qualified name we generated, the function returns the callable:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;receiver&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;qualname_to_callable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;order.models::Order.on_payment_completed&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;receiver&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;function&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;on_payment_completed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payment_process_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;int&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Order | None&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Now that we are able to persist a reference to our receiver function, we can create a task to execute an arbitrary receiver:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;collections.abc&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Mapping&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django_tasks&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;

&lt;span class="nd"&gt;@task&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;execute_task_signal_receiver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;receiver_qualname&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;named&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Mapping&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;receiver&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;qualname_to_callable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;receiver_qualname&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;receiver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;named&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This registers a new &lt;code&gt;django-tasks&lt;/code&gt; task that accepts a receiver qualified name and arguments, and executes it. Simple as that!&lt;/p&gt;
&lt;p&gt;To change the way signals are sent, we provide an alternative implementation of a Django &lt;code&gt;Signal&lt;/code&gt; that instead of executing receivers immediately, enqueues a task for each one:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# reliable_signal/__init__.py&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.dispatch&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Signal&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;DjangoSignal&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.dispatch.dispatcher&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;NO_RECEIVERS&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Signal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DjangoSignal&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;A django-workers-capable signal.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;send_reliable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;named&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Like send_robust(), but enqueues a task for each registered receiver.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;receivers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sender_receivers_cache&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;NO_RECEIVERS&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;sync_receivers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;async_receivers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_live_receivers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;async_receivers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Async receivers not supported by task&amp;#39;&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;receiver&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;sync_receivers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;execute_task_signal_receiver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;enqueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;receiver_qualname&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;callable_to_qualname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;receiver&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="n"&gt;named&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;named&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Our reliable signal is extending Django's built-in &lt;code&gt;Signal&lt;/code&gt; class and adds a function called &lt;code&gt;send_reliable&lt;/code&gt;. The function works like &lt;code&gt;send_robust&lt;/code&gt;, but instead of executing the receiver functions immediately, it enqueues a task for each receiver instead. We discuss this approach further later on.&lt;/p&gt;
&lt;p&gt;Finally, to adjust our code to use the reliable signal, all we need to do is to use our new reliable signal, and replace &lt;code&gt;send_robust&lt;/code&gt; with &lt;code&gt;send_reliable&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gh"&gt;diff --git i/payment/signals.py w/payment/signals.py&lt;/span&gt;
&lt;span class="gu"&gt;@@ -1,3 +1,3 @@&lt;/span&gt;
&lt;span class="gd"&gt;-from django.dispatch import Signal&lt;/span&gt;
&lt;span class="gi"&gt;+from reliable_signal import Signal&lt;/span&gt;

&lt;span class="w"&gt; &lt;/span&gt;payment_process_completed = Signal()

&lt;span class="gh"&gt;diff --git i/payment/models.py w/payment/models.py&lt;/span&gt;
&lt;span class="gu"&gt;@@ -34,9 +34,9 @@ class PaymentProcess(models.Model):&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;                payment_process.status = &amp;#39;failed&amp;#39;
&lt;span class="w"&gt; &lt;/span&gt;            payment_process.save()

&lt;span class="gd"&gt;-        signals.payment_process_completed.send_robust(&lt;/span&gt;
&lt;span class="gd"&gt;-            sender=None,&lt;/span&gt;
&lt;span class="gd"&gt;-            payment_process_id=payment_process.id,&lt;/span&gt;
&lt;span class="gd"&gt;-        )&lt;/span&gt;
&lt;span class="gi"&gt;+            signals.payment_process_completed.send_reliable(&lt;/span&gt;
&lt;span class="gi"&gt;+                sender=None,&lt;/span&gt;
&lt;span class="gi"&gt;+                payment_process_id=payment_process.id,&lt;/span&gt;
&lt;span class="gi"&gt;+            )&lt;/span&gt;

&lt;span class="w"&gt; &lt;/span&gt;        return payment_process
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Notice that we enqueue the task inside the database transaction. Previously, we said this might cause some issues, but using a database queue, you actually &lt;em&gt;do&lt;/em&gt; want to enqueue the task inside the sender transaction. This way, the task will be executed only after the sender commits. If the sender rollback, the task will not be enqueued and will not be executed.&lt;/p&gt;
&lt;p&gt;Now we are ready to test this out. Make sure you have a worker running and execute this in Django shell:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;170_00&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;PaymentProcess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;payment_process_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;succeeded&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PaymentProcess&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PaymentProcess&lt;/span&gt; &lt;span class="nb"&gt;object&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;refresh_from_db&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;
&lt;span class="s1"&gt;&amp;#39;completed&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Amazing! Quickly after we set the status for payment process, the worker picked up the task and updated the status of the order. You can see it in the worker logs as well:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;./manage.py&lt;span class="w"&gt; &lt;/span&gt;db_worker
Watching&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;file&lt;span class="w"&gt; &lt;/span&gt;changes&lt;span class="w"&gt; &lt;/span&gt;with&lt;span class="w"&gt; &lt;/span&gt;StatReloader
Starting&lt;span class="w"&gt; &lt;/span&gt;worker&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;worker_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;4tLA6TEzAdIZ7W620DrVnHuC342wiDfs&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;queues&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;default
Task&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;c34fa024-db4d-41b4-b875-723b2436a346&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;reliable_signal.execute_task_signal_receiver_simple&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;RUNNING
Task&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;c34fa024-db4d-41b4-b875-723b2436a346&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;reliable_signal.execute_task_signal_receiver_simple&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;SUCCEEDED
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We now have a reliable execution engine for Django signals:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Receivers are enqueued inside the sender database transaction&lt;/li&gt;
&lt;li&gt;Failed receivers can be retried by the tasks framework&lt;/li&gt;
&lt;li&gt;Enqueuing receivers is quick, with minimal impact on sender function&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="testing-reliable-django-signals"&gt;&lt;a class="toclink" href="#testing-reliable-django-signals"&gt;Testing Reliable Django Signals&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;When we test workflows we usually don't care much about the execution engine, but rather with the business logic. We also want to keep our test suite simple and deterministic as possible - this means we don't want to execute receivers in another worker, we want to execute them immediately.&lt;/p&gt;
&lt;p&gt;If you recall, we mentioned that Django comes with two built-in backends for testing and development. One of them is the &lt;a href="https://docs.djangoproject.com/en/dev/ref/tasks/#django.tasks.backends.immediate.ImmediateBackend" rel="noopener"&gt;&lt;code&gt;ImmediateBackend&lt;/code&gt;&lt;/a&gt;. This backend will execute tasks immediately when they are enqueued - exactly what we need in tests.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.test&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TestCase&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;override_settings&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;order.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Order&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;payment.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;PaymentProcess&lt;/span&gt;

&lt;span class="hll"&gt;&lt;span class="nd"&gt;@override_settings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TASKS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;default&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;BACKEND&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;django.tasks.backends.immediate.ImmediateBackend&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;}})&lt;/span&gt;
&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderTestCase&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_order_happy_path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100_00&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;pending_payment&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100_00&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;payment_process&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;initiated&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;payment_process&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100_00&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# This will trigger the signal, which should execute the receiver immediately&lt;/span&gt;
        &lt;span class="n"&gt;PaymentProcess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;payment_process_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;succeeded&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;refresh_from_db&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;completed&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;payment_process&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;succeeded&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This tests the common "happy path" for an order - create an order, payment is successful, order status is updated. To provide an alternative backend for tasks during the test, we use the &lt;code&gt;@override_settings&lt;/code&gt; with the path to the built-in &lt;code&gt;ImmediateBackend&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Running the test:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;./manage.py&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;
Found&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;test&lt;span class="o"&gt;(&lt;/span&gt;s&lt;span class="o"&gt;)&lt;/span&gt;.
Creating&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;database&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;alias&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;default&amp;#39;&lt;/span&gt;...
System&lt;span class="w"&gt; &lt;/span&gt;check&lt;span class="w"&gt; &lt;/span&gt;identified&lt;span class="w"&gt; &lt;/span&gt;no&lt;span class="w"&gt; &lt;/span&gt;issues&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;silenced&lt;span class="o"&gt;)&lt;/span&gt;.
.
----------------------------------------------------------------------
Ran&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.058s

OK
Destroying&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;database&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;alias&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;default&amp;#39;&lt;/span&gt;...
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Great! We now have reliable execution that we can easily test.&lt;/p&gt;
&lt;h3 id="reliable-signals-limitations"&gt;&lt;a class="toclink" href="#reliable-signals-limitations"&gt;Reliable Signals Limitations&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Reliable signals provide great benefits, but they also come with some restrictions and limitations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Receiver arguments must be serializable&lt;/strong&gt;: Django signals that execute immediately can accept any python object, such as Django model instances, and pass them immediately to the receiver function. When using a background task, the arguments are written to the database first, so they must be serializable. This means that instead of sending a Django model instance for example, you need to pass the pk instead. This limitation is not unique to Django tasks or even database queues in general. Any task engine needs to serialize arguments in some way.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Partial compatibility with existing Django signals&lt;/strong&gt;: When executing a receiver function, the django signal framework providers two additional arguments - &lt;code&gt;sender&lt;/code&gt; - usually the model that sent the signal, and &lt;code&gt;signal&lt;/code&gt; - the signal that executes the receiver. These arguments are usually useful when you have a single receiver that handles many different signals. Common use cases include analytics and audit. In these use-cases, it's useful to know which signal was triggered (the action) and on what object (the sender). In our simple implementation we do not provide &lt;code&gt;sender&lt;/code&gt; and &lt;code&gt;signal&lt;/code&gt; because they are objects, and we can't easily serialize them. A more complex implementation can provide them.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Not a good fit for all type of signals&lt;/strong&gt;: In this article we discussed custom signals that we use for our own workflows. However, Django has its own &lt;a href="https://docs.djangoproject.com/en/5.2/ref/signals/" rel="noopener"&gt;built-in signals&lt;/a&gt;. For example, pre- and post-save signals on models. Receivers of these signals often rely on the &lt;code&gt;sender&lt;/code&gt; and &lt;code&gt;model&lt;/code&gt; arguments, which we are not sending (see previous note). Also, some signals are actually intended to be executed inside the process that ran them, for example management and app registry signals.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Due to these limitations, we think &lt;strong&gt;it is crucial that reliable signals co-exist with the built-in signal system&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Reliable signals don't fit all types of signals so we don't attempt to monkey-patch Django. Instead, reliable signals should be explicitly defined using &lt;code&gt;reliable_signal.Signal&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Due to the different behavior, offloading execution to a background task should be explicitly invoked using &lt;code&gt;send_reliable&lt;/code&gt; rather than attempting to patch &lt;code&gt;send_robust&lt;/code&gt; or &lt;code&gt;send&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="future-work"&gt;&lt;a class="toclink" href="#future-work"&gt;Future Work&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The current implementation is mostly offered as a reference. While operational under the restrictions mentioned above, there are a few bits we did not address:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Support async tasks&lt;/strong&gt;: both Django signals and &lt;code&gt;django-tasks&lt;/code&gt; support async, but we haven't in our implementation. It should be fairly easy to add.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Utilize tasks features&lt;/strong&gt;: the tasks framework defines additional capabilities such as priority, custom queues and retries. For additional reliability and observability, it might be useful to add dedicated queues for signal receivers and potentially set a higher priority for mission critical signals so they'll execute sooner.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Increase compatibility with Django signals&lt;/strong&gt;: to provide full compatibility receivers should accept the arguments &lt;code&gt;sender&lt;/code&gt; and &lt;code&gt;signal&lt;/code&gt;. They are rarely needed in our custom workflow signals and they are harder to implement, so we didn't bother, but it's possible! Signals can reference Django models using their &lt;a href="https://docs.djangoproject.com/en/6.0/ref/contrib/contenttypes/" rel="noopener"&gt;content type ID&lt;/a&gt; or qualified name. Identifying the sending signal is bit harder because there is no unique identifier we can reference - to do that you'll have to add a unique name for each signal and manage some registry. We've done it, it just wasn't that useful.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Integrate into Django itself&lt;/strong&gt;: Since we're stitching together two built-in components of Django itself - tasks and signals - an additional &lt;code&gt;Signal.send_reliable&lt;/code&gt; / &lt;code&gt;Signal.send_durable&lt;/code&gt; / &lt;code&gt;Signal.send_task&lt;/code&gt; might be a good fit for Django itself!&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="final-thoughts"&gt;&lt;a class="toclink" href="#final-thoughts"&gt;Final Thoughts&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We have a lot of custom workflow in our systems - this is really most of what we do! We are using Django signals extensively in situations where two decoupled modules needs to communicate with each other. As our system grew, we experienced first-hand the issues that can come from having un-reliable signals. Eventually, we developed our own database task queue and integrated it with Django signals. So far its been working pretty well with moderate traffic.&lt;/p&gt;
&lt;p&gt;This article was motivated by our pains and learning from implementing reliable signals in our systems. The release of the the Django tasks framework (and the backends) is surely a welcome addition to an increasing number of large systems built with Django, that needs to have reliable and durable workflows.&lt;/p&gt;</content><category term="articles"></category><category term="Django"></category></entry><entry><title>How to Get Foreign Keys Horribly Wrong</title><link href="https://hakibenita.com/django-foreign-keys" rel="alternate"></link><published>2025-07-15T00:00:00+03:00</published><updated>2025-07-15T00:00:00+03:00</updated><author><name>Haki Benita</name></author><id>tag:hakibenita.com,2025-07-15:/django-foreign-keys</id><summary type="html">&lt;p&gt;Constraints keep the integrity of your system and prevent you from shooting yourself in the foot. Foreign keys are a special type of constraint because, unlike unique, check, and primary keys, they span more than one relation. This makes foreign keys harder to enforce and harder to get right. In this article, I demonstrate common pitfalls, potential optimizations, and implicit behavior related to foreign keys.&lt;/p&gt;</summary><content type="html">&lt;hr&gt;
&lt;p&gt;Constraints keep the integrity of your system and prevent you from shooting yourself in the foot. Foreign keys are a special type of constraint because, unlike unique, check, and primary keys, they span more than one relation. This makes foreign keys harder to enforce and harder to get right.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;In this article, I demonstrate common pitfalls, potential optimizations, and implicit behavior related to foreign keys.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;details class="toc-container" open&gt;
    &lt;summary&gt;Table of Contents&lt;/summary&gt;&lt;/p&gt;
&lt;div class="toc"&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#naive-implementation"&gt;Naive Implementation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#enhanced-implementation"&gt;Enhanced Implementation&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#replacing-unique_together"&gt;Replacing unique_together&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#identifying-duplicate-indexes"&gt;Identifying Duplicate Indexes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#identifying-blocking-migrations"&gt;Identifying Blocking Migrations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#safely-migrating-foreign-key"&gt;Safely Migrating Foreign Key&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#reversible-migration-operations"&gt;Reversible Migration Operations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#concurrent-index-operations"&gt;Concurrent Index Operations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#indexes-on-foreign-keys"&gt;Indexes on Foreign Keys&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#partial-foreign-key-indexes"&gt;Partial Foreign Key Indexes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#using-built-in-concurrent-index-operations"&gt;Using Built-in Concurrent Index Operations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#order-migration-operations"&gt;Order Migration Operations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#locking-across-relations"&gt;Locking Across Relations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#permissive-no-key-locks"&gt;Permissive No Key Locks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#the-final-model"&gt;The Final Model&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#takeaways"&gt;Takeaways&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#the-mandatory-ai-angle"&gt;The Mandatory AI Angle&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;p&gt;&lt;/details&gt;&lt;/p&gt;
&lt;div class="admonition info"&gt;
&lt;p class="admonition-title"&gt;Watch&lt;/p&gt;
&lt;p&gt;📺 This article is inspired by a talk I gave at DjangoCon EU. Watch it &lt;a href="https://youtu.be/l1xi_yKnhbE?si=nUu-ykTS31uOdl-V" rel="noopener"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;h2 id="naive-implementation"&gt;&lt;a class="toclink" href="#naive-implementation"&gt;Naive Implementation&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Imagine a simple application to manage a product catalog:&lt;/p&gt;
&lt;figure&gt;&lt;img alt="Generated using dbdiagram.io" src="images/01-django-foreign-keys-catalog-erd.png"&gt;&lt;figcaption&gt;Generated using dbdiagram.io&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The first table, or model, in the catalog is the &lt;code&gt;Category&lt;/code&gt; model:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Category&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&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="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BigAutoField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;primary_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&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;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Categories can be "household items", "fruit", "apparel", and so on.&lt;/p&gt;
&lt;p&gt;Next, a model to store products:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&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;unique_together&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;category&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;category_sort_order&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BigAutoField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;primary_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&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;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TextField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;category&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;on_delete&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PROTECT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;related_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;products&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;category_sort_order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IntegerField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;created_by&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;on_delete&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PROTECT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;related_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;+&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;last_edited_by&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;on_delete&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PROTECT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;related_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;+&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;null&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Products have a name and description, and they are associated with a category by foreign key.&lt;/p&gt;
&lt;p&gt;To make sure we present products in the right order in the UI, we add a sort order within the category. To make sure the order is deterministic, we add a unique constraint to prevent products in the same category from having the same sort order.&lt;/p&gt;
&lt;p&gt;Finally, we added two columns to keep track of the user who created the product and the user who last edited the product. This is mostly for auditing purposes.&lt;/p&gt;
&lt;p&gt;Since we are on the subject of foreign keys, here are the 3 foreign keys we have in this very simple product table:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;category&lt;/code&gt;: foreign key to the catalog model we created.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;created_by&lt;/code&gt;: foreign key to the user model.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;last_edited_by&lt;/code&gt;: foreign key to the user model. Can be null.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This naive implementation, as the name suggests, is very naive! There is a lot more than meets the eye. In the next sections, we'll make adjustments to improve this model.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="enhanced-implementation"&gt;&lt;a class="toclink" href="#enhanced-implementation"&gt;Enhanced Implementation&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;It's been said that developers spend more time reading code than writing code. As someone who has spent a fair amount of time giving code reviews and going through code to understand how it works, I believe this to be true. We started with a naive implementation and now we'll review the code and make it better.&lt;/p&gt;
&lt;h3 id="replacing-unique_together"&gt;&lt;a class="toclink" href="#replacing-unique_together"&gt;Replacing &lt;code&gt;unique_together&lt;/code&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To control the order we present products in the UI we added a &lt;code&gt;category_sort_order&lt;/code&gt; to each product. To make sure two products in the same category don't have the same value, we added a unique constraint on the combination of &lt;code&gt;category&lt;/code&gt; and &lt;code&gt;category_sort_order&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&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;unique_together&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;category&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;category_sort_order&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The &lt;a href="https://docs.djangoproject.com/en/5.1/ref/models/options/#django.db.models.Options.unique_together" rel="noopener"&gt;Django documentation on &lt;code&gt;unique_together&lt;/code&gt;&lt;/a&gt; includes a special note we should consider:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Use UniqueConstraint with the constraints option instead.&lt;/strong&gt;
UniqueConstraint provides more functionality than unique_together. unique_together may be deprecated in the future.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Unique together is deprecated and discouraged, so let's replace it with a unique constraint:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gu"&gt;@@ -13,9 +13,15 @@ class Category(models.Model):&lt;/span&gt;

&lt;span class="w"&gt; &lt;/span&gt;class Product(models.Model):
&lt;span class="w"&gt; &lt;/span&gt;    class Meta:
&lt;span class="gd"&gt;-        unique_together = (&lt;/span&gt;
&lt;span class="gd"&gt;-            (&amp;#39;category&amp;#39;, &amp;#39;category_sort_order&amp;#39;, ),&lt;/span&gt;
&lt;span class="gd"&gt;-        )&lt;/span&gt;
&lt;span class="gi"&gt;+        constraints = [&lt;/span&gt;
&lt;span class="gi"&gt;+            models.UniqueConstraint(&lt;/span&gt;
&lt;span class="gi"&gt;+                name=&amp;#39;product_category_sort_order_uk&amp;#39;,&lt;/span&gt;
&lt;span class="gi"&gt;+                fields=(&lt;/span&gt;
&lt;span class="gi"&gt;+                    &amp;#39;category&amp;#39;,&lt;/span&gt;
&lt;span class="gi"&gt;+                    &amp;#39;category_sort_order&amp;#39;,&lt;/span&gt;
&lt;span class="gi"&gt;+                ),&lt;/span&gt;
&lt;span class="gi"&gt;+            ),&lt;/span&gt;
&lt;span class="gi"&gt;+        ]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Unique constraint is easier to modify, allows to use advanced B-Tree index features (we'll use some later) and is recommended by the documentation, so let's always use that!&lt;/p&gt;
&lt;div class="admonition info"&gt;
&lt;p class="admonition-title"&gt;💡 Takeaway&lt;/p&gt;
&lt;p&gt;Don't use &lt;code&gt;unique_together&lt;/code&gt;, use &lt;code&gt;UniqueConstraint&lt;/code&gt; instead.&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id="identifying-duplicate-indexes"&gt;&lt;a class="toclink" href="#identifying-duplicate-indexes"&gt;Identifying Duplicate Indexes&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Now that we got rid of &lt;code&gt;unique_together&lt;/code&gt;, let's have a look at the schema:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;catalog&lt;/span&gt;&lt;span class="o"&gt;=#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;catalog_product&lt;/span&gt;
&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="k"&gt;Column&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="k"&gt;Type&lt;/span&gt;
&lt;span class="err"&gt;─────────────────────┼───────────────────────&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt;                  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;bigint&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;character&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;varying&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;category_sort_order&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;integer&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;category_id&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;bigint&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;created_by_id&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;integer&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;last_edited_by_id&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;integer&lt;/span&gt;
&lt;span class="n"&gt;Indexes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;catalog_product_pkey&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;PRIMARY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;btree&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;catalog_product_category_id_35bf920b&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;btree&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;category_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;catalog_product_category_id_category_sort_order_b8206596_uniq&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;UNIQUE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;CONSTRAINT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;btree&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;category_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;category_sort_order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;catalog_product_created_by_id_4e458b98&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;btree&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;created_by_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;catalog_product_last_edited_by_id_05484fb6&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;btree&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;last_edited_by_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;-- ...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;It's easy to get lost in all the information here, but notice that we have two indexes that are prefixed by &lt;code&gt;category&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The first index is the unique constraint we just created:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&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;constraints&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UniqueConstraint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;product_category_sort_order_uk&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;category&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;category_sort_order&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The second index is not that obvious:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# ...&lt;/span&gt;
    &lt;span class="n"&gt;category&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;on_delete&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PROTECT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;related_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;products&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Where is the index, you ask? The answer lies in the &lt;a href="https://docs.djangoproject.com/en/5.1/ref/models/fields/#foreignkey" rel="noopener"&gt;official documentation of the &lt;code&gt;ForeignKey&lt;/code&gt;&lt;/a&gt; field:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A database index is automatically created on the ForeignKey.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;When you define a foreign key, Django implicitly creates an index behind the scenes. In most cases, this is a good idea, but in this case, this field is already (sufficiently) indexed. Reading further in the documentation:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A database index is automatically created on the ForeignKey. &lt;strong&gt;You can disable this by setting &lt;code&gt;db_index&lt;/code&gt; to &lt;code&gt;False&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If we believe that we don't need the index, we can instruct Django not to create it by setting &lt;code&gt;db_index&lt;/code&gt; to &lt;code&gt;False&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gu"&gt;@@ -35,6 +35,8 @@ class Product(models.Model):&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;    category = models.ForeignKey(
&lt;span class="w"&gt; &lt;/span&gt;        to=Category,
&lt;span class="w"&gt; &lt;/span&gt;        on_delete=models.PROTECT,
&lt;span class="w"&gt; &lt;/span&gt;        related_name=&amp;#39;products&amp;#39;,
&lt;span class="gi"&gt;+        # Indexed in unique constraint.&lt;/span&gt;
&lt;span class="gi"&gt;+        db_index=False,&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;    )
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Queries using category can use the unique index instead.&lt;/p&gt;
&lt;div class="admonition warning"&gt;
&lt;p class="admonition-title"&gt;⚠️ Implicit Behavior&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ForeignKey&lt;/code&gt; field implicitly creates an index unless explicitly setting &lt;code&gt;db_index=False&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;To actually remove the index, we first need to generate a migration:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;./manage.py&lt;span class="w"&gt; &lt;/span&gt;makemigrations
Migrations&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;catalog&amp;#39;&lt;/span&gt;:
&lt;span class="w"&gt;  &lt;/span&gt;demo/catalog/migrations/0003_alter_product_category.py
&lt;span class="w"&gt;    &lt;/span&gt;~&lt;span class="w"&gt; &lt;/span&gt;Alter&lt;span class="w"&gt; &lt;/span&gt;field&lt;span class="w"&gt; &lt;/span&gt;category&lt;span class="w"&gt; &lt;/span&gt;on&lt;span class="w"&gt; &lt;/span&gt;product
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;But before we move on to apply this migration, there is another small thing we need to take care of.&lt;/p&gt;
&lt;h3 id="identifying-blocking-migrations"&gt;&lt;a class="toclink" href="#identifying-blocking-migrations"&gt;Identifying Blocking Migrations&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Let's have a look at the migration we just generated to remove the index from the foreign key:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;dependencies&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;catalog&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;0002_alter_product_unique_together_and_more&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;operations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AlterField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;product&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;category&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;db_index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;on_delete&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;django&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deletion&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PROTECT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;related_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;products&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;catalog.category&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The migration looks harmless. Let's dig deeper and review the actual SQL generated by the migration:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;manage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sqlmigrate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;catalog&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0003&lt;/span&gt;
&lt;span class="k"&gt;BEGIN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;--&lt;/span&gt;
&lt;span class="c1"&gt;-- Alter field category on product&lt;/span&gt;
&lt;span class="c1"&gt;--&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;CONSTRAINTS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;catalog_product_category_id_35bf920b_fk_catalog_category_id&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;IMMEDIATE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;TABLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;catalog_product&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;DROP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;CONSTRAINT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;catalog_product_category_id_35bf920b_fk_catalog_category_id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;TABLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;catalog_product&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ADD&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;CONSTRAINT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;catalog_product_category_id_35bf920b_fk_catalog_category_id&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;FOREIGN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;category_id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;REFERENCES&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;catalog_category&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;DEFERRABLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;INITIALLY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;DEFERRED&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;COMMIT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;For the inexperienced eye, this might look OK, but if you really pay attention you'll notice that something very dangerous is going on here.&lt;/p&gt;
&lt;div class="admonition info"&gt;
&lt;p class="admonition-title"&gt;💡 Takeaway&lt;/p&gt;
&lt;p&gt;Always check the SQL generated by migrations.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;When we set &lt;code&gt;db_index=False&lt;/code&gt;, what we wanted to do is to drop the index but keep the constraint. Unfortunately, Django is unable to detect this nuanced change, so instead, &lt;strong&gt;Django is re-creating the entire foreign key constraint without the index!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Re-creating the constraint works in two steps:&lt;/p&gt;
&lt;p&gt;1. Dropping the existing constraint:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;TABLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;catalog_product&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;DROP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;CONSTRAINT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;catalog_product_category_id_35bf920b_fk_catalog_category_id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The database will drop the index on the field and also stop validating the constraint. Dropping the index requires a lock on the table while the index is dropped. This can block some operations on the table.&lt;/p&gt;
&lt;p&gt;2. Creating the constraint:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;ALTER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;TABLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;catalog_product&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ADD&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;CONSTRAINT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;catalog_product_category_id_35bf920b_fk_catalog_category_id&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;FOREIGN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;category_id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;REFERENCES&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;catalog_category&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;DEFERRABLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;INITIALLY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;DEFERRED&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The database first obtains a lock on the table. Then, to make sure the constraint is valid, it needs to make sure all the values in the column have a matching field in the referenced table. In this case, it checks that the product's category exists in the category table. Depending on the size of the tables, this can take some time and interfere with ongoing operations against the table.&lt;/p&gt;
&lt;div class="admonition warning"&gt;
&lt;p class="admonition-title"&gt;⚠️ Implicit Behavior&lt;/p&gt;
&lt;p&gt;When making changes to &lt;code&gt;ForeignKey&lt;/code&gt; Django may implicitly re-create the constraint. This can require an extended locks and block certain operations on the table.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;All of this is really not necessary though. We don't have anything against the constraint, what we really want is to keep the constraint as-is and only drop the index.&lt;/p&gt;
&lt;h3 id="safely-migrating-foreign-key"&gt;&lt;a class="toclink" href="#safely-migrating-foreign-key"&gt;Safely Migrating Foreign Key&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;There are some types of changes to &lt;code&gt;ForeignKey&lt;/code&gt; fields Django is (currently?) unable to detect. As a result, Django may end up re-creating the constraint in the database. This can lead to extended locking and may impact live systems. To avoid that, we need to change the way Django applies the migration. To understand how we can do that, we need to understand how Django generates migrations in the first place:&lt;/p&gt;
&lt;div class="dark--invert"&gt;
&lt;figure&gt;&lt;img alt="&amp;quot;Django migrations&amp;quot;" src="images/02-django-foriegn-key-migrations.svg"&gt;&lt;figcaption&gt;"Django migrations"&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/div&gt;
&lt;p&gt;Generate migrations using &lt;code&gt;makemigrations&lt;/code&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Render a state from the existing migrations&lt;/li&gt;
&lt;li&gt;Compare to the desired state of the models in models.py&lt;/li&gt;
&lt;li&gt;Generate migration operations from the difference&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Applying migrations using &lt;code&gt;migrate&lt;/code&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Generate SQL from the migration operations (&lt;code&gt;sqlmigrate&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Apply to SQL to the database&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Let's break down the case of changing a foreign key:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Django identify that a &lt;code&gt;ForeignKey&lt;/code&gt; has changed: &lt;code&gt;db_index&lt;/code&gt; changed from &lt;code&gt;True&lt;/code&gt; -&amp;gt; &lt;code&gt;False&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Django generates an operation to "sync" the new state: a &lt;code&gt;migrations.AlterField&lt;/code&gt; operation with the new definition of the &lt;code&gt;ForeignKey&lt;/code&gt; field.&lt;/li&gt;
&lt;li&gt;Generate SQL from the migration operation: To "sync" the foreign key Django drops the constraint and re-creates it.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Django identified the change to the field, but it's unable to generate a migration operation to just drop the index.&lt;/p&gt;
&lt;p&gt;If the migration and the SQL generated by Django is not exactly what we want, there is a special operation called &lt;a href="https://docs.djangoproject.com/en/5.1/ref/migration-operations/#django.db.migrations.operations.SeparateDatabaseAndState" rel="noopener"&gt;&lt;code&gt;SeparateDatabaseAndState&lt;/code&gt;&lt;/a&gt; we can use:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A highly specialized operation that lets you mix and match the database (schema-changing) and state (autodetector-powering) aspects of operations.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Using &lt;code&gt;SeparateDatabaseAndState&lt;/code&gt; we can provide one set of operations to execute against Django's internal state, and another set of operations to execute against the database:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gu"&gt;@@ -11,9 +11,18 @@ class Migration(migrations.Migration):&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;    ]

&lt;span class="w"&gt; &lt;/span&gt;    operations = [
&lt;span class="gd"&gt;-        migrations.AlterField(&lt;/span&gt;
&lt;span class="gd"&gt;-            model_name=&amp;#39;product&amp;#39;,&lt;/span&gt;
&lt;span class="gd"&gt;-            name=&amp;#39;category&amp;#39;,&lt;/span&gt;
&lt;span class="gd"&gt;-            field=models.ForeignKey(db_index=False, on_delete=django.db.models.deletion.PROTECT, related_name=&amp;#39;products&amp;#39;, to=&amp;#39;catalog.category&amp;#39;),&lt;/span&gt;
&lt;span class="gd"&gt;-        ),&lt;/span&gt;
&lt;span class="gi"&gt;+        migrations.operations.SeparateDatabaseAndState(&lt;/span&gt;
&lt;span class="gi"&gt;+            state_operations=[&lt;/span&gt;
&lt;span class="gi"&gt;+                migrations.AlterField(&lt;/span&gt;
&lt;span class="gi"&gt;+                    model_name=&amp;#39;product&amp;#39;,&lt;/span&gt;
&lt;span class="gi"&gt;+                    name=&amp;#39;category&amp;#39;,&lt;/span&gt;
&lt;span class="gi"&gt;+                    field=models.ForeignKey(db_index=False, on_delete=django.db.models.deletion.PROTECT, related_name=&amp;#39;products&amp;#39;, to=&amp;#39;catalog.category&amp;#39;),&lt;/span&gt;
&lt;span class="gi"&gt;+                ),&lt;/span&gt;
&lt;span class="gi"&gt;+            ],&lt;/span&gt;
&lt;span class="gi"&gt;+            database_operations=[&lt;/span&gt;
&lt;span class="gi"&gt;+                migrations.RunSQL(&lt;/span&gt;
&lt;span class="gi"&gt;+                    &amp;#39;DROP INDEX catalog_product_category_id_35bf920b&amp;#39;,&lt;/span&gt;
&lt;span class="gi"&gt;+                ),&lt;/span&gt;
&lt;span class="gi"&gt;+            ],&lt;/span&gt;
&lt;span class="gi"&gt;+        )&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;    ]
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The &lt;code&gt;SeparateDatabaseAndState&lt;/code&gt; migration operation accepts two arguments:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;state_operations&lt;/code&gt;: operations to execute against the internal state when aggregating the changes from the migrations. In most cases, this will be the operations Django generated automatically from &lt;code&gt;makemigrations&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;database_operations&lt;/code&gt;: operations to execute in the database. This is where we control exactly what to execute in the database when the migration is applied using &lt;code&gt;migrate&lt;/code&gt;. In our case, we use &lt;code&gt;RunSQL&lt;/code&gt; to execute a &lt;code&gt;DROP INDEX&lt;/code&gt; command.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To demonstrate, this is what will be executed when we apply this migration:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;manage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sqlmigrate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;catalog&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0003&lt;/span&gt;
&lt;span class="k"&gt;BEGIN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;--&lt;/span&gt;
&lt;span class="c1"&gt;-- Custom state/database change combination&lt;/span&gt;
&lt;span class="c1"&gt;--&lt;/span&gt;
&lt;span class="k"&gt;DROP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;INDEX&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;catalog_product_category_id_35bf920b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;COMMIT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Exactly what we want!&lt;/p&gt;
&lt;p&gt;Applying the migration:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;manage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;migrate&lt;/span&gt;
&lt;span class="n"&gt;Operations&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Apply&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;all&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;catalog&lt;/span&gt;
&lt;span class="n"&gt;Running&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Applying&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;catalog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0003&lt;/span&gt;&lt;span class="n"&gt;_alter_product_category&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;OK&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;So are we done...?&lt;/p&gt;
&lt;h3 id="reversible-migration-operations"&gt;&lt;a class="toclink" href="#reversible-migration-operations"&gt;Reversible Migration Operations&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Say you applied the migration to drop the index and it went OK. A few minutes go by and then you realize you made a horrible mistake. You rush back to your laptop to reverse the migration:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;./manage.py&lt;span class="w"&gt; &lt;/span&gt;migrate&lt;span class="w"&gt; &lt;/span&gt;catalog&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0002&lt;/span&gt;
Rendering&lt;span class="w"&gt; &lt;/span&gt;model&lt;span class="w"&gt; &lt;/span&gt;states...&lt;span class="w"&gt; &lt;/span&gt;DONE
Unapplying&lt;span class="w"&gt; &lt;/span&gt;catalog.0003_alter_product_category...
django.db.migrations.exceptions.IrreversibleError:
&lt;span class="hll"&gt;Operation&lt;span class="w"&gt; &lt;/span&gt;&amp;lt;RunSQL&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;catalog.0003_alter_product_category&lt;span class="w"&gt; &lt;/span&gt;is&lt;span class="w"&gt; &lt;/span&gt;not&lt;span class="w"&gt; &lt;/span&gt;reversible
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Oh no! You now have some explaining to do...&lt;/p&gt;
&lt;p&gt;The reason you can't un-apply this migration is that the &lt;code&gt;RunSQL&lt;/code&gt; command to drop the index did not include an opposite operation. This is an easy fix:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gh"&gt;index 962c756..7ad45e0 100644&lt;/span&gt;
&lt;span class="gd"&gt;--- a/demo/catalog/migrations/0003_alter_product_category.py&lt;/span&gt;
&lt;span class="gi"&gt;+++ b/demo/catalog/migrations/0003_alter_product_category.py&lt;/span&gt;
&lt;span class="gu"&gt;@@ -22,6 +22,7 @@ class Migration(migrations.Migration):&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;            database_operations=[
&lt;span class="w"&gt; &lt;/span&gt;                migrations.RunSQL(
&lt;span class="w"&gt; &lt;/span&gt;                    &amp;#39;DROP INDEX catalog_product_category_id_35bf920b&amp;#39;,
&lt;span class="gi"&gt;+                    &amp;#39;CREATE INDEX &amp;quot;catalog_product_category_id_35bf920b&amp;quot; ON &amp;quot;catalog_product&amp;quot; (&amp;quot;category_id&amp;quot;)&amp;#39;,&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;                ),
&lt;span class="w"&gt; &lt;/span&gt;            ],
&lt;span class="w"&gt; &lt;/span&gt;        )
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The second argument to &lt;code&gt;RunSQL&lt;/code&gt; is the reverse operation - what to execute to undo the migration. In this case, to reverse dropping an index is to create an index!&lt;/p&gt;
&lt;p&gt;But, where do you get this SQL from? Usually from the migration that added it. In this case, the initial migration:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;manage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sqlmigrate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;catalog&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0001&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="k"&gt;BEGIN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="c1"&gt;--&lt;/span&gt;
&lt;span class="c1"&gt;-- Create model Product&lt;/span&gt;
&lt;span class="c1"&gt;--&lt;/span&gt;
&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;INDEX&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;catalog_product_category_id_35bf920b&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;catalog_product&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;category_id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Now if you found out you made a horrible mistake and you want to reverse the migration:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;./manage.py&lt;span class="w"&gt; &lt;/span&gt;migrate&lt;span class="w"&gt; &lt;/span&gt;catalog&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0002&lt;/span&gt;
Rendering&lt;span class="w"&gt; &lt;/span&gt;model&lt;span class="w"&gt; &lt;/span&gt;states...&lt;span class="w"&gt; &lt;/span&gt;DONE
Unapplying&lt;span class="w"&gt; &lt;/span&gt;catalog.0003_alter_product_category...&lt;span class="w"&gt; &lt;/span&gt;OK
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Great!&lt;/p&gt;
&lt;div class="admonition info"&gt;
&lt;p class="admonition-title"&gt;💡 Takeaway&lt;/p&gt;
&lt;p&gt;Provide reverse operations whenever possible - you don't know when you're going to need it.&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id="concurrent-index-operations"&gt;&lt;a class="toclink" href="#concurrent-index-operations"&gt;Concurrent Index Operations&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;You now made sure to only drop the index without recreating the constraint and you provided a reverse operation in case you made a mistake and want to "undo". That's all great, but just one more thing... from &lt;a href="https://www.postgresql.org/docs/current/sql-dropindex.html" rel="noopener"&gt;PostgreSQL documentation on &lt;code&gt;DROP INDEX&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A normal DROP INDEX acquires an ACCESS EXCLUSIVE lock on the table, blocking other accesses until the index drop can be completed.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;To drop the index, PostgreSQL acquires a lock on the table which blocks other operations. If the index is tiny, that's probably fine, but what if it's a very big index? Dropping a big index can take some time and we can't lock a live table for very long.&lt;/p&gt;
&lt;p&gt;PostgreSQL provides an option to create an index without acquiring restrictive locks on the index:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gu"&gt;@@ -5,6 +5,7 @@ from django.db import migrations, models&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;class Migration(migrations.Migration):
&lt;span class="gi"&gt;+    atomic = False&lt;/span&gt;

&lt;span class="w"&gt; &lt;/span&gt;    dependencies = [
&lt;span class="w"&gt; &lt;/span&gt;        (&amp;#39;catalog&amp;#39;, &amp;#39;0002_alter_product_unique_together_and_more&amp;#39;),
&lt;span class="gu"&gt;@@ -21,7 +22,7 @@ class Migration(migrations.Migration):&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;            ],
&lt;span class="w"&gt; &lt;/span&gt;            database_operations=[
&lt;span class="w"&gt; &lt;/span&gt;                migrations.RunSQL(
&lt;span class="gd"&gt;-                    &amp;#39;DROP INDEX catalog_product_category_id_35bf920b&amp;#39;,&lt;/span&gt;
&lt;span class="gi"&gt;+                    &amp;#39;DROP INDEX CONCURRENTLY catalog_product_category_id_35bf920b&amp;#39;,&lt;/span&gt;
&lt;span class="gd"&gt;-                    &amp;#39;CREATE INDEX &amp;quot;catalog_product_category_id_35bf920b&amp;quot; ON &amp;quot;catalog_product&amp;quot; (&amp;quot;category_id&amp;quot;)&amp;#39;,&lt;/span&gt;
&lt;span class="gi"&gt;+                    &amp;#39;CREATE INDEX CONCURRENTLY &amp;quot;catalog_product_category_id_35bf920b&amp;quot; ON &amp;quot;catalog_product&amp;quot; (&amp;quot;category_id&amp;quot;)&amp;#39;,&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;                ),
&lt;span class="w"&gt; &lt;/span&gt;            ],
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Dropping an index concurrently works in two phases. First the index is marked as "deleted" in the dictionary table. During this time, ongoing transactions can still use it. Next, the index is actually dropped. This way of dropping indexes requires minimal locking but can take a bit more time.&lt;/p&gt;
&lt;div class="admonition info"&gt;
&lt;p class="admonition-title"&gt;💡 Takeaway&lt;/p&gt;
&lt;p&gt;Use concurrent index operations in busy systems. Concurrent operations can take a bit more time but they don't block operations to the table while they execute.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Databases such as PostgreSQL that support &lt;a href="https://wiki.postgresql.org/wiki/Transactional_DDL_in_PostgreSQL:_A_Competitive_Analysis#Transactional_DDL" rel="noopener"&gt;transactional DDL&lt;/a&gt;, can execute &lt;code&gt;CREATE&lt;/code&gt;, &lt;code&gt;DROP&lt;/code&gt; and &lt;code&gt;ALTER&lt;/code&gt; commands inside a database transaction. This is a very useful feature because it allows you to execute schema changes atomically (it also allows you to do some wild things like &lt;a href="sql-tricks-application-dba#make-indexes-invisible"&gt;making indexes "invisible"&lt;/a&gt;). Unfortunately, concurrent operations cannot be executed inside a database transaction. This means we need to set the entire migration to be non-atomic by setting &lt;code&gt;atomic=False&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;In an atomic migration, if something fails in the middle, the entire transaction is rolled-back and it's like the migration never ran. In a non-atomic migration however, if something fails in the middle, you can end up with an incomplete execution and an inconsistent state. To reduce the risk of getting stuck with a half-applied migration, if you have operations such as drop/create index concurrently in the migration, it's best to split the migration and move these operations to a separate migration.&lt;/p&gt;
&lt;div class="admonition info"&gt;
&lt;p class="admonition-title"&gt;💡 Takeaway&lt;/p&gt;
&lt;p&gt;To avoid incomplete migrations, move operations that can't be executed atomically to a separate migration.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;This is the final migration:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;atomic&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;

    &lt;span class="n"&gt;dependencies&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;catalog&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;0002_alter_product_unique_together_and_more&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;operations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;operations&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SeparateDatabaseAndState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;state_operations&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AlterField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;product&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;category&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db_index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;on_delete&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;django&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deletion&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PROTECT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;related_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;products&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;catalog.category&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="n"&gt;database_operations&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RunSQL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="s1"&gt;&amp;#39;DROP INDEX CONCURRENTLY catalog_product_category_id_35bf920b&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="s1"&gt;&amp;#39;CREATE INDEX CONCURRENTLY &amp;quot;catalog_product_category_id_35bf920b&amp;quot; ON &amp;quot;catalog_product&amp;quot; (&amp;quot;category_id&amp;quot;)&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;In this migration we prevented Django from re-creating the entire foreign key and instead only drop the index. This migration also uses concurrent index operations so it's safe to execute on a live system, and if you make a mistake it is also reversible.&lt;/p&gt;
&lt;h3 id="indexes-on-foreign-keys"&gt;&lt;a class="toclink" href="#indexes-on-foreign-keys"&gt;Indexes on Foreign Keys&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;So far we handled the foreign key on category, so let's move on to the next foreign key in the model:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;#...&lt;/span&gt;
    &lt;span class="n"&gt;created_by&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;on_delete&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PROTECT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;related_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;+&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Django will implicitly add an index on a &lt;code&gt;ForeignKey&lt;/code&gt; field unless explicitly stated otherwise. Since we didn't define &lt;code&gt;db_index=False&lt;/code&gt; on this field, Django created an index on &lt;code&gt;created_by&lt;/code&gt;. But, do we really need it?&lt;/p&gt;
&lt;p&gt;To answer this question, we need first to ask how this field is being used:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Used primarily for audit purposes&lt;/li&gt;
&lt;li&gt;Products are rarely queried by the user who created them&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;From these two use-cases, it seems like no one is actually going to query the table by &lt;code&gt;created_by&lt;/code&gt;, so this index is most likely unnecessary. However, there is another use for this index, which is not as obvious.&lt;/p&gt;
&lt;p&gt;To demonstrate, let's create a user:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;haki&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;    &lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;haki&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;    &lt;span class="n"&gt;first_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;haki&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;    &lt;span class="n"&gt;last_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;benita&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;haki&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Now turn on SQL logging on and delete the user we just created a second ago:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;haki&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.438&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;FROM&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;catalog_product&amp;quot;&lt;/span&gt; &lt;span class="n"&gt;WHERE&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;catalog_product&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;created_by_id&amp;quot;&lt;/span&gt; &lt;span class="n"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;102&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;102&lt;/span&gt;&lt;span class="p"&gt;,)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.002&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;FROM&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;catalog_product&amp;quot;&lt;/span&gt; &lt;span class="n"&gt;WHERE&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;catalog_product&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;last_edited_by_id&amp;quot;&lt;/span&gt; &lt;span class="n"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;102&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;102&lt;/span&gt;&lt;span class="p"&gt;,)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;BEGIN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.002&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;DELETE&lt;/span&gt; &lt;span class="n"&gt;FROM&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;django_admin_log&amp;quot;&lt;/span&gt; &lt;span class="n"&gt;WHERE&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;django_admin_log&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;user_id&amp;quot;&lt;/span&gt; &lt;span class="n"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;102&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;102&lt;/span&gt;&lt;span class="p"&gt;,)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.001&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;DELETE&lt;/span&gt; &lt;span class="n"&gt;FROM&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;auth_user_groups&amp;quot;&lt;/span&gt; &lt;span class="n"&gt;WHERE&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;auth_user_groups&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;user_id&amp;quot;&lt;/span&gt; &lt;span class="n"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;102&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;102&lt;/span&gt;&lt;span class="p"&gt;,)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.001&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;DELETE&lt;/span&gt; &lt;span class="n"&gt;FROM&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;auth_user_user_permissions&amp;quot;&lt;/span&gt; &lt;span class="n"&gt;WHERE&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;auth_user_user_permissions&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;user_id&amp;quot;&lt;/span&gt; &lt;span class="n"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;102&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;102&lt;/span&gt;&lt;span class="p"&gt;,)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.001&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;DELETE&lt;/span&gt; &lt;span class="n"&gt;FROM&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;auth_user&amp;quot;&lt;/span&gt; &lt;span class="n"&gt;WHERE&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;auth_user&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;id&amp;quot;&lt;/span&gt; &lt;span class="n"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;102&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;102&lt;/span&gt;&lt;span class="p"&gt;,)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.368&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;COMMIT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;auth.User&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;A lot of things are happening here! Let's break it down:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Django checks if there are products that were created by or last edited by this user.&lt;/li&gt;
&lt;li&gt;Django deletes any admin logs, group memberships and permissions associated with this user.&lt;/li&gt;
&lt;li&gt;Django actually deletes the user from the user tables.&lt;/li&gt;
&lt;li&gt;Django commits the transaction and then the database does all of these checks too!&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This brings us to the next, less obvious way, indexes on foreign keys are being used - to validate the foreign key constraint. If the foreign key is defined with &lt;code&gt;on_delete=PROTECT&lt;/code&gt;, the index is used to make sure there are no related objects referencing a specific object. In our case, products that reference the user we are about to delete. If the foreign key is defined with &lt;code&gt;on_delete=CASCADE&lt;/code&gt;, the index is used to delete the related objects. In our case, deleting the user may also delete products referencing the user.&lt;/p&gt;
&lt;p&gt;You may have noticed that Django &lt;code&gt;delete()&lt;/code&gt; function returns a counter-like structure that keeps how many objects were deleted for each type of model. This is why despite the fact the database also does all of these checks, Django is also doing them. The indexes on the foreign key are working extra-hard here.&lt;/p&gt;
&lt;div class="admonition info"&gt;
&lt;p class="admonition-title"&gt;💡 Takeaway&lt;/p&gt;
&lt;p&gt;Indexes on foreign keys are used indirectly when deleting related objects. Removing these indexes may cause unexpected and hard to debug performance issues with deletes.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Now that we know this index is in-fact necessary, we explicitly set &lt;code&gt;db_index&lt;/code&gt; on the field and add an appropriate comment so the next developer understands why we decided to keep it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gu"&gt;@@ -44,7 +44,8 @@&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;class Product(models.Model):
&lt;span class="w"&gt; &lt;/span&gt;    created_by = models.ForeignKey(
&lt;span class="w"&gt; &lt;/span&gt;        to=User,
&lt;span class="w"&gt; &lt;/span&gt;        on_delete=models.PROTECT,
&lt;span class="w"&gt; &lt;/span&gt;        related_name=&amp;#39;+&amp;#39;,
&lt;span class="gi"&gt;+        # Used to speed up user deletion.&lt;/span&gt;
&lt;span class="gi"&gt;+        db_index=True,&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;    )
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;When someone else (or you in a couple of months) encounters this comment, they won't have to go through the entire process again.&lt;/p&gt;
&lt;div class="admonition info"&gt;
&lt;p class="admonition-title"&gt;💡 Takeaway&lt;/p&gt;
&lt;p&gt;Always explicitly set &lt;code&gt;db_index&lt;/code&gt; on &lt;code&gt;ForeignKey&lt;/code&gt; &lt;em&gt;and&lt;/em&gt; add a comment on how it's being used.&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id="partial-foreign-key-indexes"&gt;&lt;a class="toclink" href="#partial-foreign-key-indexes"&gt;Partial Foreign Key Indexes&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;So far we covered two of three foreign keys - &lt;code&gt;category&lt;/code&gt; and &lt;code&gt;created_by&lt;/code&gt;. Here is the last one:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;last_edited_by&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;on_delete&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PROTECT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;related_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;+&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;null&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Just like the index on &lt;code&gt;created_by&lt;/code&gt;, this index is used mostly for audit purposes. Nobody wants to query for products last edited by some user. However, we've been down this road before and we know this index is used when users are deleted, so we'll keep it. But, before we call it a day, there is something we can still do with this index.&lt;/p&gt;
&lt;p&gt;To demonstrate we first need to add some data.&lt;/p&gt;
&lt;p&gt;Create 100 users:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.contrib.auth.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;

&lt;span class="n"&gt;users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;user&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;user&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;@email.com&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;first_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;User &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&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;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Create 50 categories:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;catalog.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Category&lt;/span&gt;

&lt;span class="n"&gt;categories&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="n"&gt;Category&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Category &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&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;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Create 1,000,000 products:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;random&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.utils&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;lorem_ipsum&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;catalog.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Product&lt;/span&gt;

&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8080&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;Product&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bulk_create&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;
    &lt;span class="n"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Product &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;lorem_ipsum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;words&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;categories&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;category_sort_order&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;created_by&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;last_edited_by&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&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;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1_000_000&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="n"&gt;batch_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Notice that only one in every 1,000 products has been edited.&lt;/p&gt;
&lt;p&gt;Now that we have some data, let's have a look at the indexes:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;catalog&lt;/span&gt;&lt;span class="o"&gt;=#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;di&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;Schema&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;                  &lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;Size&lt;/span&gt;
&lt;span class="err"&gt;────────┼──────────────────────────────────────────────┼─────────&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;catalog_product_created_by_id_4e458b98&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;6440&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;kB&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;catalog_product_last_edited_by_id_05484fb6&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;6320&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;kB&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;catalog_product_pkey&lt;/span&gt;&lt;span class="w"&gt;                         &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;21&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;MB&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;product_category_sort_order_uk&lt;/span&gt;&lt;span class="w"&gt;               &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;MB&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;There is one strange thing going on. If you don't spot this right away that's fine, most people don't.&lt;/p&gt;
&lt;p&gt;Consider this query to check how many users we have in &lt;code&gt;created_by&lt;/code&gt; and &lt;code&gt;last_edited_by&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;catalog&lt;/span&gt;&lt;span class="o"&gt;=#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;created_by_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;created_by&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;last_edited_by_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;last_edited_by&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;catalog_product&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;created_by&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;last_edited_by&lt;/span&gt;
&lt;span class="err"&gt;────────────┼────────────────&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="mi"&gt;1000000&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Out of 1M rows, all products have a value in &lt;code&gt;created_by&lt;/code&gt;, but only 1,000 rows have a value for &lt;code&gt;last_edited_by&lt;/code&gt; - that's ~99.9% empty values! If that's the case, how come indexes on both these fields are the same size:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;catalog&lt;/span&gt;&lt;span class="o"&gt;=#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;di&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;Schema&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;                  &lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="w"&gt;                        &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;Size&lt;/span&gt;
&lt;span class="err"&gt;────────┼──────────────────────────────────────────────┼─────────&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;catalog_product_created_by_id_4e458b98&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;6440&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;kB&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;catalog_product_last_edited_by_id_05484fb6&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;6320&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;kB&lt;/span&gt;
&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;catalog_product_pkey&lt;/span&gt;&lt;span class="w"&gt;                         &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;21&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;MB&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;product_category_sort_order_uk&lt;/span&gt;&lt;span class="w"&gt;               &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;MB&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Both indexes are ~6MB, but one has 1M values and the other only 1K values. The reason both indexes are the same size is that in PostgreSQL, null values are indexed!&lt;/p&gt;
&lt;div class="admonition info"&gt;
&lt;p class="admonition-title"&gt;💡 Takeaway&lt;/p&gt;
&lt;p&gt;Null values are indexed (In all major databases except Oracle).&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;I started my career as an Oracle DBA, where null values are not indexed. It took me some time (and a lot of expensive storage) until I realized that in PostgreSQL null values are indexed.&lt;/p&gt;
&lt;p&gt;We have a foreign key column which is mostly used to validate the constraint and it is 99.9% empty. What if we could only index the rows which are not null? From &lt;a href="https://www.postgresql.org/docs/current/indexes-partial.html" rel="noopener"&gt;PostgreSQL documentation on "partial index"&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A partial index is an index built over a subset of a table; the subset is defined by a conditional expression [...]. The index contains entries only for those table rows that satisfy the predicate.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Exactly what we need! Let's replace the index on the &lt;code&gt;ForeignKey&lt;/code&gt; with a partial B-Tree index:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gu"&gt;@@ -22,6 +22,13 @@ class Product(models.Model):&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;   class Meta:
&lt;span class="gi"&gt;+        indexes = (&lt;/span&gt;
&lt;span class="gi"&gt;+            models.Index(&lt;/span&gt;
&lt;span class="gi"&gt;+                name=&amp;#39;product_last_edited_by_part_ix&amp;#39;,&lt;/span&gt;
&lt;span class="gi"&gt;+                fields=(&amp;#39;last_edited_by&amp;#39;,),&lt;/span&gt;
&lt;span class="gi"&gt;+                condition=models.Q(last_edited_by__isnull=False),&lt;/span&gt;
&lt;span class="gi"&gt;+            ),&lt;/span&gt;
&lt;span class="gi"&gt;+        )&lt;/span&gt;
&lt;span class="gu"&gt;@@ -53,5 +60,7 @@ class Product(models.Model):&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;    last_edited_by: User | None = models.ForeignKey(
&lt;span class="w"&gt; &lt;/span&gt;        on_delete=models.PROTECT,
&lt;span class="w"&gt; &lt;/span&gt;        related_name=&amp;#39;+&amp;#39;,
&lt;span class="w"&gt; &lt;/span&gt;        null=True,
&lt;span class="gi"&gt;+        # Indexed in Meta.&lt;/span&gt;
&lt;span class="gi"&gt;+        db_index=False,&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;    )
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We start by setting &lt;code&gt;db_index=False&lt;/code&gt; to instruct Django we don't want the default index. We also make sure to add a comment saying that the field is indexed in &lt;code&gt;Meta&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Next, we add a new index in &lt;code&gt;Meta&lt;/code&gt;. What makes this index partial is the condition we added in the index definition:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;product_last_edited_by_part_ix&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;last_edited_by&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,),&lt;/span&gt;
&lt;span class="hll"&gt;    &lt;span class="n"&gt;condition&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Q&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;last_edited_by__isnull&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This will make the index include only values which are not null. If we generate and apply the migration, these are the sizes of the indexes:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;catalog&lt;/span&gt;&lt;span class="o"&gt;=#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="n"&gt;di&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;Schema&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;                  &lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="w"&gt;                  &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;Size&lt;/span&gt;
&lt;span class="err"&gt;────────┼────────────────────────────────────────┼─────────&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;catalog_product_created_by_id_4e458b98&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;6440&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;kB&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;product_last_edited_by_part_ix&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;kB&lt;/span&gt;
&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;catalog_product_pkey&lt;/span&gt;&lt;span class="w"&gt;                   &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;21&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;MB&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;product_category_sort_order_uk&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;MB&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The full index is ~6.4MB, the partial index only 32 kB. That's ~99.5% smaller.&lt;/p&gt;
&lt;div class="admonition info"&gt;
&lt;p class="admonition-title"&gt;💡 Takeaway&lt;/p&gt;
&lt;p&gt;Use partial indexes when you don't need to index all the values in a column. Nullable foreign key columns are usually great candidates. If you are not convinced check out the story about &lt;a href="https://hakibenita.com/postgresql-unused-index-size" rel="noopener"&gt;The Unexpected Find That Freed 20GB of Unused Index Space&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id="using-built-in-concurrent-index-operations"&gt;&lt;a class="toclink" href="#using-built-in-concurrent-index-operations"&gt;Using Built-in Concurrent Index Operations&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;In the previous section we managed to save some $$$ by switching to a partial index. But with all the excitement we forgot something very &lt;em&gt;very&lt;/em&gt; important - always check the generated SQL before applying migrations!&lt;/p&gt;
&lt;p&gt;This is the migration:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;dependencies&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;catalog&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;0005_alter_product_created_by&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;swappable_dependency&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AUTH_USER_MODEL&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;operations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AlterField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;product&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;last_edited_by&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;db_index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;null&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;on_delete&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;django&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deletion&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PROTECT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;related_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;+&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AUTH_USER_MODEL&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddIndex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;product&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;condition&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Q&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;last_edited_by__isnull&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
                &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;last_edited_by&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;product_last_edited_by_part_ix&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Looks about right. The SQL:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;manage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sqlmigrate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;catalog&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0006&lt;/span&gt;
&lt;span class="k"&gt;BEGIN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;--&lt;/span&gt;
&lt;span class="c1"&gt;-- Alter field last_edited_by on product&lt;/span&gt;
&lt;span class="c1"&gt;--&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;CONSTRAINTS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;catalog_product_last_edited_by_id_05484fb6_fk_auth_user_id&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;IMMEDIATE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;TABLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;catalog_product&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;DROP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;CONSTRAINT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;catalog_product_last_edited_by_id_05484fb6_fk_auth_user_id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;ALTER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;TABLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;catalog_product&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ADD&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;CONSTRAINT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;catalog_product_last_edited_by_id_05484fb6_fk_auth_user_id&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;FOREIGN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;last_edited_by_id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;REFERENCES&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;auth_user&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;DEFERRABLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;INITIALLY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;DEFERRED&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;--&lt;/span&gt;
&lt;span class="c1"&gt;-- Create index product_last_edited_by_part_ix on field(s) last_edited_by of model product&lt;/span&gt;
&lt;span class="c1"&gt;--&lt;/span&gt;
&lt;span class="k"&gt;DROP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;INDEX&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;IF&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;EXISTS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;product_last_edited_by_part_ix&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;INDEX&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;product_last_edited_by_part_ix&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;catalog_product&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;last_edited_by_id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;last_edited_by_id&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;IS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NOT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;COMMIT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Oh no! It's recreating the foreign key from scratch &lt;em&gt;again&lt;/em&gt;. We already know what to do - use &lt;code&gt;SeparateDatabaseAndState&lt;/code&gt; to only drop the index:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gu"&gt;@@ -13,10 +13,20 @@ class Migration(migrations.Migration):&lt;/span&gt;
operations = [
&lt;span class="gd"&gt;-     migrations.AlterField(&lt;/span&gt;
&lt;span class="gd"&gt;-         model_name=&amp;#39;product&amp;#39;,&lt;/span&gt;
&lt;span class="gd"&gt;-         name=&amp;#39;last_edited_by&amp;#39;,&lt;/span&gt;
&lt;span class="gd"&gt;-         field=models.ForeignKey(db_index=False, null=True, on_delete=django.db.models.deletion.PROTECT, # ...&lt;/span&gt;
&lt;span class="gi"&gt;+     migrations.operations.SeparateDatabaseAndState(&lt;/span&gt;
&lt;span class="gi"&gt;+         state_operations=[&lt;/span&gt;
&lt;span class="gi"&gt;+             migrations.AlterField(&lt;/span&gt;
&lt;span class="gi"&gt;+                 model_name=&amp;#39;product&amp;#39;,&lt;/span&gt;
&lt;span class="gi"&gt;+                 name=&amp;#39;last_edited_by&amp;#39;,&lt;/span&gt;
&lt;span class="gi"&gt;+                 field=models.ForeignKey(db_index=False, null=True, on_delete=django.db.models.deletion.PROTECT, # ...&lt;/span&gt;
&lt;span class="gi"&gt;+             ),&lt;/span&gt;
&lt;span class="gi"&gt;+         ],&lt;/span&gt;
&lt;span class="gi"&gt;+         database_operations=[&lt;/span&gt;
&lt;span class="gi"&gt;+             migrations.RunSQL(&lt;/span&gt;
&lt;span class="gi"&gt;+                 &amp;#39;DROP INDEX CONCURRENTLY catalog_product_last_edited_by_id_05484fb6;&amp;#39;,&lt;/span&gt;
&lt;span class="gi"&gt;+                 &amp;#39;CREATE INDEX CONCURRENTLY catalog_product_last_edited_by_id_05484fb6 ON public.catalog_product USING btree (last_edited_by_id)&amp;#39;,&lt;/span&gt;
&lt;span class="gi"&gt;+             ),&lt;/span&gt;
&lt;span class="gi"&gt;+         ],&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The migration will now drop the index instead of re-creating the constraint.&lt;/p&gt;
&lt;p&gt;Next, we want to create the partial index concurrently to not interrupt any live system. So far we used &lt;code&gt;RunSQL&lt;/code&gt; for concurrent operations, but this time, we can use one of the built-in Django concurrent operations instead:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gu"&gt;@@ -3,9 +3,11 @@&lt;/span&gt;
&lt;span class="gi"&gt;+from django.contrib.postgres.operations import AddIndexConcurrently&lt;/span&gt;

&lt;span class="w"&gt; &lt;/span&gt;class Migration(migrations.Migration):
&lt;span class="gi"&gt;+    atomic = False&lt;/span&gt;

&lt;span class="w"&gt; &lt;/span&gt;    dependencies = [
&lt;span class="w"&gt; &lt;/span&gt;        (&amp;#39;catalog&amp;#39;, &amp;#39;0005_alter_product_created_by&amp;#39;),
&lt;span class="gu"&gt;@@ -28,7 +30,7 @@ class Migration(migrations.Migration):&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;                ),
&lt;span class="w"&gt; &lt;/span&gt;            ],
&lt;span class="w"&gt; &lt;/span&gt;        ),
&lt;span class="gd"&gt;-        migrations.AddIndex(&lt;/span&gt;
&lt;span class="gi"&gt;+        AddIndexConcurrently(&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;            model_name=&amp;#39;product&amp;#39;,
&lt;span class="w"&gt; &lt;/span&gt;            index=models.Index(
&lt;span class="w"&gt; &lt;/span&gt;               condition=models.Q((&amp;#39;last_edited_by__isnull&amp;#39;, False)),
&lt;span class="w"&gt; &lt;/span&gt;               fields=[&amp;#39;last_edited_by&amp;#39;],
&lt;span class="w"&gt; &lt;/span&gt;               name=&amp;#39;product_last_edited_by_part_ix&amp;#39;,
&lt;span class="w"&gt; &lt;/span&gt;           ),
&lt;span class="w"&gt; &lt;/span&gt;        ),
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Django offers two special drop-in &lt;a href="https://docs.djangoproject.com/en/5.1/ref/contrib/postgres/operations/#concurrent-index-operations" rel="noopener"&gt;concurrent migration index operations for PostgreSQL&lt;/a&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;AddIndex&lt;/code&gt; -&amp;gt; &lt;code&gt;AddIndexConcurrently&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RemoveIndex&lt;/code&gt; -&amp;gt; &lt;code&gt;RemoveIndexConcurrently&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These operations are only available for PostgreSQL. For other databases you still need to use &lt;code&gt;RunSQL&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The reason we couldn't use these operations for the implicit index on &lt;code&gt;ForeignKey&lt;/code&gt; is that these operations can only be used for indexes defined in &lt;code&gt;Meta.indexes&lt;/code&gt;, and not for implicitly created indexes or indexes created outside Django's context.&lt;/p&gt;
&lt;h3 id="order-migration-operations"&gt;&lt;a class="toclink" href="#order-migration-operations"&gt;Order Migration Operations&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;So far we prevented the migration from re-creating the foreign key and made sure all index operations are concurrent. This is the current migration:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;atomic&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;

    &lt;span class="n"&gt;dependencies&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;catalog&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;0005_alter_product_created_by&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;swappable_dependency&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AUTH_USER_MODEL&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="n"&gt;operations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;operations&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SeparateDatabaseAndState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;state_operations&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AlterField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;product&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;last_edited_by&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db_index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;null&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;on_delete&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;django&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deletion&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt; &lt;span class="c1"&gt;#...&lt;/span&gt;
            &lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="n"&gt;database_operations&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="n"&gt;migrations&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RunSQL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="s1"&gt;&amp;#39;DROP INDEX catalog_product_last_edited_by_id_05484fb6;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="s1"&gt;&amp;#39;CREATE INDEX catalog_product_last_edited_by_id_05484fb6 ON public.catalog_product USING btree (last_edited_by_id)&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;AddIndexConcurrently&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;product&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;condition&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Q&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;last_edited_by__isnull&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;last_edited_by&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="c1"&gt;# ...&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;If you pay close attention, this migration can still impact a live system. Consider the current order of migration operations:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Drop full index&lt;/li&gt;
&lt;li&gt;Create partial index&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Between the first and second steps, the system is left with no index. Keep in mind that we used concurrent operations, so we had to make the migration non-atomic. This means that all changes take effect immediately, and not at the end of the migration. This order of operations can have two consequences:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;From the time the full index is dropped until the partial index is created, the system doesn't have an index. This can cause queries using the index to become slower.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If the migration fails between the first and second steps, the system will be left without an index until the migration is attempted again.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This solution to this headache is to simply switch the order of operations:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gu"&gt;@@ -15,6 +15,10 @@ class Migration(migrations.Migration):&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;    operations = [
&lt;span class="gi"&gt;+        AddIndexConcurrently(&lt;/span&gt;
&lt;span class="gi"&gt;+            model_name=&amp;#39;product&amp;#39;,&lt;/span&gt;
&lt;span class="gi"&gt;+            index=models.Index(condition=models.Q((&amp;#39;last_edited_by__isnull&amp;#39;, False)), fields=[&amp;#39;last_edited_by&amp;#39;], #...&lt;/span&gt;
&lt;span class="gi"&gt;+        ),&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;        migrations.operations.SeparateDatabaseAndState(
&lt;span class="w"&gt; &lt;/span&gt;            state_operations=[
&lt;span class="w"&gt; &lt;/span&gt;                migrations.AlterField(
&lt;span class="gu"&gt;@@ -30,8 +34,4 @@ class Migration(migrations.Migration):&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;                ),
&lt;span class="w"&gt; &lt;/span&gt;            ],
&lt;span class="w"&gt; &lt;/span&gt;        ),
&lt;span class="gd"&gt;-        AddIndexConcurrently(&lt;/span&gt;
&lt;span class="gd"&gt;-            model_name=&amp;#39;product&amp;#39;,&lt;/span&gt;
&lt;span class="gd"&gt;-            index=models.Index(condition=models.Q((&amp;#39;last_edited_by__isnull&amp;#39;, False)), fields=[&amp;#39;last_edited_by&amp;#39;], #...&lt;/span&gt;
&lt;span class="gd"&gt;-        ),&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;    ]
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;First create the partial index and then drop the full index. This way, the system is never left without an index, and if the migration fails at any point, we still have at least one index.&lt;/p&gt;
&lt;div class="admonition info"&gt;
&lt;p class="admonition-title"&gt;💡 Takeaway&lt;/p&gt;
&lt;p&gt;Adjust the order of migration operations to reduce the impact on a running application during the migration. It is usually better to create first, and drop after.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;This wraps up the migrations part, on to actual business logic!&lt;/p&gt;
&lt;h3 id="locking-across-relations"&gt;&lt;a class="toclink" href="#locking-across-relations"&gt;Locking Across Relations&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Say we want to add a the ability to edit a product with the following requirements:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Let a user update the name and description of a product&lt;/li&gt;
&lt;li&gt;Keep track of the user who last edited the product&lt;/li&gt;
&lt;li&gt;Only superusers can edit products created by other superusers&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A naive implementation can look like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# ...&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;edit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;edited_by&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;created_by&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_superuser&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;edited_by&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_superuser&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# Only superusers can edit products created by other superusers.&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NotAllowed&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;last_edited_by&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;edited_by&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The &lt;code&gt;edit&lt;/code&gt; function is implemented as an instance method on the &lt;code&gt;Product&lt;/code&gt; model. It accepts the name and description to update, and the editing user. It performs the necessary permission check and moves on to set the required fields and save.&lt;/p&gt;
&lt;p&gt;In some cases this is fine, and you can stop here. However, blindly operating on an instance like this is not always ideal:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The copy you have in-memory may be stale&lt;/li&gt;
&lt;li&gt;Not safe against concurrent updates&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you are not convinced that concurrency is a real concern, check out &lt;a href="postgresql-get-or-create"&gt;How to Get or Create in PostgreSQL&lt;/a&gt; and &lt;a href="django-concurrency"&gt;Handling Concurrency Without Locks&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;A solution more resilient to concurrent edits using a &lt;a href="/bullet-proofing-django-models#a-better-approach"&gt;pessimistic approach&lt;/a&gt; can look like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt;

&lt;span class="hll"&gt;&lt;span class="nd"&gt;@classmethod&lt;/span&gt;
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;edit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;cls&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="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;edited_by&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Self&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="hll"&gt;    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;atomic&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;        &lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;
&lt;span class="hll"&gt;            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;select_for_update&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;created_by&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_superuser&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;edited_by&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_superuser&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# Only superusers can edit products created by other superusers.&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NotAllowed&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;
        &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt;
        &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;last_edited_by&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;edited_by&lt;/span&gt;
        &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&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;product&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;These are the main differences:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Using a class method instead of an instance method&lt;/strong&gt;: with an instance method we are operating on an already fetched object. To obtain a lock on the row we need to control how it's fetched from the database, so switching to &lt;code&gt;classmethod&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Lock the row&lt;/strong&gt;: to acquire a lock we must operate inside a database transaction. Then, to actually lock the row, we use &lt;code&gt;select_for_update&lt;/code&gt; which resolves to a &lt;code&gt;SELECT ... FOR UPDATE&lt;/code&gt; SQL clause.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All this locking business can be pretty distracting. So distracting in-fact, that you can end up missing the most obvious optimization in Django, the optimization that appears in every "top 10 Django optimizations" list. I'm taking of course, about the all mighty &lt;code&gt;select_related&lt;/code&gt;!&lt;/p&gt;
&lt;p&gt;To implement the permission check we access the user that created the product:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;created_by&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_superuser&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;edited_by&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_superuser&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Only superusers can edit products created by other superusers.&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NotAllowed&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The user is not fetched in advance, so when we access it, Django will go the database and fetch it. Since we know in advance that we are going to access the user, we can tell Django to fetch it along with the product using &lt;code&gt;select_related&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;select_for_update&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="hll"&gt;    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;select_related&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;created_by&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&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;/pre&gt;&lt;/div&gt;


&lt;p&gt;This will reduce the number of queries by one. However, there is a little gotcha here that can easily go unnoticed.&lt;/p&gt;
&lt;p&gt;Consider the following scenario with two session operating at the same time:&lt;/p&gt;
&lt;style&gt;
.side-by-side {
    display: flex;
}
.side-by-side .highlight {
    flex-grow: 1;
    flex-shrink: 0;
    width: 50%;
}
.side-by-side .highlight pre {
    height: 100%;
}

/* Desktop */
@media only screen and (min-width: 52rem) {
    .side-by-side .highlight:first-child {
        margin-right: 1em;
    }
}
/* Mobile */
@media only screen and (max-width: 52rem) {
    .side-by-side {
        flex-direction: column;
    }
    .side-by-side .highlight {
        width: 100%;
    }
    .side-by-side .highlight:first-child {
        margin-bottom: 0;
        border-bottom: 1px solid rgba(0,0,0,0.2);
    }
}
&lt;/style&gt;

&lt;div class="side-by-side"&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="c1"&gt;# Session 1&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;atomic&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;     &lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;          &lt;span class="n"&gt;Product&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;         &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;select_for_update&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;         &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;select_related&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;created_by&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;         &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;     &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;     &lt;span class="c1"&gt;# transaction is still ongoing...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="c1"&gt;# Session 2&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;97&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_active&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;# Blocked!&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;/div&gt;
&lt;p&gt;In this scenario, session 1 is in the process of editing a product. At the same time, session 2 is attempting to update the user and gets blocked. Why did session 2, which is not updating a product, gets blocked?&lt;/p&gt;
&lt;p&gt;The reason session 2 got blocked is that &lt;code&gt;SELECT ... FOR UPDATE&lt;/code&gt; locks rows from all the tables referenced by the query! In this case, when we added &lt;code&gt;select_related&lt;/code&gt; we added a join to the query, to fetch both the product and the user who created it. As a result, the rows of both the product and the user who created it are locked! If someone happens to try to update a user while we update a product they created, they can get blocked.&lt;/p&gt;
&lt;div class="admonition warning"&gt;
&lt;p class="admonition-title"&gt;⚠️ Implicit Behavior&lt;/p&gt;
&lt;p&gt;By default, &lt;code&gt;select_for_update&lt;/code&gt; locks the rows from all the referenced tables.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;To avoid locking all the rows you can explicitly state which tables to lock using &lt;code&gt;select_for_update&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gu"&gt;@@ -80,6 +80,7 @@ class Product(models.Model):&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;   product: Self = (
&lt;span class="w"&gt; &lt;/span&gt;       cls.objects
&lt;span class="w"&gt; &lt;/span&gt;       .select_related(&amp;#39;created_by&amp;#39;)
&lt;span class="gi"&gt;+       .select_for_update(of=(&amp;#39;self&amp;#39;, ))&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;       .get(id=id)
&lt;span class="w"&gt; &lt;/span&gt;   )
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;code&gt;self&lt;/code&gt; is a special keyword that evaluates to the queryset's model, in this case, product.&lt;/p&gt;
&lt;div class="admonition info"&gt;
&lt;p class="admonition-title"&gt;💡 Takeaway&lt;/p&gt;
&lt;p&gt;Always explicitly state which tables to lock when using &lt;code&gt;select_for_update&lt;/code&gt;. Even if you don't have &lt;code&gt;select_related&lt;/code&gt; right now, you might have in the future.&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id="permissive-no-key-locks"&gt;&lt;a class="toclink" href="#permissive-no-key-locks"&gt;Permissive No Key Locks&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Previously we used &lt;code&gt;select_for_update(of=('self', ))&lt;/code&gt; to avoid locking the user when we only want to lock and update the product. Now let's look at another scenario. This time, we want to insert a new product:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Product&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;     &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;my product&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;     &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;a lovely product&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;     &lt;span class="n"&gt;category_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;     &lt;span class="n"&gt;category_sort_order&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;999997&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;     &lt;span class="n"&gt;created_by_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;     &lt;span class="n"&gt;last_edited_by_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Product&lt;/span&gt; &lt;span class="mi"&gt;1000001&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This works.&lt;/p&gt;
&lt;p&gt;Now let's do it again, but this time, while another session is trying to update user 1:&lt;/p&gt;
&lt;div class="side-by-side"&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="c1"&gt;# Session 1&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;atomic&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;   &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;     &lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="o"&gt;...&lt;/span&gt;     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;select_for_update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;of&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;self&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,))&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="o"&gt;...&lt;/span&gt;     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;   &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;   &lt;span class="c1"&gt;# transaction is still ongoing...&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="c1"&gt;# Session 2&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Product&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;   &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;my product&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;   &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;a lovely product&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;   &lt;span class="n"&gt;category_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;   &lt;span class="n"&gt;category_sort_order&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;999998&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="o"&gt;...&lt;/span&gt;   &lt;span class="n"&gt;created_by_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;   &lt;span class="n"&gt;last_edited_by_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="c1"&gt;# Blocked!&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;/div&gt;
&lt;p&gt;Session 1 locks user 1 for update. At the same time, Session 2 attempts to insert a new product which is created by the same user and it gets blocked! Why did it get blocked?&lt;/p&gt;
&lt;p&gt;Imagine you are the database. You have one session locking the user and another trying to reference it. How can you be sure the first session is not going to change the primary key for this user? How can you be sure it's not going to delete this user? If Session 1 is going to do any of these things, Session 2 should fail. This is why the database has to lock Session 2 until Session 1 completes.&lt;/p&gt;
&lt;p&gt;The two scenarios the database is concerned about are valid, but very rare in most cases. How often do you actually update the primary key of an object or a unique constraint that may be referenced by a foreign key? probably never. In PostgreSQL, there is a way to communicate that to the database, and obtain a more permissive lock when selecting for update, using &lt;code&gt;FOR NO KEY UPDATE&lt;/code&gt;:&lt;/p&gt;
&lt;div class="side-by-side"&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="c1"&gt;# Session 1&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;atomic&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;   &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;     &lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="o"&gt;...&lt;/span&gt;     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;select_for_update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;no_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;self&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,))&lt;/span&gt;
&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;     &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;   &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;   &lt;span class="c1"&gt;# transaction is still ongoing...&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="c1"&gt;# Session 2&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Product&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;   &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;my product&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;   &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;a lovely product&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;   &lt;span class="n"&gt;category_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;   &lt;span class="n"&gt;category_sort_order&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;999998&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="o"&gt;...&lt;/span&gt;   &lt;span class="n"&gt;created_by_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;   &lt;span class="n"&gt;last_edited_by_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Product&lt;/span&gt; &lt;span class="mi"&gt;1000002&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;/div&gt;
&lt;p&gt;By setting &lt;code&gt;no_key=True&lt;/code&gt;, we tell the database we are not going to update the primary key of the user. The database can then acquire a more permissive lock, and the second session can now safely create the product.&lt;/p&gt;
&lt;div class="admonition info"&gt;
&lt;p class="admonition-title"&gt;💡 Takeaway&lt;/p&gt;
&lt;p&gt;Use &lt;code&gt;select_for_update(no_key=True)&lt;/code&gt; to select a row for update when not updating primary keys or unique constraints that are referenced by foreign keys. This will require a more permissive lock and prevent unnecessary locks when operating on referencing objects.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Going back to our &lt;code&gt;Product&lt;/code&gt; model, to edit a product we used &lt;code&gt;select_for_update&lt;/code&gt; to prevent concurrent updates from updating the product at the same time. By locking the row, we also accidentally prevent other referencing models from creating objects while we have the lock. In the case of model like &lt;code&gt;Product&lt;/code&gt;, this can have a huge impact on the system.&lt;/p&gt;
&lt;p&gt;Imagine you run an e-commerce website using this catalog. When a user makes a purchase you create an &lt;code&gt;Order&lt;/code&gt; instance that references the product. Now imagine that every time someone updates a product, your system can't create orders. This is unacceptable!&lt;/p&gt;
&lt;p&gt;To allow the system to create orders while a product is being updated, add &lt;code&gt;no_key=True&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gd"&gt;--- a/demo/catalog/models.py&lt;/span&gt;
&lt;span class="gi"&gt;+++ b/demo/catalog/models.py&lt;/span&gt;
&lt;span class="gu"&gt;@@ -80,7 +80,7 @@ class Product(models.Model):&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;            product: Self = (
&lt;span class="w"&gt; &lt;/span&gt;                cls.objects
&lt;span class="w"&gt; &lt;/span&gt;                .select_related(&amp;#39;created_by&amp;#39;)
&lt;span class="gd"&gt;-                .select_for_update(of=(&amp;#39;self&amp;#39;, ))&lt;/span&gt;
&lt;span class="gi"&gt;+                .select_for_update(of=(&amp;#39;self&amp;#39;, ), no_key=True)&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;                .get(id=id)
&lt;span class="w"&gt; &lt;/span&gt;            )
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Now we can safely edit a product without interfering with a live system.&lt;/p&gt;
&lt;h3 id="the-final-model"&gt;&lt;a class="toclink" href="#the-final-model"&gt;The Final Model&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;It's been quite a ride from the naive model we started with, but here is the final model:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&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;constraints&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UniqueConstraint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;product_category_sort_order_uk&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="s1"&gt;&amp;#39;category&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="s1"&gt;&amp;#39;category_sort_order&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;indexes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;product_last_edited_by_part_ix&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;last_edited_by&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,),&lt;/span&gt;
                &lt;span class="n"&gt;condition&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Q&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;last_edited_by__isnull&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BigAutoField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;primary_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&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;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TextField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;category&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;on_delete&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PROTECT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;related_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;products&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="c1"&gt;# Indexed in unique constraint.&lt;/span&gt;
        &lt;span class="n"&gt;db_index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;category_sort_order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IntegerField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;created_by&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;on_delete&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PROTECT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;related_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;+&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="c1"&gt;# Used to speed up user deletion.&lt;/span&gt;
        &lt;span class="n"&gt;db_index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;last_edited_by&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;on_delete&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PROTECT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;related_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;+&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;null&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="c1"&gt;# Indexed in Meta.&lt;/span&gt;
        &lt;span class="n"&gt;db_index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nd"&gt;@classmethod&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;edit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="bp"&gt;cls&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="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;edited_by&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;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;with&lt;/span&gt; &lt;span class="n"&gt;db_transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;atomic&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Self&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;select_related&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;created_by&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;select_for_update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;of&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;self&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;no_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;created_by&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_superuser&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;edited_by&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_superuser&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="c1"&gt;# Only superusers can edit products created by other superusers.&lt;/span&gt;
                &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NotAllowed&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

            &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;
            &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt;
            &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;last_edited_by&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;edited_by&lt;/span&gt;
            &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&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;product&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This model and the accompanying migrations are safe, resilient, and most importantly, production ready!&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="takeaways"&gt;&lt;a class="toclink" href="#takeaways"&gt;Takeaways&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Here is a recap of the takeaways from this article:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Don't use &lt;code&gt;unique_together&lt;/code&gt;, use &lt;code&gt;UniqueConstraint&lt;/code&gt; instead&lt;/li&gt;
&lt;li&gt;Always explicitly set &lt;code&gt;db_index&lt;/code&gt; &lt;em&gt;and&lt;/em&gt; add a comment&lt;/li&gt;
&lt;li&gt;Always check the SQL generated by migrations before applying them&lt;/li&gt;
&lt;li&gt;Provide reverse migration operations whenever possible&lt;/li&gt;
&lt;li&gt;Use concurrent index operations in busy systems&lt;/li&gt;
&lt;li&gt;Indexes on foreign keys are used indirectly by deletes&lt;/li&gt;
&lt;li&gt;Use partial indexes when possible&lt;/li&gt;
&lt;li&gt;Adjust the order of migration operations to reduce impact on live systems&lt;/li&gt;
&lt;li&gt;Explicitly state tables to lock when using &lt;code&gt;select_for_update&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;no_key=True&lt;/code&gt; when selecting a row for update to allow referencing objects to be created&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="the-mandatory-ai-angle"&gt;&lt;a class="toclink" href="#the-mandatory-ai-angle"&gt;The Mandatory AI Angle&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Since it's 2025 you can't really have a serious article about technology that doesn't mention AI. So here is a funny story about AI and this exact article.&lt;/p&gt;
&lt;p&gt;When I first presented this article in a talk at DjangoCon EU, I showed the final slide with all the takeaways and made a joke along the lines of "show me the LLM that can do that". Most people seemed to appreciate this little joke.&lt;/p&gt;
&lt;p&gt;After the talk, a friend came to me and said "come man, let me show you something". He pulled his laptop and opened Cursor. He took my takeaways from the slide and put them in a rules file. He then had Cursor refactor one of his &lt;code&gt;models.py&lt;/code&gt; files with my takeaways as guidelines... I can only say I had mixed feelings about the result.&lt;/p&gt;</content><category term="articles"></category><category term="Django"></category><category term="ORM"></category></entry><entry><title>Handling Concurrency Without Locks</title><link href="https://hakibenita.com/django-concurrency" rel="alternate"></link><published>2022-06-09T00:00:00+03:00</published><updated>2022-06-09T00:00:00+03:00</updated><author><name>Haki Benita</name></author><id>tag:hakibenita.com,2022-06-09:/django-concurrency</id><summary type="html">&lt;p&gt;Concurrency is not very intuitive - you need to train your brain to consider what happens when multiple processes execute a certain code block at the same time. In this article I present common concurrency challenges and how to overcome them with minimal locking.&lt;/p&gt;</summary><content type="html">&lt;hr&gt;
&lt;p&gt;Concurrency is not very intuitive. You need to train your brain to consider what happens when multiple processes execute a certain code block at the same time. There are several issues I often encounter:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Failing to recognize potential concurrency issues&lt;/strong&gt;: It's not uncommon for both beginner and seasoned developers to completely miss a potential concurrency problem. When this happens, and the concurrency issue end up causing bugs, it's usually very hard to trace and debug.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Dismiss concurrency issues due to low likelihood&lt;/strong&gt;: If you recognized a potential concurrency issue, at some point you probably thought to yourself "what are the chances if this happening...". It's very tempting to dismiss concurrency issues when the likelihood is low. However, I personally found that concurrency issues tend to creep up at the worst time - when your system is under significant load and you have very little time (and grace) to come up with a solution.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Abusing locks&lt;/strong&gt;: If you recognized a potential issue and decided to handle it properly, your next step will usually involve some kind of lock. Sometimes locks are necessary, but more often than not they can be avoided, or replaced by more permissive locks.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;In this article I present common concurrency challenges and how to overcome them with minimal locking.&lt;/strong&gt;&lt;/p&gt;
&lt;div class="dark--invert"&gt;
&lt;figure&gt;&lt;img alt="Other type of race..." src="https://hakibenita.com/images/00-django-concurrency.svg"&gt;&lt;figcaption&gt;Other type of race...&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/div&gt;
&lt;p&gt;&lt;details class="toc-container" open&gt;
    &lt;summary&gt;Table of Contents&lt;/summary&gt;&lt;/p&gt;
&lt;div class="toc"&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#a-simple-web-application"&gt;A Simple Web Application&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#naive-implementation"&gt;Naive Implementation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#handling-possible-collisions"&gt;Handling Possible Collisions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#time-of-check-to-time-of-use"&gt;Time-of-check to Time-of-use&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#locking"&gt;Locking&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#lock-in-the-database"&gt;Lock in the Database&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#asking-for-forgiveness"&gt;Asking for Forgiveness&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#asking-for-forgiveness-in-postgresql"&gt;Asking for Forgiveness in PostgreSQL&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#identifying-race-conditions"&gt;Identifying Race Conditions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#select-for-update"&gt;Select for Update&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#increment-in-the-database"&gt;Increment in the Database&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#update-and-immediately-return"&gt;Update and Immediately Return&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#take-away"&gt;Take Away&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;p&gt;&lt;/details&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="a-simple-web-application"&gt;&lt;a class="toclink" href="#a-simple-web-application"&gt;A Simple Web Application&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A URL shortener provides short URLs that redirects to other URLs. There are several reasons why you would want that:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Include links in space-constrained places&lt;/strong&gt;: Links in SMS messages, Tweets etc.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Track the number of clicks&lt;/strong&gt;: Ads, campaigns, newsletter links, promotional emails etc.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;To most developers a URL shortener sounds like a straight forward project. This is why it makes a great example to demonstrate common concurrency issues, and how easy they are to miss and get wrong. I'm using Python, Django and PostgreSQL, but the concepts apply to any programming language and RDBMs.&lt;/p&gt;
&lt;p&gt;&lt;details markdown="1"&gt;&lt;/p&gt;
&lt;p&gt;&lt;summary&gt;⚙️ Django Project setup&lt;/summary&gt;&lt;/p&gt;
&lt;p&gt;To build your URL shortener start by creating a new Django project:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;python&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;venv&lt;span class="w"&gt; &lt;/span&gt;venv
$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;source&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;venv/bin/activate
$&lt;span class="w"&gt; &lt;/span&gt;pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;django
$&lt;span class="w"&gt; &lt;/span&gt;django-admin&lt;span class="w"&gt; &lt;/span&gt;startproject&lt;span class="w"&gt; &lt;/span&gt;project
$&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;project
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This will create a Python virtual environment, install the latest Django version and create a Django project named &lt;code&gt;project&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;For the URL shortener, add a new app called &lt;code&gt;shorturl&lt;/code&gt; in the project:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;python&lt;span class="w"&gt; &lt;/span&gt;manage.py&lt;span class="w"&gt; &lt;/span&gt;startapp&lt;span class="w"&gt; &lt;/span&gt;shorturl
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Next, register the new app in &lt;code&gt;settings.py&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# settings.py&lt;/span&gt;
&lt;span class="n"&gt;INSTALLED_APPS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="c1"&gt;# ...&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;shorturl.apps.ShortUrlConfig&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Django uses SQLlite by default. If you want to configure Django to use PostgreSQL instead, install psycopg and create a database:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;psycopg2
$&lt;span class="w"&gt; &lt;/span&gt;createdb&lt;span class="w"&gt; &lt;/span&gt;-O&lt;span class="w"&gt; &lt;/span&gt;yourdbuser&lt;span class="w"&gt; &lt;/span&gt;shorturl
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Then edit the following parameters in &lt;code&gt;settings.py&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# settings.py&lt;/span&gt;
&lt;span class="n"&gt;DATABASES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;default&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;ENGINE&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;django.db.backends.postgresql&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;NAME&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;shorturl&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;USER&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;yourdbuser&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Finally, run the initial migrations:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;python&lt;span class="w"&gt; &lt;/span&gt;manage.py&lt;span class="w"&gt; &lt;/span&gt;migrate
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;You are now ready for the interesting part!&lt;/p&gt;
&lt;p&gt;&lt;/details&gt;&lt;/p&gt;
&lt;h2 id="naive-implementation"&gt;&lt;a class="toclink" href="#naive-implementation"&gt;Naive Implementation&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A short URL is composed of a short unique identifier that points to some target URL, and a counter to keep track of the number of hits. A Django model for a short URL can look like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ShortUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&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;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&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;20&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="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;target_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;URLField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;hits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PositiveIntegerField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;A simple function to create a new short URL can look like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;secrets&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;string&lt;/span&gt;

&lt;span class="n"&gt;CHARACTERS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ascii_letters&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;digits&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ShortUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="c1"&gt;# ...&lt;/span&gt;

    &lt;span class="nd"&gt;@classmethod&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ShortUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;secrets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CHARACTERS&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;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;target_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hits&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The function accepts a target URL, generates a random key from a list of possible characters and saves a new &lt;code&gt;ShortUrl&lt;/code&gt; to the database. You can now use this function to create a new short URL:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;shorturl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ShortURL&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;https://hakibenita.com&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="nb"&gt;vars&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shorturl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;{&amp;#39;_state&amp;#39;: &amp;lt;django.db.models.base.ModelState at 0x7fd5e05558a0&amp;gt;,&lt;/span&gt;
&lt;span class="go"&gt; &amp;#39;id&amp;#39;: 1,&lt;/span&gt;
&lt;span class="go"&gt; &amp;#39;created_at&amp;#39;: datetime.datetime(2022, 4, 29, 8, 2, 18, 615165, tzinfo=datetime.timezone.utc),&lt;/span&gt;
&lt;span class="go"&gt; &amp;#39;key&amp;#39;: &amp;#39;c6UFG&amp;#39;,&lt;/span&gt;
&lt;span class="go"&gt; &amp;#39;target_url&amp;#39;: &amp;#39;https://hakibenita.com&amp;#39;,&lt;/span&gt;
&lt;span class="go"&gt; &amp;#39;hits&amp;#39;: 0}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This seems innocent enough, &lt;strong&gt;so what can possibly go wrong?&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="handling-possible-collisions"&gt;&lt;a class="toclink" href="#handling-possible-collisions"&gt;Handling Possible Collisions&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Say your URL shortener becomes a wild success and you have millions of new short URLs created every day. At some point, the function that generates the random key may produce a key that already exist:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;shorturl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ShortUrl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;https://hakibenita.com/tag/django&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;IntegrityError: duplicate key value violates unique constraint &amp;quot;shorturl_shorturl_key_uk&amp;quot;&lt;/span&gt;
&lt;span class="go"&gt;DETAIL:  Key (key)=(c6UFG) already exists.&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Notice that the function generated the random key &lt;code&gt;c6UFG&lt;/code&gt; which is similar to a short URL you previously created. The &lt;code&gt;key&lt;/code&gt; column has a unique constraint defined on it, so you got a database error.&lt;/p&gt;
&lt;p&gt;In order to avoid a unique constraint violation, you might try to check the key in advance, like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ShortUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="nd"&gt;@classmethod&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ShortUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="hll"&gt;        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;            &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;secrets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CHARACTERS&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;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;span class="hll"&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;                &lt;span class="k"&gt;break&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;target_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hits&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Instead of creating the short URL immediately, you first check that the random key you generated does not already exist. If you find that is does, you keep iterating on random keys until you find one that doesn't.&lt;/p&gt;
&lt;p&gt;Aside from the fact that this function can now potentially go into an infinite loop, there is another problem!&lt;/p&gt;
&lt;h2 id="time-of-check-to-time-of-use"&gt;&lt;a class="toclink" href="#time-of-check-to-time-of-use"&gt;Time-of-check to Time-of-use&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The purpose of checking that the key does not exist in advance was to avoid a database error, but is it really impossible to end up with a unique constraint violation this way? Consider the following scenario:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Process 1&lt;/th&gt;
&lt;th&gt;Process 2&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Check key &lt;code&gt;c6UFG&lt;/code&gt; -&amp;gt; ✅&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Check key &lt;code&gt;c6UFG&lt;/code&gt; -&amp;gt; ✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Use Key &lt;code&gt;c6UFG&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Use key &lt;code&gt;c6UFG&lt;/code&gt; -&amp;gt; 💥 Already exists&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Let's break it down:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Process #1 generates the random key &lt;code&gt;c6UFG&lt;/code&gt; and checks that it does not already exist&lt;/li&gt;
&lt;li&gt;Before Process #1 has a chance to write the new shorturl to the database, process #2 generates the same key &lt;code&gt;c6UFG&lt;/code&gt; and checks that it does not already exist. At this point, it doesn't!&lt;/li&gt;
&lt;li&gt;Process #2 writes the short URL to the database and completes successfully&lt;/li&gt;
&lt;li&gt;Process #1 now tries to save a short URL with the same key &lt;code&gt;c6UFG&lt;/code&gt;, which it checked in advance, and fails with a unique constraint violation&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This is a very common concurrency issue commonly referred to as "time-of-check to time-of-use", or "TOCTOU". The name describes the issue pretty well - the problem is caused when another process changes the data between the time a process checked a value until the time it used it. In this case, Process #2 added a short URL with the same key after Process #1 had checked it, but before it used it.&lt;/p&gt;
&lt;h2 id="locking"&gt;&lt;a class="toclink" href="#locking"&gt;Locking&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Whenever you have a problem with multiple processes accessing the same resource at the same time, the most intuitive solutions is a lock. But, what exactly should you lock?&lt;/p&gt;
&lt;p&gt;A simple architecture for a web application usually contains a web application process and a database:&lt;/p&gt;
&lt;figure&gt;&lt;img alt="Single process" src="https://hakibenita.com/images/01-python-concurrency-setup.svg"&gt;&lt;figcaption&gt;Single process&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;If that's your setup, you might be able to obtain a lock at the process level and make sure you are the only one accessing a certain resource at a time.&lt;/p&gt;
&lt;p&gt;However, a common setup for Django (as well as other web applications) is to run with multiple worker processes:&lt;/p&gt;
&lt;figure&gt;&lt;img alt="Multiple worker processes" src="https://hakibenita.com/images/02-python-concurrency-setup.svg"&gt;&lt;figcaption&gt;Multiple worker processes&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;With multiple worker processes on the same server it's no longer enough to lock a resource within a single process. However, since the two processes are running on the same server, you might be tempted to try and find a solution at the operating system level. But, is it enough?&lt;/p&gt;
&lt;figure&gt;&lt;img alt="Multiple servers" src="https://hakibenita.com/images/03-python-concurrency-setup.svg"&gt;&lt;figcaption&gt;Multiple servers&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;If your system is running on multiple servers, with multiple worker processes on each one, even the OS can't save you. You now might think the lowest common denominator is the application itself. You can spend days trying to come up with an original way to coordinate a lock between the servers, but would that solve your problem?&lt;/p&gt;
&lt;figure&gt;&lt;img alt="Multiple applications" src="https://hakibenita.com/images/04-python-concurrency-setup.svg"&gt;&lt;figcaption&gt;Multiple applications&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Modern systems can run multiple applications on top of the same database. We for example, &lt;a href="/5-ways-to-make-django-admin-safer#separate-the-django-admin-from-the-main-site"&gt;do it with Django admin&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;At this point it becomes clearer that the lowest common denominator, the resource that all servers, processes and applications share, is the database. If you want to "lock" a resource, you better do it in the database.&lt;/p&gt;
&lt;h2 id="lock-in-the-database"&gt;&lt;a class="toclink" href="#lock-in-the-database"&gt;Lock in the Database&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Now that you know where to lock, there is another challenge. If you were updating an existing row in the database you could have locked that specific row, but this is not the case.  You want to create a new row, for a new short URL, so what can you possibly lock?&lt;/p&gt;
&lt;p&gt;One option is to lock the entire table:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ShortUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="nd"&gt;@classmethod&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ShortUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="hll"&gt;        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;atomic&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;            &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;LOCK TABLE shorturl_shorturl IN EXCLUSIVE MODE;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;            &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;secrets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CHARACTERS&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;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
                    &lt;span class="k"&gt;break&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;target_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hits&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;To prevent other processes from creating short URLs and potentially causing a unique constraint violation, you locked the entire shorturl table.&lt;/p&gt;
&lt;p&gt;First, to obtain a lock in the database you need to operate inside a database transaction:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;atomic&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;In most cases, &lt;a href="https://docs.djangoproject.com/en/4.0/topics/db/transactions/#django-s-default-transaction-behavior" rel="noopener"&gt;Django operates in "autocommit" mode&lt;/a&gt;, meaning it implicitly opens a transaction for every command, and commits immediately after. In this case you want to execute multiple commands in the same database transaction, so you explicitly control the transaction using the &lt;a href="https://docs.djangoproject.com/en/4.0/topics/db/transactions/#django.db.transaction.atomic" rel="noopener"&gt;&lt;code&gt;transaction.atomic&lt;/code&gt; context&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The Django ORM does not provide functions for locking tables. To lock a table in the database you need to use a cursor and &lt;a href="https://docs.djangoproject.com/en/4.0/topics/db/sql/#executing-custom-sql-directly" rel="noopener"&gt;execute raw SQL&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;atomic&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;LOCK TABLE shorturl_shorturl IN EXCLUSIVE MODE;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;After you obtained an exclusive lock on the table, no other transaction can obtain the same lock until you release it. This guarantees that the data cannot change between the time you checked the values and the time you used them.&lt;/p&gt;
&lt;p&gt;So, problem solved?&lt;/p&gt;
&lt;h2 id="asking-for-forgiveness"&gt;&lt;a class="toclink" href="#asking-for-forgiveness"&gt;Asking for Forgiveness&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Imagine your app makes it to the top page of a very popular news site. In a matter of minutes you start getting thousands of hits, and users are creating thousands of new short URLs. Now imagine your system can only create a single short URL from a single user at a time, and all other users need to wait in line. Is that acceptable?&lt;/p&gt;
&lt;p&gt;When you locked the entire table you made sure no other transaction can make changes to the table until you are done. This means adding new short URLs is now safe, but it can also get pretty slow when you have many concurrent requests.&lt;/p&gt;
&lt;p&gt;What if you could ditch the lock? What if you could make your function safe without preventing multiple users from creating short URLs at the same time? Have another look at the exception you got in the beginning:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;shorturl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ShortUrl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;https://hakibenita.com/tag/django&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="go"&gt;IntegrityError: duplicate key value violates unique constraint &amp;quot;shorturl_shorturl_key_uk&amp;quot;&lt;/span&gt;
&lt;/span&gt;&lt;span class="go"&gt;DETAIL:  Key (key)=(c6UFG) already exists.&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;When the function attempted to create a short URL with a key that already existed, the database returned an error. This is because the &lt;code&gt;key&lt;/code&gt; column has a unique constraint defined on it. So, if the database is already making sure that there are no duplicates, why not rely on it?&lt;/p&gt;
&lt;p&gt;With this in mind, you can now change your function to handle the unique constraint violation:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;IntegrityError&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ShortUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="nd"&gt;@classmethod&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ShortUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;secrets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CHARACTERS&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;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ShortUrl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;target_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hits&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="hll"&gt;            &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;IntegrityError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;                &lt;span class="c1"&gt;# Key exists, try try again!&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;                &lt;span class="k"&gt;continue&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The function now generates a random key and attempts to create a new short URL. If the command fails with an &lt;code&gt;IntegrityError&lt;/code&gt; it means a short URL with the same &lt;code&gt;key&lt;/code&gt; already exists. In this case, the function will generate another random &lt;code&gt;key&lt;/code&gt; and try again until it succeeds.&lt;/p&gt;
&lt;p&gt;The first thing you might notice is that there is no explicit lock, and there is no explicit database transaction. Multiple processes can now safely create short URLs without getting an &lt;code&gt;IntegrityError&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;According to this quote from the &lt;a href="https://docs.python.org/3.11/glossary.html#term-EAFP" rel="noopener"&gt;Python glossary&lt;/a&gt;, this approach is also much more "pythonic":&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;EAFP&lt;/strong&gt;&lt;br&gt;
Common Python coding style [ ... ] This clean and fast style is characterized by the presence of many try and except statements.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The term EAFP stands for "Easier to ask for forgiveness than permission", and this is exactly what you did. Instead of checking in advance, you tried to write the row to the database and handled the exception. The opposite of EAFP is LBYL, "Look Before You Leap", which is what you did when you checked the values in advance.&lt;/p&gt;
&lt;p&gt;Python as a language encourages "asking or forgiveness" (EAFP), as opposed to other languages such as JavaScript or Java that encourage you to check values in advance, the LBYL style.&lt;/p&gt;
&lt;h2 id="asking-for-forgiveness-in-postgresql"&gt;&lt;a class="toclink" href="#asking-for-forgiveness-in-postgresql"&gt;Asking for Forgiveness in PostgreSQL&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Sometimes it's necessary to generate short URLs as part of another transaction. For example, if you include the short URL in a notification you save to the database, your code can look like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;atomic&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;     &lt;span class="n"&gt;shorturl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ShortUrl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;https://hakibenita.com&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;     &lt;span class="n"&gt;notification&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Notification&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;...&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;...&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;To make sure both the notification and the short URL are created together, you execute the code inside a database transaction. This is a perfectly valid use of database transactions - this is exactly what they are for!&lt;/p&gt;
&lt;p&gt;If you use PostgreSQL, under some circumstances you might encounter this error:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;InternalError: current transaction is aborted, commands ignored until end of transaction block
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;In PostgreSQL, when there is an error during a transaction, all other commands are aborted until the end of the transaction. Now recall how &lt;code&gt;ShortUrl.create&lt;/code&gt; is implemented - you iterate over random keys and attempt to create until you &lt;em&gt;don't&lt;/em&gt; get an &lt;code&gt;IntegrityError&lt;/code&gt;. This means that if you execute your function inside a transaction and it did trigger an &lt;code&gt;IntegrityError&lt;/code&gt;, PostgreSQL will abort the transaction, and you won't be able to proceed with the rest of the code.&lt;/p&gt;
&lt;p&gt;To accommodate &lt;a href="https://docs.djangoproject.com/en/4.0/topics/db/transactions/#handling-exceptions-within-postgresql-transactions" rel="noopener"&gt;possible exceptions within transaction in PostgreSQL&lt;/a&gt;, you can use &lt;em&gt;another&lt;/em&gt; transaction:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IntegrityError&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ShortUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="nd"&gt;@classmethod&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ShortUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;secrets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CHARACTERS&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;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="c1"&gt;# In PostgreSQL, when an SQL command fails (usually due to&lt;/span&gt;
                &lt;span class="c1"&gt;# IntegrityError) it is not possible to execute other commands&lt;/span&gt;
                &lt;span class="c1"&gt;# until the end of the atomic block. To be able to retry different&lt;/span&gt;
                &lt;span class="c1"&gt;# keys multiple times after, we execute the command in its own&lt;/span&gt;
                &lt;span class="c1"&gt;# atomic block.&lt;/span&gt;
                &lt;span class="c1"&gt;# https://docs.djangoproject.com/en/4.0/topics/db/transactions/#handling-exceptions-within-postgresql-transactions&lt;/span&gt;
&lt;span class="hll"&gt;                &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;atomic&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;                    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ShortUrl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;target_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hits&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;IntegrityError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="c1"&gt;# Key exists, try try again!&lt;/span&gt;
                &lt;span class="k"&gt;continue&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The additional transaction introduces very little overhead, it simply restricts the effect a possible exception can have on any outer transactions.&lt;/p&gt;
&lt;h2 id="identifying-race-conditions"&gt;&lt;a class="toclink" href="#identifying-race-conditions"&gt;Identifying Race Conditions&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Now that you have all of these short URLs in your system, it's time to actually use them. The URL shortener system includes a view that redirects a short URL to its target URL, and increments the counter. The view can look like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# views.py&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.http&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;HttpRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HttpResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HttpResponseRedirect&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Http404&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.views.decorators.http&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;require_http_methods&lt;/span&gt;

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

&lt;span class="nd"&gt;@require_http_methods&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;GET&amp;quot;&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;resolve_short_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;HttpRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;HttpResponse&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="hll"&gt;        &lt;span class="n"&gt;shorturl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ShortUrl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;ShortUrl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DoesNotExist&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;Http404&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="hll"&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;HttpResponseRedirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shorturl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;target_url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The view attempts to "resolve" a key to a &lt;code&gt;ShortURL&lt;/code&gt; instance, and if it finds one, redirects to it.&lt;/p&gt;
&lt;p&gt;A naive implementation of the function &lt;code&gt;ShortUrl.resolve&lt;/code&gt; can look like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ShortUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="nd"&gt;@classmethod&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ShortUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;shorturl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;shorturl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hits&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="n"&gt;shorturl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;update_fields&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;hits&amp;#39;&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;shorturl&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The function accepts the key as argument and attempts to find a short URL with that key. If the key is not found, &lt;code&gt;.get(key=key)&lt;/code&gt; throws a &lt;code&gt;ShortUrl.DoesNotExist&lt;/code&gt; exception and the view will return a 404 response. If a short URL is found, the hit counter is incremented and saved, the object is returned to the view and the user is redirected to the target URL.&lt;/p&gt;
&lt;p&gt;So, where is the problem?&lt;/p&gt;
&lt;p&gt;As you already experienced when you created the short URL, concurrency issues often require special attention. Imagine what will happen if multiple users are trying to resolve the same key at the same time. Consider the following scenario:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Process 1&lt;/th&gt;
&lt;th&gt;Process 2&lt;/th&gt;
&lt;th&gt;Hits&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Select hits -&amp;gt; 0&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Select hits -&amp;gt; 0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Update hits -&amp;gt; 1&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;💥 Update hits -&amp;gt; 1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;In this scenario, two processes are resolving the same URL at the same time. The short URL is resolved twice, but the hits counter is 1. This is incorrect!&lt;/p&gt;
&lt;h2 id="select-for-update"&gt;&lt;a class="toclink" href="#select-for-update"&gt;Select for Update&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The problem with the naive implementation is that when multiple concurrent processes resolve the same short URL at the same time, the counter can get out of sync. Each of the processes is incrementing the counter based on the value it received when it fetched the row from the database, and that's how the counter gets out of sync.&lt;/p&gt;
&lt;p&gt;What if you could lock the row to prevent multiple processes from selecting and updating it at the same time? Consider the following implementation:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ShortUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="nd"&gt;@classmethod&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ShortUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="hll"&gt;        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;atomic&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;            &lt;span class="n"&gt;shorturl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;
&lt;span class="hll"&gt;                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;select_for_update&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;shorturl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;shorturl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hits&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
            &lt;span class="n"&gt;shorturl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;update_fields&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;hits&amp;#39;&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;shorturl&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The function now opens a database transaction, and uses &lt;a href="https://docs.djangoproject.com/en/4.0/ref/models/querysets/#select-for-update" rel="noopener"&gt;&lt;code&gt;select_for_update&lt;/code&gt;&lt;/a&gt; to lock the row. If the lock is obtained, other processes cannot obtain the same lock on the row until the transaction is finished. This means the counter can no longer get out of sync, because only a single process can fetch and update it at the same time. But it also means that any concurrent processes must either wait or fail.&lt;/p&gt;
&lt;p&gt;Imagine you launch a big campaign to hundreds of thousands of users. To check how effective your campaign is, you use your URL shortener to keep track of how many users clicked the links. Immediately when you send out the campaign it lands in your users' emails and thousands of them click the links. Now imagine each user needs to wait in line until the previous one fetched the row and updated the hit counter in the database. Sounds like it might be a problem...&lt;/p&gt;
&lt;h2 id="increment-in-the-database"&gt;&lt;a class="toclink" href="#increment-in-the-database"&gt;Increment in the Database&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Using &lt;code&gt;select_for_update&lt;/code&gt; you solved the problem of the hit counter going out of sync, but your system is now doing a very poor job at the one thing it should be doing very well - redirect short URLs!&lt;/p&gt;
&lt;p&gt;The main issue with the previous approach is that you incremented the counter based on what you fetched. With many concurrent processes operating at the same time, it is very much possible that since you fetched the row, the counter was incremented multiple times by other processes and you just don't know about it.&lt;/p&gt;
&lt;p&gt;What if instead of incrementing the counter based on what you have stored in memory, you instruct the database to update based on what it currently has stored?&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;F&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ShortUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="nd"&gt;@classmethod&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ShortUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;shorturl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="hll"&gt;        &lt;span class="n"&gt;shorturl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hits&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;F&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;hits&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/span&gt;        &lt;span class="n"&gt;shorturl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;update_fields&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;hits&amp;#39;&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;shorturl&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The function now uses an &lt;a href="https://docs.djangoproject.com/en/4.0/ref/models/expressions/#f-expressions" rel="noopener"&gt;F expression&lt;/a&gt; to update the counter relative to what is in the database.&lt;/p&gt;
&lt;p&gt;The difference seems very mild, so the best way to understand it is to look at the SQL generated by the update commands. The naive approach will execute the following command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;shorturl_shorturl&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="k"&gt;SET&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;hits&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;154&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This will update hits to 1, regardless of the current value of hits in the database.&lt;/p&gt;
&lt;p&gt;The function using the F expression will execute the following command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;shorturl_shorturl&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="k"&gt;SET&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;hits&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;hits&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;154&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The hit counter is now incremented by one and not set to a fixed value. This is how using an F expression solves the problem without obtaining an explicit lock on the row.&lt;/p&gt;
&lt;h2 id="update-and-immediately-return"&gt;&lt;a class="toclink" href="#update-and-immediately-return"&gt;Update and Immediately Return&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Using F expressions solved the problem without an exclusive lock, but there are still two minor downsides to this approach:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;There are two round trips to the database&lt;/strong&gt;: the function first access the database to fetch the short URL by key, and then access it again to update the row.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The hit counter on the returned instance is not updated&lt;/strong&gt;: the function is incrementing the value in the database, but the short URL object it returns stores the hits counter prior to when the hits counter was incremented.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Normally, the F expression solution with two round trips to the database is good enough, no reason to go through the trouble of trying to optimize it. However in this case, the system should be able to accommodate sudden bursts and redirect very quickly, so it might be worth the trouble.&lt;/p&gt;
&lt;p&gt;To solve both issues, you can extend the solution beyond Django's ORM built-in capabilities with some SQL magic:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ShortUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="nd"&gt;@classmethod&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ShortUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;short_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;span class="s1"&gt;            UPDATE shorturl_shorturl&lt;/span&gt;
&lt;span class="s1"&gt;            SET hits = hits + 1&lt;/span&gt;
&lt;span class="s1"&gt;            WHERE key = &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;
&lt;span class="s1"&gt;            RETURNING *&lt;/span&gt;
&lt;span class="s1"&gt;        &amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;short_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DoesNotExist&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;short_url&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Let's break it down:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Use &lt;code&gt;RETURNING&lt;/code&gt; to get updated results&lt;/strong&gt;: In PostgreSQL and SQLite you can &lt;a href="sql-tricks-application-dba#implement-complete-processes-using-with-and-returning"&gt;return the rows affected by an UPDATE statement&lt;/a&gt;. The rows returned by the &lt;code&gt;RETURNING&lt;/code&gt; clause are the updated rows. Even though you only update the column &lt;code&gt;hits&lt;/code&gt;, you can use &lt;code&gt;*&lt;/code&gt; to return the entire row with the updated values.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Use &lt;code&gt;.raw&lt;/code&gt; to construct a &lt;code&gt;ShortUrl&lt;/code&gt; instance&lt;/strong&gt;: Django ORM allows you to &lt;a href="https://docs.djangoproject.com/en/4.0/topics/db/sql/#django.db.models.Manager.raw" rel="noopener"&gt;construct ORM objects from raw SQL&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;If no rows were affected, raise &lt;code&gt;DoesNotExist&lt;/code&gt;&lt;/strong&gt;: if no rows were affected it means there is no short URL for the provided key. To mimic Django's behavior in this case, raise a &lt;code&gt;DoesNotExist&lt;/code&gt; exception, otherwise return the object.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Binding the table name&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Under some circumstances you might want to avoid explicitly using the name of the table. Mainly if you have a reuseable model and you cannot be sure what the name of the database table is.&lt;/p&gt;
&lt;p&gt;Using string concatenation to set the name of the table is not acceptable as it puts the query at risk of SQL Injection. Using psycopg, the Python driver for PostgreSQL, there is a way to safely bind identifiers such as table names:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;psycopg2.sql&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SQL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Identifier&lt;/span&gt;

&lt;span class="nd"&gt;@classmethod&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ShortUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;short_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="hll"&gt;        &lt;span class="n"&gt;raw_query&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;SQL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="s2"&gt;            UPDATE &lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;
&lt;/span&gt;&lt;span class="s2"&gt;            SET hits = hits + 1&lt;/span&gt;
&lt;span class="s2"&gt;            WHERE key = &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;
&lt;span class="s2"&gt;            RETURNING *&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="s2"&gt;        &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Identifier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_meta&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db_table&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
&lt;/span&gt;        &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;short_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DoesNotExist&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;short_url&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The function now uses the name of the table as parameter.&lt;/p&gt;
&lt;p&gt;Check out &lt;a href="https://realpython.com/prevent-python-sql-injection/#using-sql-composition" rel="noopener"&gt;Preventing SQL Injection Attacks With Python&lt;/a&gt; for more about how to safely compose SQL for PostgreSQL in Python using Psycopg2.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="take-away"&gt;&lt;a class="toclink" href="#take-away"&gt;Take Away&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Throughout this article you used several different approaches to solve very common concurrency issues in two seemingly simple tasks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create short URL&lt;ul&gt;
&lt;li&gt;⛔ #1: Failing to recognize possible collisions&lt;/li&gt;
&lt;li&gt;⛔ #2: Time-of-check time-of-use (TOCTOU)&lt;/li&gt;
&lt;li&gt;✅ #1: Lock!&lt;/li&gt;
&lt;li&gt;✅ #2: Ask for forgiveness&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Increment hit counter&lt;ul&gt;
&lt;li&gt;⛔ #3: Ignore race conditions&lt;/li&gt;
&lt;li&gt;✅ #3: Select for update&lt;/li&gt;
&lt;li&gt;✅ #4: Increment in the database&lt;/li&gt;
&lt;li&gt;✅ #5: Update and immediately return&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Some of these approaches were fragile, and broke under even minor loads, and others has their advantages and disadvantages.&lt;/p&gt;
&lt;p&gt;The main take away is this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Keep concurrency in mind&lt;/strong&gt;&lt;br&gt;Concurrency issues are very hard to miss, and even harder to debug. Recognizing concurrency issues requires special attention and awareness. This article covers the awareness part, so now you just need to pay attention.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Don't let probabilities distract you&lt;/strong&gt;&lt;br&gt;It's very tempting to dismiss concurrency issues when the likelihood is low. Hopefully now that you know a few more ways to handle concurrency you are in a better position to resist the temptation.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Avoid locks if possible&lt;/strong&gt;&lt;br&gt;Locks slow things down and cause contention so it's best to avoid them when possible. Now you know how.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For more advanced approaches for managing concurrency in Django, check out &lt;a href="how-to-manage-concurrency-in-django-models"&gt;How to Manage Concurrency in Django Models&lt;/a&gt;.&lt;/p&gt;</content><category term="articles"></category><category term="Django"></category><category term="ORM"></category></entry><entry><title>One Database Transaction Too Many</title><link href="https://hakibenita.com/django-nested-transaction" rel="alternate"></link><published>2021-06-07T00:00:00+03:00</published><updated>2021-06-07T00:00:00+03:00</updated><author><name>Haki Benita</name></author><id>tag:hakibenita.com,2021-06-07:/django-nested-transaction</id><summary type="html">&lt;p&gt;A story about how I ended up sending hundreds of users messages saying they got paid when they didn't! In the process we've learned a valuable lesson about nested transactions and Django signals.&lt;/p&gt;</summary><content type="html">&lt;hr&gt;
&lt;p&gt;Have you ever wondered how bugs are born? I'm not talking about the trivial kind you can catch with simple unit testing. I'm talking about bugs that may not be apparent on first sight, but are so obvious in retrospect.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;This is a story about how I accidentally sent hundreds of users messages they got paid when they didn't!&lt;/strong&gt;&lt;/p&gt;
&lt;figure&gt;&lt;img alt="What it feels like when you realized you made a mistake&amp;lt;br&amp;gt;&amp;lt;small&amp;gt;Illustration by &amp;lt;a href=&amp;quot;https://www.abstrakt.design/&amp;quot;&amp;gt;Milica Vezmar Basara&amp;lt;/a&amp;gt;&amp;lt;/small&amp;gt;" src="https://hakibenita.com/images/abstrakt-design-halloween-2020-22.png"&gt;&lt;figcaption&gt;What it feels like when you realized you made a mistake&lt;br&gt;&lt;small&gt;Illustration by &lt;a href="https://www.abstrakt.design/"&gt;Milica Vezmar Basara&lt;/a&gt;&lt;/small&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;details class="toc-container" open&gt;
    &lt;summary&gt;Table of Contents&lt;/summary&gt;&lt;/p&gt;
&lt;div class="toc"&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#the-story"&gt;The Story&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#creating-a-payout"&gt;Creating a Payout&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#sending-notifications"&gt;Sending Notifications&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#working-in-bulk"&gt;Working in Bulk&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#the-bug"&gt;The Bug&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#nested-transactions"&gt;Nested Transactions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#remedies"&gt;Remedies&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#assert-atomic-block"&gt;Assert Atomic Block&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#durable-transaction"&gt;Durable Transaction&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#sending-signal-on-commit"&gt;Sending Signal on Commit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#using-a-queue"&gt;Using a Queue&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#testing"&gt;Testing&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#testing-with-django"&gt;Testing with Django&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#testing-with-pytest"&gt;Testing with Pytest&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#thoughts-on-django-signals"&gt;Thoughts on Django Signals&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;p&gt;&lt;/details&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="the-story"&gt;&lt;a class="toclink" href="#the-story"&gt;The Story&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We have a process in the system where we pay out money to merchants and other types of users. The payout process is a pretty big deal for most users because this is how they get paid.&lt;/p&gt;
&lt;h3 id="creating-a-payout"&gt;&lt;a class="toclink" href="#creating-a-payout"&gt;Creating a Payout&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To facilitate the payout process we have a Django model called &lt;code&gt;PayoutProcess&lt;/code&gt;. To create a new payout we use a function that looks roughly like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;__future__&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;annotations&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;db_transaction&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PayoutProcess&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="c1"&gt;#... fields&lt;/span&gt;

    &lt;span class="nd"&gt;@classmethod&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;PayoutProcess&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# ... Validate input ...&lt;/span&gt;

        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;db_transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;atomic&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="n"&gt;payout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;pending&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="c1"&gt;# Create related objects etc...&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;payout&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The simplified version of this function creates a new instance of a payout process and returns it. In real life this function validates the input and creates several related objects. To make sure all of the related objects are created along with the payout process instance, we use a database transaction.&lt;/p&gt;
&lt;p&gt;The new instance now represents a payout process in the system, and the payout module is responsible for completing the payout. A payout can be fulfilled in many different ways, such as by bank transfer, credit card and other methods. Not all payout methods are immediate, so &lt;strong&gt;a payout is an asynchronous process that can take some time to complete&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;When the payout is eventually paid, the module updates the instance status:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PayoutProcess&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="nd"&gt;@classmethod&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;mark_paid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;PayoutProcess&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;db_transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;atomic&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="n"&gt;payout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;select_for_update&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pk&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;pk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;payout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;pending&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;StateError&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;payout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;paid&amp;#39;&lt;/span&gt;
            &lt;span class="n"&gt;payout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&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;payout&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The function fetches the payout, checks its state and marks it as paid. So far so good!&lt;/p&gt;
&lt;h3 id="sending-notifications"&gt;&lt;a class="toclink" href="#sending-notifications"&gt;Sending Notifications&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;At some point our staffers came to us with an idea. They said it would be nice if the system would notify users that they got paid. We thought that this is a great idea! Who doesn't like to get a message saying they got some $$$?&lt;/p&gt;
&lt;p&gt;The payout module is a core module in our system. We have payouts going out to different types of users and top level apps use it to create payouts in different contexts. For example, one app sends out commission payments to merchants, and another issues payments to business partners.&lt;/p&gt;
&lt;p&gt;To keep the payout module independent and decoupled from the apps that use it, the top level apps are the ones sending the notification to the users. The problem is that after the top level app created the payout process, the payout module handles the actual payment internally, and the top level app has no way of knowing about it unless it constantly monitors its status.&lt;/p&gt;
&lt;figure&gt;
&lt;p&gt;&lt;svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 267.8984092783621 421.3308681218084" width="267.8984092783621" height="421.3308681218084"&gt;
  &lt;g stroke-linecap="round"&gt;&lt;g transform="translate(65.9251034428728 204.6887627435733) rotate(35.06792711455562 59.703688558597264 -1.5004384097272236)" fill-rule="evenodd"&gt;&lt;path d="M-0.05 -1.42 L43.49 52.09 L120.45 -11.36 L88.65 -52.85 L-1.83 -5.86" stroke="none" stroke-width="0" fill="#daaeff44" fill-rule="evenodd"&gt;&lt;/path&gt;&lt;path d="M0.42 0.15 C11.25 12.21, 24.27 28.04, 43.47 49.28 M0.72 0.44 C9.71 10.64, 20.01 22.68, 43.98 51.48 M43.23 51.94 C72.86 27.19, 99.56 4.62, 120.68 -10.79 M43.64 50.6 C74.62 25.73, 104.61 0.73, 118.95 -12.57 M121.29 -11.96 C106.87 -25.15, 95.93 -39.37, 86.76 -53.72 M119.82 -11.4 C108.58 -28.01, 96.67 -41.63, 88.17 -52.76 M89.24 -54.94 C60.04 -39.69, 26.15 -22.3, -1.88 -7.64 M88.49 -53.05 C69.15 -42.84, 51.66 -33.6, -1.01 -5.86 M-0.13 -6.78 C-0.53 -4.55, -0.52 -3.53, 0.58 -0.51 M-0.72 -6.75 C-0.66 -4.88, 0.02 -3.27, 0.23 0.23" stroke="transparent" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g transform="translate(94.78198525753078 189.77899088367008) rotate(0 33.5 13)"&gt;&lt;text x="33.5" y="18" font-size="20px" fill="currentColor" text-anchor="middle" &gt;Payout&lt;/text&gt;&lt;/g&gt;&lt;g stroke-linecap="round"&gt;&lt;g transform="translate(19.71055668610188 14.417519022198235) rotate(0 37.05220775927103 31.470392396558566)" fill-rule="evenodd"&gt;&lt;path d="M-1.98 0.55 L70.28 2.77 L82.36 62.57 L-8.44 60.18 L1.16 0.08" stroke="none" stroke-width="0" fill="#f41d9222" fill-rule="evenodd"&gt;&lt;/path&gt;&lt;path d="M0.57 0.73 C23.26 0.68, 49.55 -0.12, 69.59 -0.08 M-0.75 0.91 C25.93 -0.01, 52.26 1.25, 70.96 1.61 M68.77 0.04 C72.46 16.59, 75.09 30.25, 81.89 61.39 M69.88 1.37 C74.6 20.01, 77.03 40.55, 82.93 61.01 M83.81 63.12 C58.02 64.29, 28.68 61.88, -9.4 63.45 M81.31 61.18 C60.22 61.62, 37.7 62.09, -9.6 62.59 M-9.71 61.72 C-6.2 48.2, -4.98 35.99, -0.06 1.37 M-8.92 62.21 C-5.78 45.21, -3.6 29.31, -0.53 -0.51 M0 0 C0 0, 0 0, 0 0 M0 0 C0 0, 0 0, 0 0" stroke="transparent" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g stroke-linecap="round"&gt;&lt;g transform="translate(163.13047010601576 17.009510364189737) rotate(0 47.01278034673828 27.617796965927198)" fill-rule="evenodd"&gt;&lt;path d="M0.47 -0.63 L95.57 -4.95 L81.08 63.8 L4.15 57.68 L-0.51 0.01" stroke="none" stroke-width="0" fill="#f41d9222" fill-rule="evenodd"&gt;&lt;/path&gt;&lt;path d="M1.48 -1.05 C33.45 -1.56, 67.82 -5.86, 91.84 -3.46 M-0.74 0.65 C36.52 -1.58, 75.37 -4.38, 92.82 -5.79 M94.77 -7.01 C90.17 14.9, 86.8 39.33, 83.72 62.25 M94.22 -5.25 C89.7 16.58, 86.16 39.68, 81.98 61.68 M81.42 60.52 C59.39 59.3, 37.18 58.13, 5.12 58.66 M81.98 60.98 C59.41 61.44, 37.76 60.63, 3.29 58.83 M3.69 60.09 C2.68 47.43, 1.87 31.84, 1.72 0.61 M3.45 58.49 C2.87 40.82, 2.51 22.56, 0.58 -0.72 M0 0 C0 0, 0 0, 0 0 M0 0 C0 0, 0 0, 0 0" stroke="transparent" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g transform="translate(19.163298388844396 35.55676866144802) rotate(0 39 10.5)"&gt;&lt;text x="39" y="15" font-size="16px" fill="currentColor" text-anchor="middle" &gt;Merchants&lt;/text&gt;&lt;/g&gt;&lt;g transform="translate(174.52693475248088 34.46585957053901) rotate(0 30.5 10.5)"&gt;&lt;text x="30.5" y="15" font-size="16px" fill="currentColor" text-anchor="middle" &gt;Refunds&lt;/text&gt;&lt;/g&gt;&lt;g stroke-linecap="round"&gt;&lt;g transform="translate(59.57238929793516 75.46585957053901) rotate(0 17.979516149667916 45.769515162418486)"&gt;&lt;path d="M0.79 1.42 C9.03 20.38, 14.5 37.98, 35.75 90.77 M0.21 0.77 C6.49 18.94, 14.48 37.32, 35.5 89.51" stroke="#000000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(59.57238929793516 75.46585957053901) rotate(0 17.979516149667916 45.769515162418486)"&gt;&lt;path d="M16.2 68.66 C20.74 73.15, 22.79 76.4, 35.2 90.81 M15.61 68.01 C19.12 72.43, 23.82 76.95, 34.95 89.55" stroke="#000000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(59.57238929793516 75.46585957053901) rotate(0 17.979516149667916 45.769515162418486)"&gt;&lt;path d="M35.21 60.95 C35.73 66.94, 33.73 71.84, 35.2 90.81 M34.63 60.3 C34.16 66.42, 34.94 72.54, 34.95 89.55" stroke="#000000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g stroke-linecap="round"&gt;&lt;g transform="translate(207.2023819182773 77.29418156135) rotate(0 -31.74047533939165 45.15799520164728)"&gt;&lt;path d="M0.41 1.54 C-14.15 20.5, -25.98 39.07, -63.89 90.76 M0.36 -0.44 C-22.99 32.22, -46.65 66.13, -62.22 90.01" stroke="#000000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(207.2023819182773 77.29418156135) rotate(0 -31.74047533939165 45.15799520164728)"&gt;&lt;path d="M-54.67 62.41 C-57.3 68.35, -57.65 74.23, -63.34 90.09 M-54.73 60.43 C-57.64 70.59, -61.01 82.06, -61.67 89.34" stroke="#000000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(207.2023819182773 77.29418156135) rotate(0 -31.74047533939165 45.15799520164728)"&gt;&lt;path d="M-37.61 73.82 C-43.67 77.51, -47.54 81.03, -63.34 90.09 M-37.67 71.83 C-46.78 77.85, -56.37 85.17, -61.67 89.34" stroke="#000000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g stroke-linecap="round" transform="translate(44.123382014298386 332.3281237465423) rotate(0 44.994461829091165 36.77409946036036)"&gt;&lt;path d="M0 0 C0 0, 0 0, 0 0 M0 0 C0 0, 0 0, 0 0 M0.04 5.72 C1.03 4.87, 3.13 2.47, 4.37 1.1 M-0.55 6.38 C1.49 4.37, 3.21 2.25, 5.34 0.71 M0.95 12.49 C3.65 8.31, 5.32 7.35, 9.83 -0.18 M-0.34 11.51 C3.57 9.14, 6.68 5.42, 10.18 -0.68 M0.79 17.78 C5.11 12.35, 10.44 3.96, 16.64 -0.37 M0.19 18.8 C4.61 11.79, 11.85 5.14, 16.16 0.95 M0.24 25.33 C7.02 17.47, 10.71 12.07, 22.75 -0.82 M-0.29 24.39 C5.5 18.66, 9.63 13.87, 20.66 -0.76 M1.88 28.5 C5.69 24.83, 11.04 15.99, 26.93 -1.06 M-0.12 30.16 C6.46 22.92, 14.02 14.36, 26.5 0.44 M0.36 35.29 C9.35 23.26, 21.06 12.2, 32.2 0.23 M0 36.12 C10.63 25.2, 19.22 14.19, 32.27 -0.19 M-1.23 42.58 C9.88 29.73, 19.8 20.21, 38.5 -0.79 M0.89 41.63 C10.23 31.9, 18.68 21.79, 36.87 0.48 M0.78 50.29 C16.03 32.64, 27.68 16.97, 42.08 0.61 M0.83 48.69 C14.78 31.6, 29.01 14.48, 42.7 0.57 M1.34 55.94 C16.36 38.84, 31.08 20.87, 48.37 -0.29 M0.13 53.78 C11.62 42.86, 22.17 29.28, 48.36 0.22 M-0.41 60.59 C13.36 46.05, 23.48 32.31, 51.32 0.15 M-0.24 60.89 C15.08 42.29, 33.12 22.85, 53.82 0.64 M1.57 67.43 C12.6 52.28, 22.7 40.33, 58.35 -0.13 M-0.05 67.36 C18.08 45.7, 35.24 24.98, 59.13 -0.78 M0.37 71.9 C26.1 46.15, 49.49 17.17, 65.64 0.9 M0.09 73.16 C24.13 43.41, 50.57 13.55, 63.61 0.54 M2.9 76.82 C26.02 47.68, 50.79 19.85, 67.07 -0.67 M3.38 77.23 C20.56 55.68, 39.26 33.56, 68.48 -0.53 M7.38 77.03 C32.28 46.72, 55.45 21.17, 73.58 -0.91 M8.08 75.29 C27.38 53.52, 47.73 29.92, 74.12 0.2 M12.2 77.29 C32.21 57.26, 48.09 34.92, 77.99 1.91 M12.21 76.92 C33.8 51.75, 55.07 27.05, 78.74 -0.66 M20.56 76.09 C42.09 49.92, 63.87 23.91, 86.48 1.13 M17.97 76.58 C38.7 54.03, 58.32 30.61, 85.53 -1.05 M22.71 74.56 C43.89 53.87, 67.51 29.39, 91.37 -0.38 M23.84 75.69 C45.2 52.18, 65.12 27.5, 89.39 -0.39 M31.3 74.35 C49.34 54.35, 65.46 34.88, 88.62 6.41 M29.72 75.7 C49.47 52.63, 70.92 27.08, 90.18 6.31 M34.33 75.49 C48.05 58.82, 60.33 47.02, 89.77 12.72 M33.66 77.15 C51.89 56.31, 70.24 34.92, 90.47 11.56 M38.42 74.27 C50.69 63.3, 62.63 46.95, 90.28 20.2 M41.01 76.72 C50.98 61.13, 63.79 48.2, 89.73 18.93 M45.79 77.12 C57.75 63.58, 68.45 51.36, 89.57 25.52 M45.14 76.32 C58.14 60.42, 72.61 45.58, 90.13 24.71 M49.56 74.47 C63.71 58.51, 77.99 42.76, 91.85 30.21 M50.6 76.11 C65.59 59.58, 80.52 41.55, 89.21 31.07 M54.64 74.66 C63.86 67.3, 75.13 53.48, 88.83 35.34 M55.84 75.63 C67.69 62, 80.97 47.52, 90.44 37.81 M61.15 77.78 C67.9 65.43, 78.92 57.94, 91.92 43.6 M60.35 76.76 C68.08 66.67, 75.19 58.29, 91.16 41.69 M65.23 77.54 C73.3 67.7, 83.61 54.17, 88.35 49.44 M65.42 76.91 C71.89 69.68, 75.25 64.62, 90.56 49.77 M70.46 75.19 C75.78 72.73, 79.24 65.74, 91.91 53.71 M71.23 75.95 C78.63 67.79, 85.82 61.54, 90.2 55.16 M76.72 77.35 C80.59 73.57, 84.66 66.3, 91.81 61.51 M77.64 75.39 C79.98 73.8, 81.72 70.6, 90.74 60.15 M83.11 74.73 C85.97 73.53, 87.98 69.53, 90.67 68.21 M82.96 75.54 C83.82 73.99, 86.24 72.25, 90.12 67.74 M87.45 76.06 C88.4 75.4, 88.75 74.17, 89.8 73 M87.71 76.03 C88.4 75.08, 89.16 74.33, 90.34 72.9" stroke="#ced4da" stroke-width="0.5" fill="none"&gt;&lt;/path&gt;&lt;path d="M-0.95 -1 C18.71 -2.39, 35.89 -0.78, 88.7 -0.34 M-0.76 0.01 C27.86 0.74, 56.89 0.34, 89.32 -0.21 M90.61 1.19 C90.38 29.13, 89.11 56.32, 89.83 74.86 M89.88 0.28 C90.07 29.12, 90.85 56.4, 89.23 73.31 M90.46 73.84 C62.7 73.32, 30.79 71.76, 0.68 71.77 M90.68 74.35 C63.49 74.14, 38.68 74.49, -0.04 74.17 M1.12 72.92 C-0.16 56.06, 1.37 35.66, 1.58 -0.58 M0 74.28 C0.46 51.48, -0.87 29.29, 0.39 0.57" stroke="transparent" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(70.11784384338966 358.6022232069027) rotate(0 19 10.5)"&gt;&lt;text x="19" y="15" font-size="16px" fill="currentColor" text-anchor="middle" &gt;Bank&lt;/text&gt;&lt;/g&gt;&lt;g stroke-linecap="round" transform="translate(160.03247292338915 337.7826692010877) rotate(0 44.994461829091165 36.77409946036036)"&gt;&lt;path d="M0 0 C0 0, 0 0, 0 0 M0 0 C0 0, 0 0, 0 0 M-0.57 6.81 C0.85 5.19, 2.22 3.97, 5.24 0.65 M-0.49 6.62 C1.15 4.94, 2.07 3.86, 4.98 0.61 M1.43 13.23 C3.13 6.4, 6.3 2.27, 9.74 0.46 M-0.38 11.56 C3.68 6.65, 7.65 3.4, 11.38 -0.83 M-1.59 19.12 C3.06 12.3, 7.86 8.25, 15.36 -0.31 M-0.6 18.69 C3.91 14.15, 8.13 10.26, 15.93 -0.47 M0.19 24.39 C6.2 17.92, 9.47 9.72, 21.77 -1.58 M0.07 24.46 C7.97 15.06, 16.52 6.18, 20.58 -0.04 M-0.13 32.1 C7.6 19.58, 15.78 12, 27.75 -1.5 M-0.23 30.47 C10.79 18.56, 20.71 6.4, 26.7 0.98 M0.2 36.44 C13.26 23.23, 27.2 7.78, 33.81 -0.56 M-0.51 37.05 C11.89 23.86, 21.98 11.46, 32.42 0.59 M-0.19 43.38 C13.02 25.12, 27.26 11.44, 36.59 -1.31 M0.09 43.37 C12.45 27.14, 25.94 12.73, 36.67 0.07 M-0.85 47.09 C16.59 30.57, 33.08 13.19, 42.03 -0.28 M-0.68 49.59 C9.49 37.32, 17.18 27.37, 43.43 -0.1 M0.9 54.77 C15.44 37.7, 28.89 22.2, 47.91 0.22 M-0.44 55.56 C12.89 40.58, 27.28 23.85, 47.62 -0.33 M0.65 62.82 C20.33 40.41, 40.02 17.53, 53.05 0.14 M-0.43 60.78 C11.35 49.29, 21.42 37.29, 53.35 0.05 M-0.66 65.66 C15.08 48.53, 33.92 30.45, 59.89 1.89 M0.15 67.07 C16.12 47.75, 34.13 26.18, 57.75 0.71 M1.15 72.16 C17.39 53.53, 33.96 33.92, 63.64 -1.6 M-0.71 72.71 C13.63 57.62, 26.76 43.42, 63.31 0.26 M2.09 74.98 C29.28 47.37, 52.66 16.13, 66.91 1.3 M1.81 75.72 C16.59 60.78, 32.38 43.86, 69.66 0.22 M9.15 77.43 C31.65 50.44, 52.43 23.79, 72.8 1.01 M7.83 76.48 C27.94 52.12, 49.64 27.8, 73.88 -1.11 M14.98 77.68 C28.24 57.54, 45.56 36.91, 79.55 -1.13 M12.56 75.71 C35.81 50.56, 58.66 23.52, 80.35 -0.66 M20.22 74.8 C41.86 51.01, 65.49 21.94, 85.67 -0.72 M17.83 76.37 C33.05 58.61, 47.14 42.56, 85.01 -0.67 M25.54 75.92 C43.96 55.37, 63.19 34.46, 88.55 1.83 M23.35 76.62 C43.44 51.35, 65.15 28.02, 89.22 -0.72 M28.74 76.3 C44.43 59.15, 56.85 46.28, 91.7 8.15 M28.95 75.75 C44.71 58.7, 58.83 42.33, 90.15 6.92 M35.38 75.56 C56.68 51.74, 75.15 28.64, 90.38 12.28 M34.43 76.11 C49.41 60.12, 63.97 42.89, 89.6 11.44 M39.91 77.7 C52.61 63.13, 64.19 46.51, 89.76 19.05 M39.88 75.06 C53.65 60.26, 66.98 44.07, 89.39 17.75 M44.29 75.3 C53.41 65.48, 64.19 52.86, 90.65 23 M45.45 76.91 C55.53 64.7, 64.3 53.42, 90.22 25.15 M51.11 77.6 C60.88 64.23, 68.37 55.58, 88.14 32.46 M49.71 76.84 C60.13 65.36, 71.58 52.24, 89.5 31.25 M54.24 74.65 C62.8 66.34, 68.3 61.08, 88.04 38.43 M55.45 76.86 C68.91 61.54, 79.57 48.77, 89.03 36.67 M59.91 77.43 C68.74 66.98, 77.59 56.96, 88.78 42.95 M61.96 75.72 C71.18 64.94, 80.57 53.79, 90.07 43.21 M65.39 77.94 C73.43 66.36, 80.2 58.27, 91.35 47.52 M66.89 76.88 C73.16 67.95, 81.09 58.32, 90.4 48.08 M72.06 77.4 C79.69 66.92, 83 59.78, 91.76 53.9 M72.53 76.41 C76.27 71.2, 80.52 66.69, 90.07 54.54 M76.09 74.3 C80.75 71.59, 87.24 67.94, 91.63 60.31 M77.64 76.98 C80.39 70.55, 85.96 66.37, 89.11 61.84 M81.99 76.19 C85.13 73.57, 86.21 72.13, 90.29 66.79 M82.11 75.74 C84.83 73.67, 86.53 71.46, 89.49 67.73 M87.41 76.42 C88.36 74.56, 89.63 73.36, 89.89 73.05 M87.43 76.14 C88.21 75.31, 88.61 74.64, 90.34 73.21" stroke="#ced4da" stroke-width="0.5" fill="none"&gt;&lt;/path&gt;&lt;path d="M-1.82 0.68 C33.13 0.21, 68.72 1.36, 91.36 1.61 M0.55 -0.04 C20.59 -0.56, 40.2 0.73, 90.55 -0.31 M89.66 1.58 C89.89 22.17, 90.27 45.35, 89.99 75.01 M89.52 0.39 C90.43 24.95, 90.64 49.46, 89.3 73.16 M90.34 73.11 C54.39 72.58, 20.89 72.85, 0.26 71.77 M89.96 74.38 C66.99 74.43, 44.89 74.71, 0.37 72.7 M-0.36 72 C1.51 55.15, 0.75 36.97, -1.46 -0.07 M-0.9 74.44 C1.11 48.31, 1.14 24.47, 0.51 0.28" stroke="transparent" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(179.5269347524802 353.5567686614481) rotate(0 25.5 21)"&gt;&lt;text x="25.5" y="15" font-size="16px" fill="currentColor" text-anchor="middle" &gt;Credit&lt;/text&gt;&lt;text x="25.5" y="36" font-size="16px" fill="currentColor" text-anchor="middle" &gt;Card&lt;/text&gt;&lt;/g&gt;&lt;g transform="translate(97.07238929793516 87.96585957053901) rotate(0 32.5 21)"&gt;&lt;text x="32.5" y="15" font-size="16px" fill="currentColor" text-anchor="middle" &gt;Create &lt;/text&gt;&lt;text x="32.5" y="36" font-size="16px" fill="currentColor" text-anchor="middle" &gt;Payout&lt;/text&gt;&lt;/g&gt;&lt;g stroke-linecap="round"&gt;&lt;g transform="translate(139.44000617548727 241.06440285847123) rotate(0 28.42418093826973 45.024129553697975)"&gt;&lt;path d="M-3.49 -2.16 C22.23 31, 40.66 58.74, 60.34 92.21 M-1.25 0.92 C18.19 28.25, 37.6 58.49, 60.34 91.59" stroke="#495057" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(139.44000617548727 241.06440285847123) rotate(0 28.42418093826973 45.024129553697975)"&gt;&lt;path d="M32.54 71.84 C47.19 82.24, 54.42 87.6, 59.4 92.37 M34.79 74.93 C43.21 78.14, 50.52 84.2, 59.4 91.75" stroke="#495057" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(139.44000617548727 241.06440285847123) rotate(0 28.42418093826973 45.024129553697975)"&gt;&lt;path d="M49.53 60.34 C59.01 74.04, 61.09 82.88, 59.4 92.37 M51.78 63.42 C54.47 70.42, 56.22 80.25, 59.4 91.75" stroke="#495057" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g stroke-linecap="round"&gt;&lt;g transform="translate(139.11175452857128 237.6194530633179) rotate(0 -26.606720757402172 45.86629873359206)"&gt;&lt;path d="M0.39 0.55 C-21.68 36.52, -42.26 73.8, -53.6 91.48 M0.08 -0.56 C-14 23.22, -27.07 45.26, -53.34 92.29" stroke="#495057" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(139.11175452857128 237.6194530633179) rotate(0 -26.606720757402172 45.86629873359206)"&gt;&lt;path d="M-48.04 63.24 C-51.64 74.44, -53.33 86.82, -53.85 90.7 M-48.35 62.14 C-50.37 70.78, -51.78 77.46, -53.58 91.51" stroke="#495057" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(139.11175452857128 237.6194530633179) rotate(0 -26.606720757402172 45.86629873359206)"&gt;&lt;path d="M-30.16 73.32 C-40.84 80.52, -49.54 88.95, -53.85 90.7 M-30.47 72.21 C-36.75 78.33, -42.49 82.58, -53.58 91.51" stroke="#495057" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g transform="translate(126.07238929793516 275.3749504796299) rotate(0 13.5 10.5)"&gt;&lt;text x="13.5" y="15" font-size="16px" fill="currentColor" text-anchor="middle" &gt;Pay&lt;/text&gt;&lt;/g&gt;&lt;/svg&gt;
&lt;figcaption&gt;Top level app creates a payout&lt;/figcaption&gt;&lt;/p&gt;
&lt;/figure&gt;
&lt;p&gt;To have the top level app respond to changes in the payout module, we need to have a mechanism to let the top level app know that something had changed. The tricky part is that the top level apps depend on the payout module, so the payout module cannot depend back on them. It will cause a circular dependency.&lt;/p&gt;
&lt;figure&gt;
&lt;p&gt;&lt;svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400.1237576753888 420.9203548399862" width="400.1237576753888" height="420.9203548399862"&gt;
&lt;g stroke-linecap="round"&gt;&lt;g transform="translate(65.26044666035023 204.2782494617511) rotate(35.06792711455562 60.22152623642637 -1.1676249167510946)" fill-rule="evenodd"&gt;&lt;path d="M-0.61 -0.37 L45.61 51.41 L120.57 -13.49 L89.58 -54.47 L-1.2 -5.9" stroke="none" stroke-width="0" fill="#daaeff44" fill-rule="evenodd"&gt;&lt;/path&gt;&lt;path d="M-1.07 -0.04 C14.32 16.35, 26.23 33.07, 42.72 50.18 M-0.01 0.69 C11.28 12.43, 21.87 25.36, 44.33 50.97 M43.62 52.17 C63.36 34.56, 83.91 16.86, 121.69 -11.08 M44.47 51.23 C72.04 28.57, 99.94 4.58, 120.88 -11.46 M118.63 -12.84 C110.92 -21.72, 105.84 -34.36, 89.87 -54.09 M120.71 -12.9 C109.19 -25.49, 97.29 -39.98, 88.69 -54.51 M87.79 -53.25 C64.65 -41.12, 39.49 -29.72, -0.76 -6.11 M88.26 -54.21 C56.08 -38, 25.12 -19.83, -0.74 -6.82 M-1.25 -6.83 C-0.77 -5.36, -0.2 -3.76, -0.09 -0.2 M-1.01 -6.65 C-0.48 -5.43, -0.61 -3.92, -0.23 0.01" stroke="transparent" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g transform="translate(94.11732847500798 189.36847760184787) rotate(0 33.5 13)"&gt;&lt;text x="33.5" y="18"  font-size="20px" fill="currentColor" text-anchor="middle" &gt;Payout&lt;/text&gt;&lt;/g&gt;&lt;g stroke-linecap="round"&gt;&lt;g transform="translate(19.045899903579084 14.007005740375917) rotate(0 37.13987370549398 30.82963782247333)" fill-rule="evenodd"&gt;&lt;path d="M1.95 0.27 L70.67 -0.13 L83.52 61.01 L-9.11 62.62 L0.91 -1.5" stroke="none" stroke-width="0" fill="#f41d9222" fill-rule="evenodd"&gt;&lt;/path&gt;&lt;path d="M1.06 0.3 C13.87 1.63, 27.53 -0.52, 69 1.11 M0.35 -0.65 C16.44 -0.75, 33.53 -0.16, 69.64 0.93 M69.46 1.72 C74.18 22.7, 80.46 46.15, 83.33 62.92 M70.87 0.94 C73.92 12.69, 76.45 27.06, 81.54 61.16 M80.75 62.73 C52.63 60.28, 21.72 61.22, -7.79 63.17 M82.53 62.85 C51.64 62.09, 22.7 60.5, -9.05 61.37 M-8.32 62.91 C-8.12 49.32, -5.07 37.37, 1.15 -1.52 M-8.59 61.24 C-5.74 47.19, -4.43 30.88, -0.56 -0.19 M0 0 C0 0, 0 0, 0 0 M0 0 C0 0, 0 0, 0 0" stroke="transparent" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g stroke-linecap="round"&gt;&lt;g transform="translate(162.46581332349297 16.598997082367532) rotate(0 46.38528032770205 28.47783512720497)" fill-rule="evenodd"&gt;&lt;path d="M0.27 0.67 L92.43 -3.8 L81.13 61.48 L3.72 60.2 L-1.5 -0.1" stroke="none" stroke-width="0" fill="#f41d9222" fill-rule="evenodd"&gt;&lt;/path&gt;&lt;path d="M0.3 -0.36 C24.5 -2.73, 45.24 -4.11, 93.68 -6.6 M-0.65 0.01 C27.41 -1.23, 55.73 -3.84, 93.5 -4.68 M94.29 -5.45 C90.24 16.53, 88.25 36.1, 83.04 60.04 M93.51 -4.36 C88.3 21.43, 85.66 45.61, 81.28 62.33 M82.85 63.55 C61.96 62.57, 44.2 61.07, 4.28 59.9 M82.97 61.49 C52.7 61.92, 21.03 59.96, 2.47 58.31 M4.01 58.02 C1.96 42.4, 1.63 26.62, -1.52 -1.02 M2.34 60.19 C3.06 40.49, 1.19 21.03, -0.19 0.2 M0 0 C0 0, 0 0, 0 0 M0 0 C0 0, 0 0, 0 0" stroke="transparent" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g transform="translate(18.498641606322053 35.14625537962593) rotate(0 39 10.5)"&gt;&lt;text x="39" y="15"  font-size="16px" fill="currentColor" text-anchor="middle" &gt;Merchants&lt;/text&gt;&lt;/g&gt;&lt;g transform="translate(173.86227796995763 34.05534628871692) rotate(0 30.5 10.5)"&gt;&lt;text x="30.5" y="15"  font-size="16px" fill="currentColor" text-anchor="middle" &gt;Refunds&lt;/text&gt;&lt;/g&gt;&lt;g stroke-linecap="round"&gt;&lt;g transform="translate(58.90773251541259 75.05534628871692) rotate(0 18.258686520328638 44.60247732614249)"&gt;&lt;path d="M-0.93 -0.97 C10.21 27.98, 21.26 56.11, 37.44 90.17 M0.68 -0.17 C8.12 21.04, 17.55 42.17, 36.57 89.2" stroke="#000000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(58.90773251541259 75.05534628871692) rotate(0 18.258686520328638 44.60247732614249)"&gt;&lt;path d="M15.48 66.03 C21.72 73.89, 27.77 80.82, 37.96 89.9 M17.09 66.82 C20.79 72.17, 26.49 77.51, 37.09 88.93" stroke="#000000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(58.90773251541259 75.05534628871692) rotate(0 18.258686520328638 44.60247732614249)"&gt;&lt;path d="M34.47 58.26 C34.72 68.71, 34.78 78.09, 37.96 89.9 M36.08 59.05 C35.32 66.13, 36.57 73.29, 37.09 88.93" stroke="#000000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g stroke-linecap="round"&gt;&lt;g transform="translate(206.5377251357545 76.88366827952768) rotate(0 -31.61287960367781 45.005114175379276)"&gt;&lt;path d="M-0.97 -1 C-17.03 23.98, -34.8 52.25, -62.09 89.37 M-0.17 -0.36 C-19.54 28.37, -38.34 55.61, -63.05 91.01" stroke="#000000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(206.5377251357545 76.88366827952768) rotate(0 -31.61287960367781 45.005114175379276)"&gt;&lt;path d="M-56.34 61.01 C-57.29 68.84, -59.53 79.72, -62.36 89.7 M-55.54 61.65 C-57.98 71.41, -60.17 80.06, -63.32 91.33" stroke="#000000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(206.5377251357545 76.88366827952768) rotate(0 -31.61287960367781 45.005114175379276)"&gt;&lt;path d="M-39.49 72.74 C-45.28 77.18, -52.27 84.75, -62.36 89.7 M-38.7 73.38 C-46.15 79.72, -53.42 84.83, -63.32 91.33" stroke="#000000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g stroke-linecap="round" transform="translate(43.45872523177604 331.91761046472016) rotate(0 44.99446182909128 36.77409946036039)"&gt;&lt;path d="M0 0 C0 0, 0 0, 0 0 M0 0 C0 0, 0 0, 0 0 M-0.86 6.36 C1.31 5.12, 1.91 3.25, 5.42 0.37 M-0.05 6.01 C0.99 4.92, 2.78 2.76, 4.68 0.74 M-1.32 11.02 C3.96 8.98, 6.78 4.82, 11.67 0.34 M-0.42 12.65 C2.89 9.37, 4.18 7.2, 10.56 -0.38 M-0.2 18.4 C4.39 11.99, 9.33 9.33, 16.02 -0.05 M0.45 19.34 C4.63 12.33, 10.82 6.52, 16.24 1.03 M-0.02 23.66 C6.83 15.22, 11.71 10.57, 21.23 -0.36 M0.33 23.17 C6.95 16.23, 12.76 9.23, 20.97 0.81 M0.82 32.02 C6.81 23.88, 12.79 16.65, 26.22 2.08 M0.5 30.22 C9.06 19.42, 19.76 7.94, 26.32 -0.7 M-0.3 38.59 C9.03 25.38, 21.02 10.19, 32.84 1.61 M-0.59 35.97 C9.19 25.39, 19.92 13.48, 30.9 -0.73 M1.51 42.23 C9.29 30.72, 17.49 20.4, 34.99 2.2 M0.4 42.82 C8.2 34.2, 14.14 25.46, 37.11 0.97 M-1.12 47.71 C13.88 35.53, 24.76 21.04, 42 -0.66 M-0.23 49.39 C11.12 37.02, 20.52 23.17, 42.28 -0.82 M-1.14 56.29 C13.74 35.14, 30.64 21.02, 47.51 1.91 M1.23 54.31 C17.58 35.05, 32.79 17.83, 47.36 0.27 M-0.21 59.63 C17.43 43.87, 30.77 24.11, 52.81 -1.97 M0.63 61.31 C17.01 41.42, 33.62 22.8, 52.15 -0.75 M-1.96 66.39 C18.82 48.43, 36.87 24.37, 59.75 1 M0.5 67.01 C17.91 46.02, 38.23 24.06, 57.93 0.85 M0.16 72.38 C15.32 54.6, 32.68 33.99, 62.12 -0.93 M-0.02 72.25 C15.85 55.17, 32.5 36.88, 64.68 0.57 M1.63 75.65 C18.59 55.44, 36.63 36.44, 70.51 1.44 M3.43 76.37 C23.24 52.62, 44.09 29.5, 69.75 -0.24 M8.39 77.14 C29.63 47.62, 55.36 21.91, 75.66 0.02 M7.74 75.07 C27.22 52.92, 48.35 28.71, 75.08 0.64 M13.66 74.59 C26.45 60.57, 43.07 40.93, 81.38 -0.61 M12.97 75.48 C35.61 50.53, 57.29 25.2, 79.86 -0.55 M18.59 74.35 C35.55 53.1, 55.15 35.18, 83.91 -1.37 M18.83 75.15 C39.35 52, 60.53 28.27, 85.71 -0.62 M22.51 76.28 C42.36 52.69, 62.08 31.19, 88.78 1.21 M24.71 76.18 C39.65 57.26, 57.37 39.03, 89.38 -0.22 M30.3 75.63 C43.66 61.18, 57.13 47.03, 88.3 7.65 M28.88 76.25 C42.78 60.59, 54.22 45.91, 90.06 5.9 M33.27 76.92 C54.69 53.13, 75.27 32.54, 89.45 11.52 M35.19 75.67 C53.75 54.49, 73.67 31.61, 91.05 12.12 M40.51 75.78 C55.1 59.76, 72.49 41.85, 90.28 19.7 M40.23 76.5 C54.29 58.58, 67.94 43.3, 89.99 19.4 M43.8 76.88 C58.27 63.06, 70.3 49.09, 91.91 24.95 M44.05 75.55 C62.42 57.13, 78.44 38.46, 90.67 25.05 M48.99 75.76 C65.63 60.11, 76.69 44.24, 91.38 31.81 M51.33 75.46 C65.97 58.11, 81.16 41.1, 89.69 29.75 M56.82 76.74 C69.74 61.53, 78.84 46.24, 88.37 35.18 M56.04 75.81 C64.52 63.61, 74.92 53.65, 88.99 37.41 M62.35 74.74 C69.06 68.22, 74.57 56.71, 89.36 42.48 M60.7 76.09 C69.07 65.51, 77.36 56.72, 89.86 42.46 M67.82 77.25 C73.9 65.96, 80.5 59.88, 91.8 47.8 M66.99 76.87 C74.22 67.16, 81.94 58.66, 90.84 49.18 M70.01 74.08 C75.77 69.86, 82.23 61.66, 90.8 54.43 M70.99 76.76 C77.22 69.51, 82.84 62.62, 90.32 55.48 M77.13 75.35 C80.56 70.33, 82.94 68.83, 92.03 60.85 M77.82 75.54 C80.73 71.32, 85.74 65.19, 89.16 62.01 M83.46 76.6 C85.3 73.07, 85.8 72.67, 90.13 67.35 M82.75 75.45 C84.72 73.69, 86.57 71.08, 89.51 67.71 M87.21 76.12 C88.65 74.59, 89.37 73.42, 90.2 73.23 M87.35 76.29 C88.41 75.23, 89.06 74.42, 90.06 72.91" stroke="#ced4da" stroke-width="0.5" fill="none"&gt;&lt;/path&gt;&lt;path d="M-1 0.04 C32 0.41, 67.99 1.13, 88.68 0.03 M-0.36 -0.14 C22.94 0.14, 45.17 -0.12, 90.31 -0.08 M91.29 1.01 C88.79 22.67, 90.81 43.82, 89.73 75.42 M89.51 -0.74 C91.13 14.39, 90.43 29.41, 90.4 74.37 M90.89 74.7 C69.97 74.93, 47.51 74.33, 1.89 72.71 M89.63 72.89 C55.72 72.56, 23.59 72.9, 0.44 72.91 M1.15 72.03 C-1.03 52.48, -1.26 30.89, -1.57 1.8 M-0.56 73.36 C0.17 46.7, -0.54 21.03, -0.69 0.33" stroke="transparent" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(69.4531870608671 358.19170992508043) rotate(0 19 10.5)"&gt;&lt;text x="19" y="15"  font-size="16px" fill="currentColor" text-anchor="middle" &gt;Bank&lt;/text&gt;&lt;/g&gt;&lt;g stroke-linecap="round" transform="translate(159.36781614086658 337.3721559192654) rotate(0 44.99446182909105 36.77409946036039)"&gt;&lt;path d="M0 0 C0 0, 0 0, 0 0 M0 0 C0 0, 0 0, 0 0 M-0.14 6.83 C0.85 4.56, 3.84 2.24, 4.81 0.29 M-0.47 6.64 C1.87 3.87, 3.49 2.12, 5.02 0.53 M0.76 12.61 C2.58 9.79, 5.55 7.57, 9.12 1.35 M0.84 11.78 C2.55 9.15, 5.14 5.88, 10.27 -0.29 M0.31 16.67 C6.49 12.59, 10.79 5.98, 16.51 1.34 M-0.71 18.59 C4.77 13.32, 10.05 7.88, 15.55 1.26 M0.9 22.2 C4.93 18.62, 10.8 11.72, 21.53 0.96 M0.49 23.82 C6.21 17.1, 12.99 10.17, 20.62 -0.57 M0.45 30.99 C7.7 21.96, 15.03 14.61, 27.62 -0.08 M-0.67 30.6 C8.55 20.72, 16.62 10.35, 26.56 0.5 M-1.53 37.92 C12.58 20.04, 26.19 8.99, 32.15 0.97 M-0.68 36.86 C8.68 26.97, 16.01 16.83, 31.37 0.38 M0.4 41.28 C11.21 27.67, 21.96 16.91, 34.94 0.13 M0.72 42.64 C8.74 31.28, 19.56 20.92, 36.65 -0.32 M1.77 48.86 C11.07 36.71, 19.61 25.64, 41.58 0.54 M0.5 49.39 C11.6 33.45, 24.53 19.45, 42.76 0.65 M-1.11 52.57 C13.62 40.99, 26.12 26.55, 48.35 -1.64 M0.92 53.74 C14.6 37.79, 29.93 19.85, 47.48 0.39 M1.76 62.38 C18.98 38.59, 38.91 14.45, 53.59 0.63 M0.14 60.63 C10.42 48.67, 20.95 34.52, 53.23 -0.21 M0.78 68.86 C16.07 48.92, 31.26 34.71, 58.55 -0.83 M-1.16 67.07 C12.38 52.52, 23.97 39.54, 57.77 0.65 M-1.1 73.33 C12.95 56.71, 25.4 41.5, 62.8 0.41 M0.11 73.47 C19.28 52.89, 37.9 31.98, 63.34 0.75 M2.39 75.01 C24.63 51.8, 46.18 30.05, 69.06 -0.68 M2.08 75.95 C14.75 61.42, 29.53 45.22, 69.37 0.87 M6.32 75.63 C33.63 47.61, 60.38 17.11, 74.62 1.23 M7.22 76.14 C23.61 58.34, 40.15 41.79, 73.69 -0.1 M11.45 77.35 C26.68 59.67, 42.73 44.2, 80.18 -0.25 M12.99 76.32 C31.38 55.47, 51.87 32.35, 79.19 -0.14 M16.84 74.54 C39.33 53.91, 60.38 26.32, 85.27 -1.38 M18.29 76.36 C44.85 46.92, 71.54 16.17, 85.02 -1 M22.09 74.73 C35.82 60.66, 49.53 46.92, 89.06 1.6 M23.54 75.89 C38.06 60.6, 50.81 43.42, 89.26 0.85 M30.72 76.44 C46.21 59.69, 59.25 40.8, 87.9 5.11 M29.83 75.26 C50.59 50.1, 72.88 25.89, 90.23 6.03 M35.08 75.11 C50.95 60.17, 62.56 42.69, 91.15 10.26 M33.67 76.7 C48.34 59.62, 64.27 42.68, 90.19 11.53 M39.66 76.91 C54.58 61.72, 65.8 47.73, 89.66 19.1 M40.09 75.33 C56.54 56.4, 74.56 36.72, 89.55 18.07 M46.03 76.67 C57.96 63.03, 70.15 50.2, 91.7 23.16 M45.41 76.23 C57.04 63.5, 69.63 50.15, 90.68 23.88 M52.16 75.71 C64.56 58.74, 81 44.2, 91.82 29.4 M50.39 75.49 C59.32 65.49, 68.74 53.64, 90.58 29.8 M57.35 75.75 C67.48 58.92, 80.71 43.63, 89.62 38.76 M54.9 75.33 C67.85 62.38, 80.08 47.62, 89.39 37.08 M61.55 76.77 C66.66 65.68, 74.96 59, 89.85 44.31 M61.15 75.06 C71.22 64.37, 80.75 53.09, 90.4 42.64 M66.28 74.89 C73.51 67.7, 84.95 55.31, 90.61 49.41 M66.36 76.26 C73.02 68.61, 81.12 59.98, 90 48.47 M69.96 76.23 C74.7 70.41, 80.17 67.05, 90.04 54.3 M71.91 75.76 C79.8 66.31, 86.82 59.55, 90.21 55.38 M76.85 74.82 C80.22 72.2, 88.57 64.85, 91.21 61.96 M76.09 76.72 C80.03 71.74, 83.45 68.49, 89.37 60.08 M83.54 75.01 C84.49 73.95, 87.25 70.52, 89.26 68.18 M82.95 75.72 C84.47 73.5, 85.31 72.54, 90.26 67.19 M87.62 75.77 C88.24 74.75, 89.38 74.21, 90.02 73.4 M87.74 76.04 C88.59 75, 89.18 73.93, 90.15 73.18" stroke="#ced4da" stroke-width="0.5" fill="none"&gt;&lt;/path&gt;&lt;path d="M-1.31 -0.03 C24.03 0.68, 43.99 0.01, 88.95 1.36 M0.61 -0.01 C25.96 -0.14, 51.21 0.05, 89.85 0.89 M88.13 1.63 C90.63 28.24, 90.21 54.85, 88.08 75.5 M90.41 -0.63 C89.35 27.5, 90.05 57.11, 89.28 74.44 M90.6 75.16 C64.06 75.08, 42.46 73.23, 1.4 74.77 M89.01 73.4 C71.52 73.14, 53.16 72.32, -0.09 73.56 M-1.02 73.91 C-1.67 50.26, -1.99 28.36, 1.11 0.09 M0.2 72.83 C-0.1 53.59, -0.28 34.48, -0.67 -0.14" stroke="transparent" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(178.86227796995763 353.1462553796259) rotate(0 25.5 21)"&gt;&lt;text x="25.5" y="15"  font-size="16px" fill="currentColor" text-anchor="middle" &gt;Credit&lt;/text&gt;&lt;text x="25.5" y="36"  font-size="16px" fill="currentColor" text-anchor="middle" &gt;Card&lt;/text&gt;&lt;/g&gt;&lt;g transform="translate(96.40773251541259 87.55534628871692) rotate(0 32.5 21)"&gt;&lt;text x="32.5" y="15"  font-size="16px" fill="currentColor" text-anchor="middle" &gt;Create &lt;/text&gt;&lt;text x="32.5" y="36"  font-size="16px" fill="currentColor" text-anchor="middle" &gt;Payout&lt;/text&gt;&lt;/g&gt;&lt;g stroke-linecap="round"&gt;&lt;g transform="translate(138.77534939296447 240.6538895766489) rotate(0 31.017570713873738 44.91082421038297)"&gt;&lt;path d="M1.39 -2.61 C14.41 18.53, 31.46 41.37, 59.85 90.86 M-0.54 0.65 C21.95 32.1, 46.02 66.6, 62.58 92.43" stroke="#495057" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(138.77534939296447 240.6538895766489) rotate(0 31.017570713873738 44.91082421038297)"&gt;&lt;path d="M39.88 71.95 C44.19 76.35, 52.51 81.93, 61.15 91.87 M37.94 75.21 C47.02 80.49, 57.67 88.43, 63.88 93.45" stroke="#495057" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(138.77534939296447 240.6538895766489) rotate(0 31.017570713873738 44.91082421038297)"&gt;&lt;path d="M57 60.64 C57.29 67.93, 61.59 76.16, 61.15 91.87 M55.07 63.9 C57.96 73.44, 62.42 85.46, 63.88 93.45" stroke="#495057" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g stroke-linecap="round"&gt;&lt;g transform="translate(138.44709774604826 237.2089397814957) rotate(0 -26.522568929671934 47.107910371698466)"&gt;&lt;path d="M-1.31 0.03 C-16.25 29.35, -31.42 55.07, -53.37 94.29 M0.32 -0.08 C-15.51 28.53, -30.62 56.07, -52.58 92.14" stroke="#495057" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(138.44709774604826 237.2089397814957) rotate(0 -26.522568929671934 47.107910371698466)"&gt;&lt;path d="M-48.27 62.7 C-49.48 72.85, -50.46 79.58, -52.86 93.36 M-46.64 62.59 C-48.63 72.35, -49.57 80.9, -52.08 91.21" stroke="#495057" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(138.44709774604826 237.2089397814957) rotate(0 -26.522568929671934 47.107910371698466)"&gt;&lt;path d="M-30.65 73.2 C-37.19 80.28, -43.44 83.87, -52.86 93.36 M-29.01 73.1 C-36.32 79.67, -42.52 85.09, -52.08 91.21" stroke="#495057" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g transform="translate(125.40773251541259 274.9644371978077) rotate(0 13.5 10.5)"&gt;&lt;text x="13.5" y="15"  font-size="16px" fill="currentColor" text-anchor="middle" &gt;Pay&lt;/text&gt;&lt;/g&gt;&lt;g stroke-linecap="round"&gt;&lt;g transform="translate(180.77641938409852 203.28817523967632) rotate(0 28.132230309098304 -64.22513856614634)"&gt;&lt;path d="M-0.03 1.03 C9.03 -4.06, 47.42 -7.62, 54.58 -29.37 C61.74 -51.12, 44.79 -113.15, 42.93 -129.48" stroke="#f41d92" stroke-width="1.5" fill="none" stroke-dasharray="8 9"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(180.77641938409852 203.28817523967632) rotate(0 28.132230309098304 -64.22513856614634)"&gt;&lt;path d="M59.98 -104.96 C54.15 -111.89, 46.28 -119.29, 44.65 -129.21" stroke="#f41d92" stroke-width="1.5" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(180.77641938409852 203.28817523967632) rotate(0 28.132230309098304 -64.22513856614634)"&gt;&lt;path d="M39.82 -101.14 C40.42 -109.28, 39.01 -117.92, 44.65 -129.21" stroke="#f41d92" stroke-width="1.5" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g transform="translate(234.13063826463303 182.3951092413472) rotate(0 27 21)"&gt;&lt;text x="27" y="15"  font-size="16px" fill="#f41d92" text-anchor="middle" &gt;Payout&lt;/text&gt;&lt;text x="27" y="36"  font-size="16px" fill="#f41d92" text-anchor="middle" &gt;Paid&lt;/text&gt;&lt;/g&gt;&lt;g stroke-linecap="round" transform="translate(366.4263776130042 20.2779413215477) rotate(0 7.5 7.142857142857167)"&gt;&lt;path d="M11.44 0.9 C12.84 1.38, 14.02 3.41, 14.57 4.76 C15.12 6.12, 15.13 7.83, 14.74 9.05 C14.35 10.26, 13.6 11.22, 12.24 12.05 C10.88 12.87, 8.28 13.88, 6.58 13.99 C4.87 14.11, 2.99 13.93, 1.99 12.74 C1 11.55, 0.68 8.31, 0.61 6.85 C0.54 5.39, 0.89 5, 1.58 3.98 C2.28 2.97, 3.1 1.3, 4.79 0.74 C6.48 0.18, 10.59 0.69, 11.74 0.63 C12.88 0.57, 11.7 0.18, 11.65 0.38 M6.55 1.14 C7.81 1.09, 10.44 -0.42, 12.07 0.56 C13.7 1.55, 15.83 5.49, 16.34 7.06 C16.84 8.64, 16.35 8.87, 15.1 10 C13.85 11.14, 10.72 13.44, 8.84 13.87 C6.96 14.31, 5.23 13, 3.84 12.62 C2.45 12.23, 1.23 12.55, 0.5 11.58 C-0.24 10.6, -0.79 8.29, -0.56 6.77 C-0.34 5.26, 0.5 3.37, 1.87 2.48 C3.23 1.58, 6.91 1.9, 7.63 1.43 C8.36 0.95, 6.2 -0.29, 6.21 -0.37" stroke="none" stroke-width="0" fill="#f41d92"&gt;&lt;/path&gt;&lt;path d="M8.22 0.17 C9.57 -0.01, 10.75 0.9, 11.92 1.98 C13.1 3.06, 14.88 5.03, 15.29 6.63 C15.71 8.23, 15.4 10.21, 14.41 11.56 C13.42 12.9, 11.05 14.23, 9.34 14.7 C7.63 15.18, 5.66 15.04, 4.14 14.38 C2.61 13.72, 1 12.04, 0.18 10.77 C-0.64 9.49, -1.21 8.2, -0.78 6.75 C-0.35 5.29, 1.1 2.99, 2.74 2.04 C4.37 1.08, 7.87 1.18, 9.01 1.02 C10.16 0.87, 9.66 1.16, 9.59 1.09 M8.84 1.6 C10.12 1.55, 11.6 0.58, 12.43 1.35 C13.25 2.11, 13.59 4.71, 13.81 6.21 C14.02 7.7, 14.76 8.8, 13.71 10.32 C12.67 11.85, 9.13 14.78, 7.55 15.35 C5.97 15.93, 5.58 14.55, 4.21 13.75 C2.83 12.96, 0.12 11.94, -0.7 10.57 C-1.53 9.2, -1.5 7.05, -0.73 5.54 C0.04 4.03, 2.52 2.32, 3.92 1.51 C5.31 0.7, 6.8 0.65, 7.63 0.68 C8.46 0.71, 8.91 1.45, 8.89 1.7" stroke="#f41d92" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g stroke-linecap="round"&gt;&lt;g transform="translate(373.56923475586154 33.84936989297648) rotate(0 0.224559781788912 14.131781067302086)"&gt;&lt;path d="M0.44 0.38 C0.44 5.02, -0.66 24.02, -0.9 28.73 M-0.79 -0.46 C-0.33 3.76, 0.98 22.54, 1.35 27.06" stroke="#f41d92" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g stroke-linecap="round"&gt;&lt;g transform="translate(374.0412538346477 57.108725622177985) rotate(0 -3.357253714589433 6.988165282189527)"&gt;&lt;path d="M0.87 -0.98 C-0.37 1.4, -5.67 11.17, -7.03 13.39 M-0.13 1.12 C-1.5 3.79, -6.3 12.96, -7.58 14.96" stroke="#f41d92" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g stroke-linecap="round"&gt;&lt;g transform="translate(381.04125383464816 57.77539228884473) rotate(293.5804257326911 -3.3068900323789876 7.172603662639517)"&gt;&lt;path d="M0.59 1.15 C-0.43 3.25, -5.45 11.47, -6.78 13.64 M-0.56 0.7 C-1.59 2.95, -5.97 9.75, -7.21 11.68" stroke="#f41d92" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g stroke-linecap="round"&gt;&lt;g transform="translate(360.9301427235364 42.66428117773353) rotate(0 14.200112145559615 -1.6809096463469473)"&gt;&lt;path d="M-0.79 -0.96 C4.22 -1.42, 24.23 -2.49, 29.19 -2.79 M0.99 1.15 C5.89 0.35, 23.56 -3.54, 28.06 -4.51" stroke="#f41d92" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g stroke-linecap="round"&gt;&lt;g transform="translate(249.31903161242553 45.0253922888445) rotate(0 52.810719070708046 -1.5714045324673407)"&gt;&lt;path d="M0 0.45 C17.57 0, 88.02 -3.25, 105.62 -3.6" stroke="#f41d92" stroke-width="1.5" fill="none" stroke-dasharray="8 9"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(249.31903161242553 45.0253922888445) rotate(0 52.810719070708046 -1.5714045324673407)"&gt;&lt;path d="M78.56 5.93 C85.23 2.35, 97.27 1.26, 106.69 -3.06" stroke="#f41d92" stroke-width="1.5" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(249.31903161242553 45.0253922888445) rotate(0 52.810719070708046 -1.5714045324673407)"&gt;&lt;path d="M77.85 -14.58 C84.59 -11.71, 96.86 -6.35, 106.69 -3.06" stroke="#f41d92" stroke-width="1.5" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g transform="translate(252.98569827909205 54.5253922888445) rotate(0 45 10.5)"&gt;&lt;text x="45" y="15"  font-size="16px" fill="#f41d92" text-anchor="middle" &gt;Notification&lt;/text&gt;&lt;/g&gt;&lt;/svg&gt;
&lt;figcaption&gt;Top level app gets notified when the payout is paid&lt;/figcaption&gt;&lt;/p&gt;
&lt;/figure&gt;
&lt;p&gt;One way to avoid circular dependencies and keep modules decoupled in Django is to use signals:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# payouts/signals.py&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.dispatch&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Signal&lt;/span&gt;

&lt;span class="n"&gt;payout_paid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Signal&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;After declaring the signal we can send it when a payout is paid. This is done by the model:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;.&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;signals&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PayoutProcess&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="nd"&gt;@classmethod&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;mark_paid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;PayoutProcess&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;db_transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;atomic&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="n"&gt;payout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;select_for_update&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pk&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;pk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;payout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;pending&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;StateError&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;payout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;paid&amp;#39;&lt;/span&gt;
            &lt;span class="n"&gt;payout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="hll"&gt;        &lt;span class="n"&gt;signals&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;payout_paid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send_robust&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;PayoutProcess&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;payout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;payout&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Now a top level app can listen to this signal, and send a notification to the user:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.dispatch&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;receiver&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;payout.signals&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;payout.models&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;MerchantPayoutProcess&lt;/span&gt;

&lt;span class="hll"&gt;&lt;span class="nd"&gt;@receiver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;signals&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;payout_paid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;on_merchant_was_paid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;payout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PayoutProcess&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MerchantPayoutProcess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payout_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;payout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;MerchantPayoutProcess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DoesNotExist&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Not a merchant payout&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;

    &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Dear merchant, you got paid &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;payout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;$!&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;When the signal receiver is triggered, it first checks to see if it's one of its own payouts. If it is, it fetches the related object, in this case a payout to a merchant, and sends a notification to the user.&lt;/p&gt;
&lt;div class="admonition info"&gt;
&lt;p class="admonition-title"&gt;N receivers&lt;/p&gt;
&lt;p&gt;With this scheme, if you have N receivers, then every dispatch causes N-1 useless queries. This can be avoided by adding a bit of context to the signal.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonition info"&gt;
&lt;p class="admonition-title"&gt;dispatch_uid&lt;/p&gt;
&lt;p&gt;It's usually a good idea to set &lt;code&gt;dispatch_uid&lt;/code&gt; on signal receivers. The &lt;a href="https://docs.djangoproject.com/en/3.2/topics/signals/#preventing-duplicate-signals" rel="noopener"&gt;documentation&lt;/a&gt; explains it well.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;The benefit of using signals this way is that the low level payout module can communicate with apps that depend on it, without forming a dependency back on them. This pattern eliminates the circular dependency and keeps low level modules independent and decoupled.&lt;/p&gt;
&lt;h3 id="working-in-bulk"&gt;&lt;a class="toclink" href="#working-in-bulk"&gt;Working in Bulk&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;This design was working pretty well - payouts were flying out and the users were happy.&lt;/p&gt;
&lt;p&gt;At some point the staffers came back with another idea. They said that work is picking up and they now want to automate and streamline some of it. They asked if it was possible to mark payouts as paid in bulk. After a quick discussion we decided it's best to have the bulk process "all or nothing", meaning, if the operation fails for even one of the payouts in the bulk, it should not be applied to any of them.&lt;/p&gt;
&lt;p&gt;We figured this would be a straightforward task, all we have to do is execute the command on all of the given payouts inside of a database transaction:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;db_transaction&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;mark_paid_in_bulk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payout_ids&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="hll"&gt;    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;db_transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;atomic&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;pk&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;payout_ids&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;PayoutProcess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mark_paid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The bulk process simply iterates over payout IDs and marks each one as paid. To make sure the process is atomic, or "all or nothing", we wrap the loop in a database transaction.&lt;/p&gt;
&lt;p&gt;Easy, right? This is where it gets hairy.&lt;/p&gt;
&lt;h3 id="the-bug"&gt;&lt;a class="toclink" href="#the-bug"&gt;The Bug&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;This bulk process was also working great for a while. Staffers would upload Excel files (what else), and the system would go over the payouts and mark them all as paid.&lt;/p&gt;
&lt;p&gt;One day, the person that usually does this was on holiday, and asked someone else to do it instead. The other person prepared the Excel file and uploaded it to the system. This new person was not familiar with the process so they made some mistakes with the amounts. As a result, the system rejected some of the payouts.&lt;/p&gt;
&lt;p&gt;Now what does a normal person do when a system reports an error? They try again and again...&lt;/p&gt;
&lt;p&gt;At some point we started getting complaints from users saying they are getting a ton of messages that they got paid. Some were happy, but others opened the app to check the details and saw that they were in fact not paid, and realized it must be a mistake.&lt;/p&gt;
&lt;p&gt;At this point hundreds of users got these messages, but none of them did get paid! So what's causing the issue? How are notifications being sent when all of the payouts are still marked as pending? A closer look at the implementation of the bulk process revealed the problem.&lt;/p&gt;
&lt;h3 id="nested-transactions"&gt;&lt;a class="toclink" href="#nested-transactions"&gt;Nested Transactions&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The function that marks a payout as paid is executed inside of a database transaction. To make sure that the signal is sent only when the payout status is committed to the database, the signal is sent &lt;em&gt;after&lt;/em&gt; the transaction is completed:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nd"&gt;@classmethod&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;mark_paid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;PayoutProcess&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="hll"&gt;    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;db_transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;atomic&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;        &lt;span class="n"&gt;payout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;select_for_update&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pk&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;pk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;payout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;pending&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;StateError&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;payout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;paid&amp;#39;&lt;/span&gt;
        &lt;span class="n"&gt;payout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="hll"&gt;    &lt;span class="n"&gt;signals&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;payout_paid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send_robust&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;PayoutProcess&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;payout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;payout&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;When this function is executed on a single payout it works as expected. But what happens if we add the bulk process:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;db_transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;atomic&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;pk&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;payout_ids&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

        &lt;span class="c1"&gt;# inline `mark_paid()`&lt;/span&gt;
&lt;span class="hll"&gt;        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;db_transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;atomic&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;            &lt;span class="n"&gt;payout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;select_for_update&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pk&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;pk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;payout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;pending&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;StateError&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;payout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;paid&amp;#39;&lt;/span&gt;
            &lt;span class="n"&gt;payout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="hll"&gt;        &lt;span class="n"&gt;signals&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;payout_paid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send_robust&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;PayoutProcess&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;payout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Ha! The bulk process is using its own database transaction! When the signal is sent the payout can still be rolled back if a later payout in the bulk fails.&lt;/p&gt;
&lt;p&gt;Just to illustrate, if we mark three payouts in bulk and we fail to mark the third one, all three payouts are rolled back, but notifications for the first two had already been sent:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;db_transaction&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;db_transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;atomic&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;span class="gp"&gt;... &lt;/span&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;fail&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
&lt;span class="gp"&gt;... &lt;/span&gt;        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;db_transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;atomic&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;span class="gp"&gt;... &lt;/span&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="gp"&gt;... &lt;/span&gt;                &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="ne"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Failed!&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;... &lt;/span&gt;        &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Message sent!&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;...&lt;/span&gt;
&lt;span class="go"&gt;Message sent!&lt;/span&gt;
&lt;span class="go"&gt;Message sent!&lt;/span&gt;
&lt;span class="go"&gt;Exception: Failed!&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Notice how the first two messages were sent even though the third failure caused the outer transaction to rollback all three.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="remedies"&gt;&lt;a class="toclink" href="#remedies"&gt;Remedies&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The single &lt;code&gt;mark_paid&lt;/code&gt; function assumes that it is not executed inside of a database transaction, but it is not checking or enforcing it in any way. This is a problem.&lt;/p&gt;
&lt;h3 id="assert-atomic-block"&gt;&lt;a class="toclink" href="#assert-atomic-block"&gt;Assert Atomic Block&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Before Django 3.2 we had some cases where we wanted to make sure a function is executed, or not executed, inside a database transaction. We ended up implementing two functions:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# common/db.py&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;assert_is_in_atomic_block&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;in_atomic_block&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;This function must be run inside of a DB transaction.&amp;#39;&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;assert_is_not_in_atomic_block&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;in_atomic_block&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;This function must not be run inside of a DB transaction.&amp;#39;&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Using these utility functions we could prevent some code from being executed inside a database transaction:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;common.db&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;do_not_run_inside_a_db_transaction&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;span class="hll"&gt;    &lt;span class="n"&gt;common&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assert_is_not_in_atomic_block&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;    &lt;span class="c1"&gt;# Rest of function goes here...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Running this code block inside of an atomic block will now trigger an assertion error at runtime:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;db_transaction&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;db_transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;atomic&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;span class="o"&gt;...&lt;/span&gt;     &lt;span class="n"&gt;do_not_run_inside_a_db_transaction&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="ne"&gt;AssertionError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;This&lt;/span&gt; &lt;span class="n"&gt;function&lt;/span&gt; &lt;span class="n"&gt;must&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;be&lt;/span&gt; &lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="n"&gt;inside&lt;/span&gt; &lt;span class="n"&gt;of&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;DB&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The main downside to this approach is that unless explicitly stated otherwise, tests will run inside a database transaction. This will cause any test which uses a transaction to fail. To overcome that we ended up patching these functions in tests:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nd"&gt;@pytest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fixture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scope&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;session&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;autouse&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&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;patch_is_in_db_transaction&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="c1"&gt;# Patch atomic transaction check in tests.&lt;/span&gt;
    &lt;span class="c1"&gt;# The checks can&amp;#39;t be run in tests because tests are always wrapped in a transaction.&lt;/span&gt;
    &lt;span class="n"&gt;patch_in&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;common.db.assert_is_in_atomic_block&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;patch_not_in&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;common.db.assert_is_not_in_atomic_block&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;patch_in&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;patch_not_in&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This function creates a fixture which is automatically applied for the entire test session. The fixture mocks the two functions and disables their functionality.&lt;/p&gt;
&lt;h3 id="durable-transaction"&gt;&lt;a class="toclink" href="#durable-transaction"&gt;Durable Transaction&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Starting with Django 3.2, there is another way to prevent a transaction from being executed inside of another transaction, by marking a transaction as &lt;a href="https://docs.djangoproject.com/en/3.2/topics/db/transactions/#controlling-transactions-explicitly" rel="noopener"&gt;"durable"&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;db_transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;atomic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;durable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;    &lt;span class="n"&gt;payout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;select_for_update&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pk&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;pk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;payout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;pending&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;StateError&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;payout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;paid&amp;#39;&lt;/span&gt;
    &lt;span class="n"&gt;payout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;signals&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;payout_paid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send_robust&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;PayoutProcess&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;payout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;If you try to open a durable transaction inside of another transaction, a &lt;code&gt;RuntimeError&lt;/code&gt; is raised:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;db_transaction&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;db_transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;atomic&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="gp"&gt;... &lt;/span&gt;    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;db_transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;atomic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;durable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;span class="gp"&gt;... &lt;/span&gt;            &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;span class="gp"&gt;...&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="go"&gt;RuntimeError: A durable atomic block cannot be nested within another atomic block.&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Using a durable transaction may have prevented this issue from happening, but it would have also made the bulk feature impossible, or at least very complicated to implement!&lt;/p&gt;
&lt;h3 id="sending-signal-on-commit"&gt;&lt;a class="toclink" href="#sending-signal-on-commit"&gt;Sending Signal on Commit&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Another way to tackle this issue is to instead try to make sure the signal is only sent when the overall transaction is successfully committed. One way of doing this is using &lt;a href="https://docs.djangoproject.com/en/3.2/topics/db/transactions/#django.db.transaction.on_commit" rel="noopener"&gt;&lt;code&gt;on_commit&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Using &lt;code&gt;on_commit&lt;/code&gt; we can register a function to be executed only when the transaction is actually committed. To illustrate how using &lt;code&gt;on_commit&lt;/code&gt; can solve the issue, consider the following example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;db_transaction&lt;/span&gt;
&lt;span class="gp"&gt;... &lt;/span&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;db_transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;atomic&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;span class="gp"&gt;... &lt;/span&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fail&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="gp"&gt;... &lt;/span&gt;        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;db_transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;atomic&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;span class="gp"&gt;... &lt;/span&gt;            &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;processing &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;...&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;... &lt;/span&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="gp"&gt;... &lt;/span&gt;                &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="ne"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Failed!&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="gp"&gt;... &lt;/span&gt;            &lt;span class="n"&gt;db_transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;on_commit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Message sent!&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;span class="go"&gt;processing 1...&lt;/span&gt;
&lt;span class="go"&gt;processing 2...&lt;/span&gt;
&lt;span class="go"&gt;processing 3...&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="go"&gt;Exception: Failed!&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;In the example we loop over three values where the third one is expected to fail. To print the message only when the transaction is committed successfully we use &lt;code&gt;on_commit&lt;/code&gt;. Notice in the output that three items were processed, but since the third one failed, the entire process failed and none of the messages were sent.&lt;/p&gt;
&lt;p&gt;To illustrate what happens when all items succeed, consider the following example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;db_transaction&lt;/span&gt;
&lt;span class="gp"&gt;... &lt;/span&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;db_transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;atomic&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;span class="gp"&gt;... &lt;/span&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fail&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="gp"&gt;... &lt;/span&gt;        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;db_transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;atomic&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;span class="gp"&gt;... &lt;/span&gt;            &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;processing &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;...&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;... &lt;/span&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;fail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="gp"&gt;... &lt;/span&gt;                &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="ne"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Failed!&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;... &lt;/span&gt;            &lt;span class="n"&gt;db_transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;on_commit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Message sent!&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="go"&gt;processing 1...&lt;/span&gt;
&lt;span class="go"&gt;processing 2...&lt;/span&gt;
&lt;span class="go"&gt;processing 3...&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="go"&gt;Message sent!&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="go"&gt;Message sent!&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="go"&gt;Message sent!&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Amazing! Three items were processed and three messages were sent. We can now apply a similar fix to our payout module:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;.&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;signals&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PayoutProcess&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="nd"&gt;@classmethod&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;mark_paid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;PayoutProcess&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;db_transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;atomic&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="n"&gt;payout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;select_for_update&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pk&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;pk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;payout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;pending&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;StateError&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;payout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;paid&amp;#39;&lt;/span&gt;
            &lt;span class="n"&gt;payout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="hll"&gt;        &lt;span class="n"&gt;db_transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;on_commit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;signals&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;payout_paid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send_robust&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PayoutProcess&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payout&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;payout&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;When a payout is marked as paid the function now sends the signal only when the transaction is committed. This makes the function safe to execute inside of another transaction!&lt;/p&gt;
&lt;h3 id="using-a-queue"&gt;&lt;a class="toclink" href="#using-a-queue"&gt;Using a Queue&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;When dealing with such problems it's not uncommon to immediately think about queues. As a thought exercise, let's examine two common patterns often referred to as "queues".&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Async Tasks&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Async task runners such as &lt;a href="https://celeryproject.org/" rel="noopener"&gt;Celery&lt;/a&gt; are very popular. They let you execute tasks asynchronously, now, at a later time or at a predetermined time. Using async tasks in this case would not solve the issue:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Fire an async task in &lt;code&gt;on_commit&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;If we set aside the fact that the payout module is not the one sending the messages, the outcome in this case is exactly the same as sending a signal in &lt;code&gt;on_commit&lt;/code&gt; and firing an async task from the receiver (which is what we do).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Fire an async task instead of sending a signal&lt;/strong&gt;&lt;br&gt;This will suffer from the same issues as the signal. If the bulk process fails, the task was already fired and the message will be sent.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Schedule an async task for a later time and check the status before sending&lt;/strong&gt;&lt;br&gt;This may work in some cases, but there are other problems:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;We have a race&lt;/strong&gt;: How long after the payout was processed should the task be executed? 1s? 10s? 1m? What if the bulk process takes 2m to run? When the task will be fired the transaction will not be commited yet and the message will not be sent. What do you do then?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;We do extra work&lt;/strong&gt;: You now have to fetch the payout &lt;em&gt;again&lt;/em&gt; before sending the message.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;We send the message later&lt;/strong&gt;: If we wait, users can receive the message a few minutes or even hours after they were paid. This may not be a big deal in some cases, but in others sending the message close to when the event occurred may be crucial.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Another downside to using an async task runner, is that now you need to have an async task runner. If you already have one it may no be so bad, but if you don't, it can be a pain to set up and operate.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Transactional Queue&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If you decided to implement a queue in the database you are likely one step closer to a proper solution. Instead of using signals, you can stage tasks to a database table that acts as a queue.&lt;/p&gt;
&lt;p&gt;The main benefit of using a queue table in the database is that the task will be added only when the transaction is committed. This plays very nicely with the overall transaction management of the process, and guarantees that the task is only added when it should.&lt;/p&gt;
&lt;p&gt;The challenging part is how to make sure the tasks are being picked up shortly after they were added to the queue. If you are using a cron job to process tasks, the sending may be delayed up to the cron job's repeat interval. If you use database triggers, LISTEN/NOTIFY or similar to trigger processing of tasks then the delay can be shorter.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="testing"&gt;&lt;a class="toclink" href="#testing"&gt;Testing&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We ended up implementing the &lt;code&gt;on_commit&lt;/code&gt; solution because it required very little changes to existing code. However, after we finished making the changes to the code, we faced yet another challenge - the tests!&lt;/p&gt;
&lt;h3 id="testing-with-django"&gt;&lt;a class="toclink" href="#testing-with-django"&gt;Testing with Django&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Our tests included scenarios to make sure a notification is sent when a payout is paid:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_should_send_notification&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mailoutbox&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;merchant_user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;comm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MerchantCommission&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_payout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;merchant_user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100_00&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;PayoutProcess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mark_paid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;comm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;payout_process_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mailoutbox&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;After we made the change to send the signal in &lt;code&gt;on_commit&lt;/code&gt;, all of these tests failed. After some debugging we found that the receiver function we registered for the signals was not executed, but only in the tests!&lt;/p&gt;
&lt;p&gt;The fact that the &lt;code&gt;on_commit&lt;/code&gt; handler is not fired is not surprising if you know how tests are executed. To speed things up, Django starts a database transaction at the beginning of every test and then rolls it back immediately after. Executing tests in this manner is a fast way to prevent tests that change data in the database from affecting each other.&lt;/p&gt;
&lt;p&gt;To make it possible to test things that are triggered in &lt;code&gt;on_commit&lt;/code&gt; without using slow transactional tests, Django 3.2 added a new context manager called &lt;a href="https://docs.djangoproject.com/en/3.2/topics/testing/tools/#django.test.TestCase.captureOnCommitCallbacks" rel="noopener"&gt;&lt;code&gt;captureOnCommitCallbacks&lt;/code&gt;&lt;/a&gt; (&lt;a href="https://code.djangoproject.com/ticket/30457" rel="noopener"&gt;Ticket #30457&lt;/a&gt;):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.core&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;mail&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.test&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TestCase&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TestPayoutProcess&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_should_send_notification&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;merchant_user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;comm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MerchantCommission&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_payout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;merchant_user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100_00&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="hll"&gt;        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;captureOnCommitCallbacks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;            &lt;span class="n"&gt;PayoutProcess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mark_paid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;comm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;payout_process_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mail&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;outbox&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The context manager is available on instances of &lt;code&gt;TestCase&lt;/code&gt;, and when &lt;code&gt;execute=True&lt;/code&gt; any &lt;code&gt;on_commit&lt;/code&gt; handlers will also be executed, not just captured.&lt;/p&gt;
&lt;h3 id="testing-with-pytest"&gt;&lt;a class="toclink" href="#testing-with-pytest"&gt;Testing with Pytest&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Unfortunately we are not using Django's &lt;code&gt;TestCase&lt;/code&gt; directly anymore, we are using pytest, and we were not in a position to start rewriting stuff. Lucky for us, &lt;code&gt;pytest-django&lt;/code&gt; implemented equivalent functionality. A quick upgrade to &lt;a href="https://pytest-django.readthedocs.io/en/latest/changelog.html#v4-4-0-2021-06-06" rel="noopener"&gt;&lt;code&gt;pytest-django&lt;/code&gt; version 4.4&lt;/a&gt; and we were ready to go:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_should_send_notification&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;mailoutbox&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="hll"&gt;    &lt;span class="n"&gt;django_capture_on_commit_callbacks&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;    &lt;span class="n"&gt;merchant_user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;comm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MerchantCommission&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_payout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;merchant_user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100_00&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="hll"&gt;    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;django_capture_on_commit_callbacks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;        &lt;span class="n"&gt;PayoutProcess&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mark_paid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;comm&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;payout_process_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mailoutbox&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The fixture &lt;a href="https://pytest-django.readthedocs.io/en/stable/helpers.html#django-capture-on-commit-callbacks" rel="noopener"&gt;&lt;code&gt;django_capture_on_commit_callbacks&lt;/code&gt;&lt;/a&gt; is based on the Django function. Once you inject it you can use it just like you would the Django one.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;The "bug" caused by this nested transaction ended up causing some users to get multiple messages saying they got paid, but all of these users were eventually paid.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="thoughts-on-django-signals"&gt;&lt;a class="toclink" href="#thoughts-on-django-signals"&gt;Thoughts on Django Signals&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;As described in this story, Django signals are useful for implementing interactions between modules without creating explicit dependencies between them. The &lt;a href="https://docs.djangoproject.com/en/3.2/topics/signals/" rel="noopener"&gt;official documentation about Signals&lt;/a&gt; also provide this as the main reason for using signals:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Django includes a “signal dispatcher” which allows decoupled applications get notified when actions occur elsewhere in the framework. In a nutshell, signals allow certain senders to notify a set of receivers that some action has taken place. They’re especially useful when many pieces of code may be interested in the same events.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If you look at &lt;a href="https://github.com/django/django/blob/main/django/dispatch/dispatcher.py" rel="noopener"&gt;how signals are implemented in Django&lt;/a&gt; you'll find that there is not a lot of magic under the hood. The function &lt;code&gt;connect&lt;/code&gt; adds a function to a list of receivers, and when a signal is &lt;code&gt;send&lt;/code&gt; (or &lt;code&gt;send_robust&lt;/code&gt;) the signal object iterates over the list of receiver functions, and executes them one by one.&lt;/p&gt;
&lt;p&gt;This is very similar to a pub-sub pattern, but it lacks some of the guarantees of more advanced implementations. One of the main disadvantages of Django signals is that &lt;strong&gt;there is no guarantee that a "message" ever reaches the destination&lt;/strong&gt;. If for example, the server crashes while a signal is being broadcast, some receivers may not be executed and they will not be attempted when the service starts up again. This can become a problem if you rely on signals exclusively to trigger certain actions in the system.&lt;/p&gt;</content><category term="articles"></category><category term="Django"></category><category term="ORM"></category></entry><entry><title>Exciting New Features in Django 3.2</title><link href="https://hakibenita.com/django-32-exciting-features" rel="alternate"></link><published>2021-03-03T00:00:00+02:00</published><updated>2021-03-03T00:00:00+02:00</updated><author><name>Haki Benita</name></author><id>tag:hakibenita.com,2021-03-03:/django-32-exciting-features</id><summary type="html">&lt;p&gt;Django 3.2 is just around the corner and it's packed with new features. Django versions are usually not that exciting (it's a good thing!), but this time many features were added to the ORM, so I find it especially interesting!&lt;/p&gt;</summary><content type="html">&lt;hr&gt;
&lt;p&gt;Django 3.2 is just around the corner and it's &lt;a href="https://docs.djangoproject.com/en/dev/releases/3.2/" rel="noopener"&gt;packed with new features&lt;/a&gt;. Django versions are usually not that exciting (it's a good thing!), but this time many features were added to the ORM, so I find it especially interesting!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;This is a list of my favorite features in Django 3.2&lt;/strong&gt;&lt;/p&gt;
&lt;figure&gt;
&lt;p&gt;&lt;svg viewBox="0 0 508 268" aria-hidden="true" width="20em" height="auto"&gt;
    &lt;path d="M305.2 156.6c0 4.6-.5 9-1.6 13.2-2.5-4.4-5.6-8.4-9.2-12-4.6-4.6-10-8.4-16-11.2 2.8-11.2 4.5-22.9 5-34.6 1.8 1.4 3.5 2.9 5 4.5 10.5 10.3 16.8 24.5 16.8 40.1zm-75-10c-6 2.8-11.4 6.6-16 11.2-3.5 3.6-6.6 7.6-9.1 12-1-4.3-1.6-8.7-1.6-13.2 0-15.7 6.3-29.9 16.6-40.1 1.6-1.6 3.3-3.1 5.1-4.5.6 11.8 2.2 23.4 5 34.6z" fill="#2E3B39" fill-rule="nonzero"&gt;&lt;/path&gt;
    &lt;path d="M282.981 152.6c16.125-48.1 6.375-104-29.25-142.6-35.625 38.5-45.25 94.5-29.25 142.6h58.5z" stroke="var(--bg-color)" stroke-width="3.396" fill="#6DDCBD"&gt;&lt;/path&gt;
    &lt;path d="M271 29.7c-4.4-10.6-9.9-20.6-16.6-29.7-6.7 9-12.2 19-16.6 29.7H271z" stroke="var(--bg-color)" stroke-width="3" fill="#2E3B39"&gt;&lt;/path&gt;
    &lt;circle fill="#FFF" cx="254.3" cy="76.8" r="15.5"&gt;&lt;/circle&gt;
    &lt;circle stroke="#FFF" stroke-width="7" fill="#6DDCBD" cx="254.3" cy="76.8" r="12.2"&gt;&lt;/circle&gt;
    &lt;path class="smoke" d="M507.812 234.24c0-2.16-.632-4.32-1.58-6.24-3.318-6.72-11.85-11.52-21.804-11.52-1.106 0-2.212.12-3.318.24-.474-11.52-12.956-20.76-28.282-20.76-3.318 0-6.636.48-9.638 1.32-4.74-6.72-14.062-11.28-24.806-11.28-.79 0-1.58 0-2.37.12-.79 0-1.58-.12-2.37-.12-10.744 0-20.066 4.56-24.806 11.28a35.326 35.326 0 00-9.638-1.32c-15.642 0-28.282 9.6-28.282 21.48 0 1.32.158 2.76.474 3.96a26.09 26.09 0 00-4.424-.36c-8.058 0-15.01 3.12-19.118 7.8-3.476-1.68-7.742-2.76-12.324-2.76-12.008 0-21.804 7.08-22.752 15.96h-.158c-9.322 0-17.38 4.32-20.856 10.44-4.108-3.6-10.27-6-17.222-6h-1.264c-6.794 0-12.956 2.28-17.222 6-3.476-6.12-11.534-10.44-20.856-10.44h-.158c-.948-9-10.744-15.96-22.752-15.96-4.582 0-8.69.96-12.324 2.76-4.108-4.68-11.06-7.8-19.118-7.8-1.422 0-3.002.12-4.424.36.316-1.32.474-2.64.474-3.96 0-11.88-12.64-21.48-28.282-21.48-3.318 0-6.636.48-9.638 1.32-4.74-6.72-14.062-11.28-24.806-11.28-.79 0-1.58 0-2.37.12-.79 0-1.58-.12-2.37-.12-10.744 0-20.066 4.56-24.806 11.28a35.326 35.326 0 00-9.638-1.32c-15.326 0-27.808 9.24-28.282 20.76-1.106-.12-2.212-.24-3.318-.24-9.954 0-18.486 4.8-21.804 11.52-.948 1.92-1.58 4.08-1.58 6.24 0 4.8 2.528 9.12 6.636 12.36-.79 1.44-1.264 3.12-1.264 4.8 0 7.2 7.742 13.08 17.222 13.08h462.15c9.48 0 17.222-5.88 17.222-13.08 0-1.68-.474-3.36-1.264-4.8 4.582-3.24 7.11-7.56 7.11-12.36z" fill="#E6E9EE"&gt;&lt;/path&gt;
    &lt;path fill="#6DDCBD" d="M239 152h30v8h-30z"&gt;&lt;/path&gt;
    &lt;path class="exhaust__line" fill="#E6E9EE" d="M250 172h7v90h-7z"&gt;&lt;/path&gt;
    &lt;path class="flame" d="M250.27 178.834l-5.32-8.93s-2.47-5.7 3.458-6.118h10.26s6.232.266 3.306 6.194l-5.244 8.93s-3.23 4.37-6.46 0v-.076z" fill="#AA2247"&gt;&lt;/path&gt;
&lt;/svg&gt;
&lt;figcaption&gt;Image from the Django welcome page&lt;/figcaption&gt;&lt;/p&gt;
&lt;/figure&gt;
&lt;p&gt;A lot of great people worked on this release and none of them is me. I included links to the tickets of each new feature to show my appreciation to the people behind it.&lt;/p&gt;
&lt;p&gt;&lt;details class="toc-container" open&gt;
    &lt;summary&gt;Table of Contents&lt;/summary&gt;&lt;/p&gt;
&lt;div class="toc"&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#covering-indexes"&gt;Covering Indexes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#provide-timezone-to-truncdate"&gt;Provide Timezone to TruncDate&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#building-json-objects"&gt;Building JSON Objects&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#loud-signal-receiver"&gt;Loud Signal Receiver&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#queryset-alias"&gt;QuerySet Alias&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#new-admin-decorators"&gt;New Admin Decorators&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#value-expression-detects-type"&gt;Value Expression Detects Type&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#more-mentionable-features"&gt;More Mentionable Features&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#wishlist"&gt;Wishlist&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;p&gt;&lt;/details&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;details markdown="1"&gt;&lt;/p&gt;
&lt;p&gt;&lt;summary&gt;⚙ Setup local environment with the latest version of Django&lt;/summary&gt;&lt;/p&gt;
&lt;p&gt;To setup an environment with the latest version of Django start by creating a new directory and a virtual environment:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;mkdir&lt;span class="w"&gt; &lt;/span&gt;django32
&lt;span class="gp"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;django32
&lt;span class="gp"&gt;$ &lt;/span&gt;python3.9&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;venv&lt;span class="w"&gt; &lt;/span&gt;venv
&lt;span class="gp"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;source&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;venv/bin/activate
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;To install the latest version of Django you can either install using &lt;code&gt;pip&lt;/code&gt;, or if it hasn't been released yet, install directly from git:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp gp-VirtualEnv"&gt;(venv)&lt;/span&gt; &lt;span class="gp"&gt;$ &lt;/span&gt;pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;git+https://github.com/django/django@3.2a1
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Start a new project and app:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp gp-VirtualEnv"&gt;(venv)&lt;/span&gt; &lt;span class="gp"&gt;$ &lt;/span&gt;django-admin&lt;span class="w"&gt; &lt;/span&gt;startproject&lt;span class="w"&gt; &lt;/span&gt;project
&lt;span class="gp gp-VirtualEnv"&gt;(venv)&lt;/span&gt; &lt;span class="gp"&gt;$ &lt;/span&gt;./manage.py&lt;span class="w"&gt; &lt;/span&gt;startapp&lt;span class="w"&gt; &lt;/span&gt;store
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Add the new app to the list of &lt;code&gt;INSTALLED_APPS&lt;/code&gt;, and configure a PostgreSQL database:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# settings.py&lt;/span&gt;

&lt;span class="n"&gt;INSTALLED_APPS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="c1"&gt;# ...&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;store&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;DATABASES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;default&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;ENGINE&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;django.db.backends.postgresql&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;NAME&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;django32&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;USER&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;postgres&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;To try out some of the new features, create a &lt;code&gt;Customer&lt;/code&gt; model:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# store/models.py&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;datetime&lt;/span&gt;

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

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Customer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&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;joined_at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DateTimeField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&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;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Finally, create the DB, generate and apply the migrations:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp gp-VirtualEnv"&gt;(venv)&lt;/span&gt; &lt;span class="gp"&gt;$ &lt;/span&gt;createdb&lt;span class="w"&gt; &lt;/span&gt;django32&lt;span class="w"&gt; &lt;/span&gt;-O&lt;span class="w"&gt; &lt;/span&gt;postrges
&lt;span class="gp gp-VirtualEnv"&gt;(venv)&lt;/span&gt; &lt;span class="gp"&gt;$ &lt;/span&gt;./manage.py&lt;span class="w"&gt; &lt;/span&gt;makemigrations
&lt;span class="gp gp-VirtualEnv"&gt;(venv)&lt;/span&gt; &lt;span class="gp"&gt;$ &lt;/span&gt;./manage.py&lt;span class="w"&gt; &lt;/span&gt;migrate
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Great! Now add some random customer data:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;string&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;datetime&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;random&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pytz&lt;/span&gt;

&lt;span class="n"&gt;starting_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pytz&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UTC&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;localize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2021&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;DAY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;days&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;Customer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bulk_create&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;Customer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;joined_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;starting_at&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DAY&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;365&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ascii_letters&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39; &amp;#39;&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&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;__&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10_000&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Congratulations! You now have 10K new customers, you are ready to go!&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;/details&gt;&lt;/p&gt;
&lt;h2 id="covering-indexes"&gt;&lt;a class="toclink" href="#covering-indexes"&gt;Covering Indexes&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://code.djangoproject.com/ticket/30913" rel="noopener"&gt;Ticket #30913&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Covering indexes let you store additional columns in an index. The main benefit of a covering index is that when a query only uses fields that are present in the index, the &lt;a href="https://www.postgresql.org/docs/current/indexes-index-only-scans.html" rel="noopener"&gt;database can use an index-only scan&lt;/a&gt;, meaning the actual table is not accessed at all. This can make queries faster.&lt;/p&gt;
&lt;p&gt;Django 3.2 added support for PostgreSQL covering indexes:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The new Index.include and UniqueConstraint.include attributes allow creating covering indexes and covering unique constraints on PostgreSQL 11+.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If for example you are searching for names of customers that have joined during a certain period of time, you can create an index on &lt;code&gt;joined_at&lt;/code&gt;, and include the field &lt;code&gt;name&lt;/code&gt; in the index:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Customer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&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;indexes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="si"&gt;%(app_label)s&lt;/span&gt;&lt;span class="s1"&gt;_&lt;/span&gt;&lt;span class="si"&gt;%(class)s&lt;/span&gt;&lt;span class="s1"&gt;_joined_at_ix&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="hll"&gt;                &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;joined_at&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,),&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;                &lt;span class="n"&gt;include&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,),&lt;/span&gt;
&lt;/span&gt;            &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The &lt;code&gt;include&lt;/code&gt; arguments makes this a covering index.&lt;/p&gt;
&lt;p&gt;For queries that only use the fields &lt;code&gt;joined_at&lt;/code&gt; and &lt;code&gt;name&lt;/code&gt;, the database will be able to satisfy queries using just the index:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="gp"&gt;... &lt;/span&gt;  &lt;span class="n"&gt;Customer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;
&lt;span class="gp"&gt;... &lt;/span&gt;  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;joined_at__lt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;pytz&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UTC&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;localize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2021&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;span class="gp"&gt;... &lt;/span&gt;  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;values_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;... &lt;/span&gt;  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;explain&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="gp"&gt;... &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="hll"&gt;&lt;span class="go"&gt;Index Only Scan using store_customer_joined_at_ix on store_customer&lt;/span&gt;
&lt;/span&gt;&lt;span class="go"&gt;  Index Cond: (joined_at &amp;lt; &amp;#39;2021-02-01 00:00:00+00&amp;#39;::timestamp with time zone)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The query above finds names of customers that joined before February 2021. According to the execution plan, the database was able to satisfy the query using just the index, without even accessing the table. This is called an "index only scan".&lt;/p&gt;
&lt;p&gt;Index only scans can be a bit confusing at first. As described in the &lt;a href="https://www.postgresql.org/docs/current/indexes-index-only-scans.html" rel="noopener"&gt;official documentation of PostgreSQL&lt;/a&gt;, it might take some time before PostgreSQL can actually use &lt;em&gt;just&lt;/em&gt; the index:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;But there is an additional requirement for any table scan in PostgreSQL: it must verify that each retrieved row be “visible” to the query's MVCC snapshot [...]. Visibility information is not stored in index entries, only in heap entries; so at first glance it would seem that every row retrieval would require a heap access anyway.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Another way to check if a table page can be viewed by the current transaction is to check the table's visibility map, which is significantly smaller and faster to access than the table itself. It may take some time for PostgreSQL to update the visibility map, so until then you might see an execution plan like this one:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;Bitmap&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Heap&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Scan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;store_customer&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;cost&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;27.07..117.02&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;876&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;16&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;Recheck&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Cond&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;joined_at&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;2021-02-01 00:00:00+00&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;timestamp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;with time zone&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Bitmap&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;Index&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Scan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;store_customer_joined_at_ix&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;cost&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.00..26.86&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;876&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;Index&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Cond&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;joined_at&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;2021-02-01 00:00:00+00&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;timestamp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;with time zone&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;To check if your index can really be used for index only scans, you can speed up the process by manually issuing &lt;a href="https://www.postgresql.org/docs/current/sql-vacuum.html" rel="noopener"&gt;vacuum analyze&lt;/a&gt; on the table:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;VACUUM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ANALYZE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;store_customer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Executing &lt;code&gt;VACUUM&lt;/code&gt; will also &lt;a href="/postgresql-unused-index-size#index-and-table-bloat"&gt;reclaim some unused space&lt;/a&gt; and make it available for re-use.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;UPDATE 2020-03-04&lt;/em&gt;: I originally suggested using &lt;code&gt;VACUUM FULL&lt;/code&gt; instead of plain &lt;code&gt;VACUUM&lt;/code&gt;. &lt;a href="https://twitter.com/Adrien_nayrat/status/1367153917239427078?s=20" rel="noopener"&gt;A commenter on twitter&lt;/a&gt; mentioned that using &lt;code&gt;VACUUM&lt;/code&gt; will be sufficient for this purpose, and will be much less intrusive and disruptive, so use that instead!&lt;/p&gt;
&lt;p&gt;It is also important to keep in mind that inclusive indexes are not free. Additional fields in the index will make the index bigger.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="provide-timezone-to-truncdate"&gt;&lt;a class="toclink" href="#provide-timezone-to-truncdate"&gt;Provide Timezone to TruncDate&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://code.djangoproject.com/ticket/31948" rel="noopener"&gt;Ticket #31948&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I write a lot about &lt;a href="sql-dos-and-donts"&gt;mistakes in SQL&lt;/a&gt; and timezones are usually at the top of the list. One of the most dangerous mistakes when working with timestamps is &lt;a href="sql-dos-and-donts#be-aware-of-timezones"&gt;truncating without explicitly specifying a timezone&lt;/a&gt;, which can lead to incorrect and inconsistent results.&lt;/p&gt;
&lt;p&gt;In Django 3.2 it becomes easier to avoid this mistake:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The new tzinfo parameter of the TruncDate and TruncTime database functions allows truncating datetimes in a specific timezone.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In previous Django versions, the timezone was set internally according to the current timezone:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# django/db/models/functions/datetime.py&lt;/span&gt;
&lt;span class="n"&gt;tzname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_current_timezone_name&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;USE_TZ&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;As of Django 3.2, you can explicitly provide a timezone to the &lt;a href="https://docs.djangoproject.com/en/dev/ref/models/database-functions/#django.db.models.functions.TruncDate" rel="noopener"&gt;&lt;code&gt;TruncDate&lt;/code&gt;&lt;/a&gt; functions family:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pytz&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db.models.functions&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TruncDay&lt;/span&gt;

&lt;span class="n"&gt;Customer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;annotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;joined_at_day&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;TruncDay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;joined_at&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tzinfo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;pytz&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UTC&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;joined_at_day&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# SELECT DATE_TRUNC(&amp;#39;day&amp;#39;, &amp;quot;store_customer&amp;quot;.&amp;quot;joined_at&amp;quot; AT TIME ZONE &amp;#39;UTC&amp;#39;) AS &amp;quot;joined_at_day&amp;quot;&lt;/span&gt;
&lt;span class="c1"&gt;# FROM &amp;quot;store_customer&amp;quot;&lt;/span&gt;

&lt;span class="n"&gt;Customer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;annotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;joined_at_day&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;TruncDay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;joined_at&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tzinfo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;pytz&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;America/New_York&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;joined_at_day&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# SELECT DATE_TRUNC(&lt;/span&gt;
&lt;span class="c1"&gt;#     &amp;#39;day&amp;#39;,&lt;/span&gt;
&lt;span class="c1"&gt;#     &amp;quot;store_customer&amp;quot;.&amp;quot;joined_at&amp;quot; AT TIME ZONE &amp;#39;America/New_York&amp;#39;&lt;/span&gt;
&lt;span class="c1"&gt;# ) AS &amp;quot;joined_at_day&amp;quot;&lt;/span&gt;
&lt;span class="c1"&gt;# FROM &amp;quot;store_customer&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;A step in the right direction!&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="building-json-objects"&gt;&lt;a class="toclink" href="#building-json-objects"&gt;Building JSON Objects&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://code.djangoproject.com/ticket/32179" rel="noopener"&gt;Ticket #32179&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Building JSON objects in PostgreSQL is very handy, especially if you are working with unstructured data.&lt;/p&gt;
&lt;p&gt;As of Django 3.2, the function &lt;a href="https://www.postgresql.org/docs/current/functions-json.html#FUNCTIONS-JSON-CREATION-TABLE" rel="noopener"&gt;&lt;code&gt;json_build_object&lt;/code&gt; from PostgreSQL&lt;/a&gt; that accepts arbitrary key-value pairs was added to the ORM:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Added the JSONObject database function.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;One interesting use case is serializing objects directly in the DB, bypassing the need to create ORM objects:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;F&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db.models.functions&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;JSONObject&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Customer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;annotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;JSONObject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;F&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;F&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;joined_at_day&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;TruncDay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;joined_at&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tzinfo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;pytz&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UTC&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;values_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;obj&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Haki Benita&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;joined_at_day&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;2021-04-25T00:00:00&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;},)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We already showed how crucial &lt;a href="/django-rest-framework-slow"&gt;serialization performance&lt;/a&gt; can be, so this is something to consider.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="loud-signal-receiver"&gt;&lt;a class="toclink" href="#loud-signal-receiver"&gt;Loud Signal Receiver&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://code.djangoproject.com/ticket/32261" rel="noopener"&gt;Ticket #32261&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;A while back I &lt;a href="https://twitter.com/be_haki/status/1335921247306264579?s=20" rel="noopener"&gt;twitted about a mysterious bug&lt;/a&gt; I had that went unnoticed for a long time because it happened inside a signal receiver.&lt;/p&gt;
&lt;p&gt;When you use &lt;code&gt;send_robust&lt;/code&gt; to broadcast signals, if the signal fails, Django keeps the error and moves on to the next receiver. After all of the receivers processed the signal, Django returns a list with the receivers' return values and exceptions. To check if any of the receivers failed, you need to go over the list and check for instances of &lt;code&gt;Exception&lt;/code&gt;. Signals are often used to decouple modules, and handing exceptions from receivers this way defeats that purpose.&lt;/p&gt;
&lt;p&gt;To make sure I don't miss exceptions in signal receivers again, I created a "loud signal receiver" that logs exceptions:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.dispatch&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;receiver&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;loud_receiver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Subscribe to Django signal and log errors from the receiver function.&lt;/span&gt;

&lt;span class="sd"&gt;    When using `send_robust` to send Django Signals, errors happening in the&lt;/span&gt;
&lt;span class="sd"&gt;    receivers are kept and returned. Because signals are mostly used for decoupling&lt;/span&gt;
&lt;span class="sd"&gt;    modules, the return value from `send_robust` is often dismissed.&lt;/span&gt;
&lt;span class="sd"&gt;    To make it easier not to miss errors from Django signal receivers, use this decorator&lt;/span&gt;
&lt;span class="sd"&gt;    instead to log the exceptions to a specific logger.&lt;/span&gt;

&lt;span class="sd"&gt;    NOTE: Not necessary as of Django 3.2&lt;/span&gt;

&lt;span class="sd"&gt;    Example:&lt;/span&gt;
&lt;span class="sd"&gt;        logger = logging.getLogger(&amp;#39;some.logger&amp;#39;)&lt;/span&gt;
&lt;span class="sd"&gt;        @loud_receiver(signals.SomeSignal, logger=logger, dispatch_uid=&amp;#39;uid&amp;#39;)&lt;/span&gt;
&lt;span class="sd"&gt;        def receiver_func():&lt;/span&gt;
&lt;span class="sd"&gt;            pass&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_decorator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;func&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;loud_func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;func_args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;func_kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;func_args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;func_kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="ne"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;exception from signal receiver&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;raise&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;receiver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;loud_func&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;_decorator&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;As of Django 3.2 this is no longer necessary:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Signal.send_robust() now logs exceptions.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Great!&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="queryset-alias"&gt;&lt;a class="toclink" href="#queryset-alias"&gt;QuerySet Alias&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://code.djangoproject.com/ticket/27719" rel="noopener"&gt;Ticket #27719&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;alias&lt;/code&gt; function is an entirely new feature in Django 3.2:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The new QuerySet.alias() method allows creating reusable aliases for expressions that don’t need to be selected but are used for filtering, ordering, or as a part of complex expressions.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I often use &lt;code&gt;SubQuery&lt;/code&gt; and &lt;code&gt;OuterRef&lt;/code&gt; to write complex queries, and there is a little gotcha when combined with &lt;code&gt;annotate&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Subquery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;OuterRef&lt;/span&gt;

&lt;span class="n"&gt;Customer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;annotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id_of_previous_customer&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Subquery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;Customer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;joined_at__lt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;OuterRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;joined_at&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;order_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;-joined_at&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)[:&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id_of_previous_customer__isnull&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The query above is a complicated way to find the first customer that joined. The queryset is using &lt;code&gt;SubQuery&lt;/code&gt; to find the previous customer for every customer by &lt;code&gt;joined_at&lt;/code&gt;, and then looks for the customer which no other customer has joined before. This is very inefficient, but I'm using it to illustrate my point.&lt;/p&gt;
&lt;p&gt;To understand the problem, inspect the query this queryset is producing:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;store_customer&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;store_customer&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;joined_at&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;store_customer&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;name&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;U0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;id&amp;quot;&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;store_customer&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;U0&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;U0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;joined_at&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;store_customer&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;joined_at&amp;quot;&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;ORDER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;U0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;joined_at&amp;quot;&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;LIMIT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;id_of_previous_customer&amp;quot;&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;store_customer&amp;quot;&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;U0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;id&amp;quot;&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;store_customer&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;U0&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;U0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;joined_at&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;store_customer&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;joined_at&amp;quot;&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;ORDER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;U0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;joined_at&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;DESC&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;LIMIT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;IS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The annotated subquery appears in both the &lt;code&gt;SELECT&lt;/code&gt; and the &lt;code&gt;WHERE&lt;/code&gt; clauses. This affects the execution plan:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;Seq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Scan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;store_customer&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cost&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;4401&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;05&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;width&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="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;SubPlan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;IS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;SubPlan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;Limit&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cost&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;29&lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;Index&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Scan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;Backward&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;using&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;store_customer_joined_at_ix&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;store_customer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;u0&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cost&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;29&lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;450&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;59&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3333&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;Index&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Cond&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;joined_at&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;store_customer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;joined_at&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;SubPlan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;Limit&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cost&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;29&lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;Index&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Scan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;Backward&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;using&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;store_customer_joined_at_ix&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;store_customer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;u0_1&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cost&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;29&lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;450&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;59&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3333&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;Index&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Cond&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;joined_at&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;store_customer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;joined_at&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The subquery is executed twice!&lt;/p&gt;
&lt;p&gt;To solve this in Django versions prior to 3.2, you can provide a &lt;code&gt;values_list&lt;/code&gt; that excludes the annotated subquery from the &lt;code&gt;SELECT&lt;/code&gt; clause:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# Django 3.1&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Subquery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;OuterRef&lt;/span&gt;

&lt;span class="n"&gt;Customer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;annotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id_of_previous_customer&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Subquery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;Customer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;joined_at__lt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;OuterRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;joined_at&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;order_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;-joined_at&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)[:&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id_of_previous_customer__isnull&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;em&gt;Side note&lt;/em&gt;: You might think that instead of using &lt;code&gt;values_list&lt;/code&gt; in this case you can omit the annotated field using &lt;code&gt;.defer('id_of_previous_customer')&lt;/code&gt;. This won't work. Django will throw a &lt;code&gt;KeyError: 'id_of_previous_customer'&lt;/code&gt; at you!&lt;/p&gt;
&lt;p&gt;Starting with Django 3.2, you can replace &lt;code&gt;annotate&lt;/code&gt; with &lt;code&gt;alias&lt;/code&gt; and the field will not be added to the select clause:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# Django 3.2&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Subquery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;OuterRef&lt;/span&gt;

&lt;span class="hll"&gt;&lt;span class="n"&gt;Customer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;alias&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;    &lt;span class="n"&gt;id_of_previous_customer&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Subquery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;Customer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;joined_at__lt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;OuterRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;joined_at&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;order_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;-joined_at&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)[:&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id_of_previous_customer__isnull&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The generated SQL now uses the subquery only once:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;store_customer&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;store_customer&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;joined_at&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;store_customer&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;name&amp;quot;&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;store_customer&amp;quot;&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;U0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;id&amp;quot;&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;store_customer&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;U0&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;U0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;joined_at&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;store_customer&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;joined_at&amp;quot;&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;ORDER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;U0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;joined_at&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;DESC&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;LIMIT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;IS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The execution plan is simpler:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;Seq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Scan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;store_customer&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cost&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;4380&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;04&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;28&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;SubPlan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;IS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;SubPlan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;Limit&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cost&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;29&lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;Index&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Scan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;Backward&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;using&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;store_customer_joined_at_ix&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;store_customer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;u0&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cost&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;29&lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;450&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;59&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3333&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;Index&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Cond&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;joined_at&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;store_customer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;joined_at&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;One less way to shoot yourself in the foot!&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="new-admin-decorators"&gt;&lt;a class="toclink" href="#new-admin-decorators"&gt;New Admin Decorators&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://code.djangoproject.com/ticket/16117" rel="noopener"&gt;Ticket #16117&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Before Django 3.2, to &lt;a href="/things-you-must-know-about-django-admin-as-your-app-gets-bigger"&gt;customize a calculated field in Django admin&lt;/a&gt; you first added a function, and then assigned some attributes to it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# Django 3.1&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.contrib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt;

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

&lt;span class="nd"&gt;@admin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Customer&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;CustomerAdmin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ModelAdmin&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;list_display&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;joined_at&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;joined_at_year&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;joined_at_year&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Customer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&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;obj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;joined_at&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt;
&lt;span class="hll"&gt;    &lt;span class="n"&gt;joined_at_year&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;admin_order_field&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;joined_at__year&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;    &lt;span class="n"&gt;joined_at_year&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;short_description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Year joined&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This is the kind of weird APIs that are mostly only possible in dynamic languages such as Python.&lt;/p&gt;
&lt;p&gt;If you are using Mypy (&lt;a href="/python-mypy-exhaustive-checking"&gt;and you should&lt;/a&gt;), this code will trigger an annoying warning, and the only way to silence it is to add a &lt;code&gt;type: ignore&lt;/code&gt; comment:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="n"&gt;joined_at_year&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;admin_order_field&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;joined_at__year&amp;#39;&lt;/span&gt;  &lt;span class="c1"&gt;# type: ignore[attr-defined]&lt;/span&gt;
&lt;span class="n"&gt;joined_at_year&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;short_description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Year joined&amp;#39;&lt;/span&gt;  &lt;span class="c1"&gt;# type: ignore[attr-defined]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;If you are using &lt;a href="tag/django-admin"&gt;Django Admin&lt;/a&gt; and &lt;a href="python-mypy-exhaustive-checking"&gt;Mypy&lt;/a&gt; as much as I do, this can be pretty annoying.&lt;/p&gt;
&lt;p&gt;The new &lt;a href="https://docs.djangoproject.com/en/dev/ref/contrib/admin/#the-display-decorator" rel="noopener"&gt;&lt;code&gt;display&lt;/code&gt; decorator&lt;/a&gt; solves this problem:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The new display() decorator allows for easily adding options to custom display functions that can be used with list_display or readonly_fields.
Likewise, the new action() decorator allows for easily adding options to action functions that can be used with actions.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Adjusting the code to use the new &lt;code&gt;display&lt;/code&gt; decorator:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="nd"&gt;@admin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;display&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ordering&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;joined_at__year&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Year joined&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;joined_at_year&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Customer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&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;obj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;joined_at&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;No type errors!&lt;/p&gt;
&lt;p&gt;Another useful decorator is &lt;a href="https://docs.djangoproject.com/en/dev/ref/contrib/admin/actions/#django.contrib.admin.action" rel="noopener"&gt;&lt;code&gt;action&lt;/code&gt;&lt;/a&gt; which uses a similar approach to customize &lt;a href="https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.actions" rel="noopener"&gt;custom admin actions&lt;/a&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="value-expression-detects-type"&gt;&lt;a class="toclink" href="#value-expression-detects-type"&gt;Value Expression Detects Type&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://code.djangoproject.com/ticket/30446" rel="noopener"&gt;Ticket #30446&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This is a small feature that addresses a small nuisance in the ORM:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Value() expression now automatically resolves its output_field to the appropriate Field subclass based on the type of its provided value for bool, bytes, float, int, str, datetime.date, datetime.datetime, datetime.time, datetime.timedelta, decimal.Decimal, and uuid.UUID instances. As a consequence, resolving an output_field for database functions and combined expressions may now crash with mixed types when using Value(). You will need to explicitly set the output_field in such cases.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In previous Django versions if you wanted to use some constant value in a query, you had to explicitly set an &lt;code&gt;output_field&lt;/code&gt;, otherwise it will fail:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="c1"&gt;# Django 3.1&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Value&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;Customer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;annotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="go"&gt;    number=Value(1),&lt;/span&gt;
&lt;span class="go"&gt;    text=Value(&amp;#39;text&amp;#39;),&lt;/span&gt;
&lt;span class="go"&gt;    boolean=Value(True),&lt;/span&gt;
&lt;span class="go"&gt;    date_=Value(datetime.date(2020, 1, 1)),&lt;/span&gt;
&lt;span class="go"&gt;    datetime_=Value(pytz.UTC.localize(datetime.datetime(2020, 1, 1))),&lt;/span&gt;
&lt;span class="go"&gt;).values_list(&amp;#39;number&amp;#39;, &amp;#39;text&amp;#39;, &amp;#39;boolean&amp;#39;, &amp;#39;date_&amp;#39;, &amp;#39;datetime_&amp;#39;).first()&lt;/span&gt;

&lt;span class="hll"&gt;&lt;span class="go"&gt;FieldError: Cannot resolve expression type, unknown output_field&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;In Django 3.2, the ORM figures it out on its own:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="c1"&gt;# Django 3.2&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Value&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;Customer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;annotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="go"&gt;    number=Value(1),&lt;/span&gt;
&lt;span class="go"&gt;    text=Value(&amp;#39;text&amp;#39;),&lt;/span&gt;
&lt;span class="go"&gt;    boolean=Value(True),&lt;/span&gt;
&lt;span class="go"&gt;    date_=Value(datetime.date(2020, 1, 1)),&lt;/span&gt;
&lt;span class="go"&gt;    datetime_=Value(pytz.UTC.localize(datetime.datetime(2020, 1, 1))),&lt;/span&gt;
&lt;span class="go"&gt;).values_list(&amp;#39;number&amp;#39;, &amp;#39;text&amp;#39;, &amp;#39;boolean&amp;#39;, &amp;#39;date_&amp;#39;, &amp;#39;datetime_&amp;#39;).first()&lt;/span&gt;

&lt;span class="hll"&gt;&lt;span class="go"&gt;(1, &amp;#39;text&amp;#39;, True, datetime.date(2020, 1, 1), datetime.datetime(2020, 1, 1, 0, 0, tzinfo=&amp;lt;UTC&amp;gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Very cool!&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="more-mentionable-features"&gt;&lt;a class="toclink" href="#more-mentionable-features"&gt;More Mentionable Features&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;There are plenty more features in Django 3.2 that the documentation explains better than me. To name a few:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Navigable links in the admin&lt;/strong&gt; (&lt;a href="https://code.djangoproject.com/ticket/31181" rel="noopener"&gt;Ticket #31181&lt;/a&gt;): Read-only related fields are now rendered as navigable links if target models are registered in the admin. I'm still using a &lt;a href="things-you-must-know-about-django-admin-as-your-app-gets-bigger#admin_link"&gt;decorator to add links to Django Admin&lt;/a&gt;, guess now i'll have less use for it.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://docs.djangoproject.com/en/dev/topics/db/transactions/#controlling-transactions-explicitly" rel="noopener"&gt;Durable argument for &lt;code&gt;atomic()&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt; (&lt;a href="https://code.djangoproject.com/ticket/32220" rel="noopener"&gt;Ticket #32220&lt;/a&gt;): When you execute code inside a database transaction, when the transaction finishes without any errors you expect it to be committed to the database. However, if the caller executed your code inside a database transaction of his own, if the parent transaction is rolled back, so is yours. To prevent this from happening, you can now mark your transaction as &lt;code&gt;durable&lt;/code&gt;. When there is an attempt to open a durable transaction inside another transaction, a &lt;code&gt;RuntimeError&lt;/code&gt; error is raised.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Cached templates are reloaded on Django's development server&lt;/strong&gt; (&lt;a href="https://code.djangoproject.com/ticket/25791" rel="noopener"&gt;Ticket #25791&lt;/a&gt;): If you are using Django's &lt;code&gt;runserver&lt;/code&gt; command to develop locally, you probably got used to it reloading when a python file changes. However, if you are using Django's &lt;a href="https://docs.djangoproject.com/en/dev/ref/templates/api/#django.template.loaders.cached.Loader" rel="noopener"&gt;&lt;code&gt;django.template.loaders.cached.Loader&lt;/code&gt; loader&lt;/a&gt;, when an HTML file is changing the dev server will not reload it, and you will have to restart the devserver to see the changes. This is pretty annoying, and so far I had to disable the cached loader in dev. Starting at Django 3.2 this is no longer necessary because cached templates are correctly reloaded in development.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Support for &lt;a href="https://docs.djangoproject.com/en/dev/releases/3.2/#functional-indexes" rel="noopener"&gt;function based indexes&lt;/a&gt;&lt;/strong&gt; (&lt;a href="https://code.djangoproject.com/ticket/26167" rel="noopener"&gt;Ticket #26167&lt;/a&gt;): FBIs are useful when you query an expression often and you want to index it. A classic example is indexing lower case texts.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="wishlist"&gt;&lt;a class="toclink" href="#wishlist"&gt;Wishlist&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Django ORM is comprehensive and feature rich, but there are still some things on my wishlist for future versions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Custom joins&lt;/strong&gt;: Django is currently able to perform joins only between tables that are connected via &lt;code&gt;ForeignKey&lt;/code&gt;. There are situations where you want to join tables that are not necessarily connected with a foreign key, or using more complex conditions. One common example is &lt;a href="https://en.wikipedia.org/wiki/Slowly_changing_dimension" rel="noopener"&gt;slowly changing dimensions&lt;/a&gt;, where a join condition require a &lt;code&gt;BETWEEN&lt;/code&gt; operator.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Update returning&lt;/strong&gt;: When updating many rows it's sometimes useful to fetch them immediately. This is a well known (&lt;a href="sql-tricks-application-dba#implement-complete-processes-using-with-and-returning"&gt;and a very useful&lt;/a&gt;) feature in SQL. Django currently &lt;a href="https://code.djangoproject.com/ticket/28682" rel="noopener"&gt;has no support for it&lt;/a&gt;, but I hear &lt;a href="https://groups.google.com/g/django-developers/c/qQ5DT91nBLM" rel="noopener"&gt;it might soon&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Database views&lt;/strong&gt;: There are many hacks for getting database views to work with the ORM. These hacks usually involves creating a view directly in the database or in a manual migration, and then setting &lt;a href="https://docs.djangoproject.com/en/3.2/ref/models/options/#managed" rel="noopener"&gt;&lt;code&gt;managed=False&lt;/code&gt;&lt;/a&gt; on the model. These hacks get the job done, but not in a very graceful way. I wish there was a way to define database views so that migrations can detect changes. Maybe even an option to create views using a Django queryset.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Database partitions&lt;/strong&gt;: Database partitions are extremely useful in data modeling. When used correctly they can make queries much faster and maintenance a lot easier. Some database engines such as Oracle already provide very mature implementations for database partitioning, and other engines such as PostgreSQL are getting there. At the moment, there is no native support for database partitioning in Django, and most implementations I've seen resort to manually managing tables. As a result, I often avoid partitions all together and that's unfortunate.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Require authentication by default&lt;/strong&gt;: Django currently permits access to any view unless explicitly marked otherwise, usually using the &lt;a href="https://docs.djangoproject.com/en/3.2/topics/auth/default/#the-login-required-decorator" rel="noopener"&gt;&lt;code&gt;require_login&lt;/code&gt; decorator&lt;/a&gt;. This makes it easier to get started with Django, but it can potentially cause security issues down the road if you are not careful. I know there are solutions for this, usually using custom middleware and decorators. I really wish Django had an option to flip the condition so that access is &lt;em&gt;restricted&lt;/em&gt; by default unless marked otherwise.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Typing&lt;/strong&gt;: If you are following this blog you know I'm a &lt;a href="/python-mypy-exhaustive-checking"&gt;big fan of type hinting in Python&lt;/a&gt;. At the moment, Django does not come with type hinting or official stubs. Shiny new frameworks such as &lt;a href="https://www.starlette.io/" rel="noopener"&gt;Starlette&lt;/a&gt; and &lt;a href="https://fastapi.tiangolo.com/" rel="noopener"&gt;FastAPI&lt;/a&gt; advertise themselves as being 100% type annotated, but Django is still lagging behind. There is a project called &lt;a href="https://github.com/typeddjango/django-stubs" rel="noopener"&gt;django-stubs&lt;/a&gt; that is making some progress in this regard.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Database connection pooling&lt;/strong&gt; Django currently supports two modes for managing database connections - creating a new connection per request, or a new connection per thread (persistent connections). Creating database connections in common deployments is a relatively heavy operation. It requires setting up a TCP connection, often a TLS connection, and initializing the connection, which adds significant latency. In PostgreSQL in particular, it also consumes many database server resources, so creating a new connection per request is a really bad idea. &lt;br&gt;&lt;br&gt;
Persistent connections are much better. They work well with the way Django is usually deployed, small amount of worker processes and/or threads. But such deployments tend to breakdown under real world conditions. Whenever your database or one of your upstreams starts taking longer to process requests for some reason, the workers get tied up, requests back up, and the entire system chokes. Even with strict timeouts, this will still happen. &lt;br&gt;&lt;br&gt;
To improve upon this catastrophic failure mode, a common solution is to use async workers such as &lt;a href="http://www.gevent.org/api/gevent.greenlet.html" rel="noopener"&gt;gevent greenlets&lt;/a&gt;, or in the future, &lt;a href="https://docs.python.org/3/library/asyncio-task.html#creating-tasks" rel="noopener"&gt;asycnio tasks&lt;/a&gt;. But now, each request gets its own lightweight thread, hence its own connection, which renders Django's persistent connections feature useless.&lt;br&gt;&lt;br&gt;
It would be great if Django included a high-quality connection pool, which maintains a certain number of connections and hands them out to requests as needed. External solutions like &lt;a href="https://www.pgbouncer.org/" rel="noopener"&gt;PgBouncer&lt;/a&gt; exist, but they add operational overhead. A built-in solution would often be sufficient.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</content><category term="articles"></category><category term="Django"></category><category term="Django Admin"></category><category term="ORM"></category></entry><entry><title>The Unexpected Find That Freed 20GB of Unused Index Space</title><link href="https://hakibenita.com/postgresql-unused-index-size" rel="alternate"></link><published>2021-02-01T00:00:00+02:00</published><updated>2021-02-01T00:00:00+02:00</updated><author><name>Haki Benita</name></author><id>tag:hakibenita.com,2021-02-01:/postgresql-unused-index-size</id><summary type="html">&lt;p&gt;In this article I describe the process we took to identify potential free space, and one surprising find that helped up clear up ~10GB of unused indexed values!&lt;/p&gt;</summary><content type="html">&lt;hr&gt;
&lt;p&gt;Every few months we get an alert from our database monitoring to warn us that we are about to run out of space. Usually we just provision more storage and forget about it, but this time we were under quarantine, and the system in question was under less load than usual. We thought this is a good opportunity to do some cleanups that would otherwise be much more challenging.&lt;/p&gt;
&lt;p&gt;To start from the end, &lt;strong&gt;we ended up freeing more than 70GB of un-optimized and un-utilized space&lt;/strong&gt; without dropping a single index or deleting any data!&lt;/p&gt;
&lt;p&gt;Using conventional techniques such as rebuilding indexes and tables we cleared up a lot of space, but then &lt;strong&gt;one surprising find helped us clear an additional ~20GB of unused indexed values!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This is what the free storage chart of one of our databases looked like in the process:&lt;/p&gt;
&lt;figure&gt;&lt;img alt="Free space over time (higher means more free space)" src="https://hakibenita.com/images/00-postgresql-unused-index-size.png"&gt;&lt;figcaption&gt;Free space over time (higher means more free space)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;details class="toc-container" open&gt;
    &lt;summary&gt;Table of Contents&lt;/summary&gt;&lt;/p&gt;
&lt;div class="toc"&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#the-usual-suspects"&gt;The Usual Suspects&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#unused-indexes"&gt;Unused Indexes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#index-and-table-bloat"&gt;Index and Table Bloat&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#clearing-bloat-in-indexes"&gt;Clearing Bloat in Indexes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#activating-b-tree-index-deduplication"&gt;Activating B-Tree Index Deduplication&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#clearing-bloat-in-tables"&gt;Clearing Bloat in Tables&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#using-pg_repack"&gt;Using pg_repack&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#the-find"&gt;The "Find"&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#the-aha-moment"&gt;The "Aha Moment"&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#utilizing-partial-indexes"&gt;Utilizing Partial Indexes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#bonus-migrating-with-django-orm"&gt;Bonus: Migrating with Django ORM&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#prevent-implicit-creation-of-indexes-on-foreign-keys"&gt;Prevent Implicit Creation of Indexes on Foreign Keys&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#migrate-exiting-full-indexes-to-partial-indexes"&gt;Migrate Exiting Full Indexes to Partial Indexes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#conclusion"&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;p&gt;&lt;/details&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="the-usual-suspects"&gt;&lt;a class="toclink" href="#the-usual-suspects"&gt;The Usual Suspects&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Provisioning storage is something we do from time to time, but before we throw money at the problem we like to make sure we make good use of the storage we already have. To do that, we start with the usual suspects.&lt;/p&gt;
&lt;h3 id="unused-indexes"&gt;&lt;a class="toclink" href="#unused-indexes"&gt;Unused Indexes&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Unused indexes are double-edged swords; you create them to make things faster, but they end up taking space and slow inserts and updates. Unused indexes are the first thing we always check when we need to clear up storage.&lt;/p&gt;
&lt;p&gt;To find unused indexes we use the following query:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;relname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;indexrelname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;idx_scan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;idx_tup_read&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;idx_tup_fetch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;pg_size_pretty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pg_relation_size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;indexrelname&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;regclass&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;size&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;pg_stat_all_indexes&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;schemaname&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;public&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;AND&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;indexrelname&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NOT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;LIKE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;pg_toast_%&amp;#39;&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;AND&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;idx_scan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;AND&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;idx_tup_read&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;AND&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;idx_tup_fetch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;span class="k"&gt;ORDER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;pg_relation_size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;indexrelname&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;regclass&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The query is looking for &lt;strong&gt;indexes that were not scanned or fetched&lt;/strong&gt; since the last time the statistics were reset.&lt;/p&gt;
&lt;p&gt;Some indexes may seem like they were not used but they were in-fact used:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://www.postgresql.org/docs/current/monitoring-stats.html#MONITORING-PG-STAT-ALL-INDEXES-VIEW" rel="noopener"&gt;The documentation&lt;/a&gt; lists a few scenarios when this is possible. For example, when the optimizer uses meta data from the index, but not the index itself.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Indexes used to enforce unique or primary key constraints for tables that were not updated in a while. The indexes will look like they were not used, but it doesn't mean we can dispose of them.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To find the unused indexes you can actually drop, you usually have to go over the list one by one and make a decision. This can be time consuming in the first couple of times, but after you get rid of most unused indexes it becomes easier.&lt;/p&gt;
&lt;p&gt;It's also a good idea to &lt;strong&gt;reset the statistics counters from time to time&lt;/strong&gt;, usually right after you finished inspecting the list. PostgreSQL provides a few &lt;a href="https://www.postgresql.org/docs/current/monitoring-stats.html#MONITORING-STATS-FUNCS-TABLE" rel="noopener"&gt;functions to reset statistics&lt;/a&gt; at different levels. When we find an index we suspect is not being used, or when we add new indexes in place of old ones, we usually reset the counters for the table and wait for a while:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;-- Find table oid by name&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;oid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg_class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;relname&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;table_name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- Reset counts for all indexes of table&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg_stat_reset_single_table_counters&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;14662536&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We do this every once in a while, so in our case there were no unused indexes to drop.&lt;/p&gt;
&lt;h3 id="index-and-table-bloat"&gt;&lt;a class="toclink" href="#index-and-table-bloat"&gt;Index and Table Bloat&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The next suspect is bloat. When you update rows in a table, PostgreSQL marks the tuple as dead and adds the updated tuple in the next available space. This process creates what's called "bloat", which can cause tables to consume more space than they really need. Bloat also affects indexes, so to free up space, bloat is a good place to look.&lt;/p&gt;
&lt;p&gt;Estimating bloat in tables and indexes is apparently not a simple task. Lucky for us, some good people on the world wide web already &lt;a href="https://wiki.postgresql.org/wiki/Show_database_bloat" rel="noopener"&gt;did the hard work&lt;/a&gt; and wrote queries to estimate &lt;a href="https://github.com/ioguix/pgsql-bloat-estimation/blob/master/table/table_bloat.sql" rel="noopener"&gt;table bloat&lt;/a&gt; and &lt;a href="https://github.com/ioguix/pgsql-bloat-estimation/blob/master/btree/btree_bloat.sql" rel="noopener"&gt;index bloat&lt;/a&gt;. After running these queries you will most likely find &lt;em&gt;some&lt;/em&gt; bloat, so the next thing to do it clear up that space.&lt;/p&gt;
&lt;h4 id="clearing-bloat-in-indexes"&gt;&lt;a class="toclink" href="#clearing-bloat-in-indexes"&gt;Clearing Bloat in Indexes&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;To clear bloat in an index, you need to rebuild it. There are several ways to rebuild an index:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Re-create the index&lt;/strong&gt;: If you re-create the index, it will be built in an optimal way.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Rebuild the index&lt;/strong&gt;: Instead of dropping and creating the index yourself, PostgreSQL provides a way to re-build an existing index in-place using the &lt;a href="https://www.postgresql.org/docs/current/sql-reindex.html" rel="noopener"&gt;&lt;code&gt;REINDEX&lt;/code&gt;&lt;/a&gt; command:&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;REINDEX&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;INDEX&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;index_name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Rebuild the index concurrently&lt;/strong&gt;: The previous methods will obtain a lock on the table and prevent it from being changed while the operation is in progress, which is usually unacceptable. To rebuild the index without locking it for updates, you can &lt;a href="https://www.postgresql.org/docs/current/sql-reindex.html#SQL-REINDEX-CONCURRENTLY" rel="noopener"&gt;rebuilt the index concurrently&lt;/a&gt;:&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;REINDEX&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;INDEX&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CONCURRENTLY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;index_name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;When using &lt;code&gt;REINDEX CONCURRENTLY&lt;/code&gt;, PostgreSQL creates a new index with a name suffixed with &lt;code&gt;_ccnew&lt;/code&gt;, and syncs any changes made to the table in the meantime. When the rebuild is done, it will switch the old index with the new index, and drop the old one.&lt;/p&gt;
&lt;figure&gt;
&lt;p&gt;&lt;svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 314.6178987276779 320.25169897053615" width="auto" height="50vh"&gt;&lt;g transform="translate(10 107.64129082580851) rotate(0 6.281818150930462 6.5734725011127075)"&gt;&lt;path d="M-1.3842925820499659 -1.1872281339019537 L11.874384720654007 1.136923560872674 L13.000843186707016 13.56048340789713 L1.9123801793903112 13.650584351939337" stroke="none" stroke-width="0" fill="#f41d92"&gt;&lt;/path&gt;&lt;path d="M0.05795299176248214 0.783424572535047 C4.617292622409793 0.4092872833196779, 8.339113611975323 -0.6279773110008966, 13.269594132837716 -0.3166460868843761 M-0.12968918729727869 0.2332091903590222 C4.088457091815665 0.6669499351364089, 7.727869091193958 0.1664303611183769, 13.108608599698075 0.6161000606459712 M13.09428494502322 -0.5669882488247653 C12.66378901352661 5.217510340309369, 13.376305351980335 8.800848271500545, 12.96301501223118 12.876522021465087 M12.746510372692322 0.6553986082474832 C12.581113803029872 2.5919560008029765, 12.622839104445353 5.245383714807264, 12.558198560950945 13.600653581123265 M11.415610236150687 12.46772735150659 C9.355425389381876 12.362106375017214, 6.354124046039259 13.711371459097675, -0.20865465424804808 12.188989366410045 M11.995184746102135 13.659943864136133 C8.592181550162307 13.001284919018138, 5.1106813878129635 13.685477705799183, -0.04532038597203758 12.815673899989063 M-1.1000040886275568 12.731057849432949 C0.21902566548406913 9.489100552062574, 1.1305611659235009 5.642408810245789, -0.3697369778005192 0.7188796123611438 M-0.14602182774982642 12.604513383399677 C-0.36849946392273375 7.913082986757547, -0.5731878546013287 3.708427301249994, 0.21159051344801982 -0.1758975795591194" stroke="#000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(37.36399155052743 110.03810659595638) rotate(0 7.661128495758078 6.228644914905818)"&gt;&lt;path d="M0 0 C0 0, 0 0, 0 0 M0 0 C0 0, 0 0, 0 0 M-0.7823861285718032 6.21932599772989 C1.4500921559493123 5.108182356875764, 3.441469844423457 1.8062618040133345, 5.504833485516256 0.7565337944624239 M-0.5410761464061091 6.493475117956392 C1.1042147590433127 4.450646080947254, 2.799193682642527 2.947971151017162, 4.936016817348546 0.20714467091832695 M0.749740474773537 12.50263638687092 C3.429416267581061 8.328572483740857, 8.430646119535286 3.310563269361819, 9.894020706367623 -1.2278720308944822 M-0.2530843212176455 11.5469090965088 C3.6198122759283624 8.115291381178743, 7.263796162362432 3.2204682975414873, 11.011310499113929 0.26771362601665283 M1.9822030287651877 16.18222850012341 C7.9079710780281545 7.660818137719537, 11.162632307947874 3.256727180213902, 17.40162397288107 -1.3712397212232479 M2.8965779627623567 15.074667477474824 C6.039740370888001 9.748772210846312, 10.362500897020059 6.389892315776484, 16.156475719300513 0.5774116917909107 M7.991257522999459 15.791074121828208 C11.157692524742462 13.017885557118333, 13.823949476039282 10.31235320142318, 15.124491205474788 6.933083097492914 M8.542597238951512 15.010502843410725 C10.065679500528379 12.58774874205957, 11.700320943807514 10.608037233401689, 15.43983565409524 7.124962455402698 M-0.13035152897861035 12.343976974416462 C-0.13035152897861035 12.343976974416462, -0.13035152897861035 12.343976974416462, -0.13035152897861035 12.343976974416462 M-0.13035152897861035 12.343976974416462 C-0.13035152897861035 12.343976974416462, -0.13035152897861035 12.343976974416462, -0.13035152897861035 12.343976974416462 M6.2573820087746075 12.258746413783095 C3.6406931770093367 10.838096683894566, 2.6110627295665267 8.509710568718162, 0.7097642420763544 6.642233357922574 M5.953746929733484 12.289846019715926 C4.156915574139798 11.156445197377138, 2.099160544225261 9.062240973186897, 0.6041243846500686 7.300675533683439 M12.639922596477781 12.234711891984539 C9.479159855375592 8.829157312289606, 5.428766041175559 6.918530999021034, -0.8353254491257551 1.817709034658029 M11.564287603610072 11.857971174841717 C8.586089238068022 9.110229739716594, 4.070770943018075 5.774034595831673, 0.3099437890780087 1.9091063685482288 M17.79961197254866 9.889928571972144 C13.390884911628714 7.16659965384633, 10.133121394450043 5.370337843963153, 2.6919010213791985 -3.8236372402865806 M15.813024015069601 11.302633100281549 C12.24047516447084 6.120951890525337, 5.277437527884766 1.260175445221373, 0.9323548746511001 -2.8756859523240843 M17.219884106443903 4.846063522998845 C13.325119344161799 3.2295918377387403, 12.60798064011254 0.9736529774247229, 8.90229585811149 -0.8253047820503379 M16.033102332215464 5.703039742032667 C13.261186573292777 3.347397258867739, 10.59688428133295 0.8245542573304023, 8.640990040382627 -1.7184323719559724" stroke="#ced4da" stroke-width="0.5" fill="none"&gt;&lt;/path&gt;&lt;path d="M0.9554425442952907 0.17351121434821248 C6.020914674081412 -0.7521373383153069, 10.362654422614952 0.8538985044074764, 14.936084341971874 0.11788972649868734 M0.28441536045858595 0.2716002979256231 C5.26031749302167 0.027190904072255995, 9.21531618367666 0.6962659218955699, 16.073635273638825 0.4665317254535257 M14.785011498779108 0.20878779183966767 C15.893441702951487 3.482225933567059, 14.79302138157144 5.575025505643894, 15.066019709972592 11.224815477452086 M15.9432750619205 -0.05896997058487752 C15.213790548045978 4.47305994358339, 15.223313621437892 8.979834340188118, 15.752165176622714 11.956165625267571 M14.493902274184343 11.908574566482287 C10.239204099332579 11.472190360597354, 7.782717539140194 12.06281046436649, -1.1682957135793768 12.753982614078424 M15.947895955403313 12.50718534840714 C9.09735388014193 13.015962183585783, 3.859093964666937 12.263364861976484, -0.40400890636864245 12.712069310836586 M-0.3940707744615911 12.548505916712244 C0.5718840886765646 10.83823815603984, 1.0784481677227495 6.958975438401683, 0.6811690231007677 0.939137292326574 M-0.5139770408579577 12.209975628186587 C-0.26907735731890764 7.260290507492793, 0.4231567017782914 3.1463125241907344, -0.16667044157858651 -0.4311133475963682" stroke="transparent" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(137.17690806005203 10) rotate(0 14.902507806102847 12.090713880423056)"&gt;&lt;path d="M-0.6892515812069178 1.136923560872674 L30.242222497051753 0.41353840567171574 L31.717395791595973 24.685067110560034 L-0.8249499592930079 23.021942728064154" stroke="none" stroke-width="0" fill="#fc54ee"&gt;&lt;/path&gt;&lt;path d="M0.22648257948458195 0.5405943002551794 C6.315562732056079 -1.2964765519575752, 15.70000154246449 -0.6899212688879646, 29.958895979261197 -0.04080186225473881 M0.35451735090464354 0.9070455180481076 C6.630746084515305 -0.00037904585653902867, 14.140627886668447 0.8701175550097787, 30.413975148273803 -0.9142344547435641 M30.140221414899624 0.7193018402904272 C31.3217514927654 8.597681899834354, 30.510180073526357 18.878792901150742, 27.826295731401242 25.394061450711582 M29.710340170933105 -0.07822566758841276 C29.841088208187912 6.031803132773185, 29.864016118277405 12.879690705412616, 29.000467895342208 23.85858704508761 M29.088782666063107 22.90088451360831 C19.296032693008062 24.887641517194606, 10.66106813560243 22.495329765352107, 0.3872703406959772 26.108043496362065 M29.87014377005229 23.98273517550448 C22.268235174484772 25.315298706557957, 12.838883456053484 23.897478580739705, 0.3325612945482135 23.247676897659098 M0.14644611813127995 25.281996075860356 C1.7354599115129878 16.635301946417464, -0.36014242203269686 6.3550838759345325, 1.5077714417129755 1.6352629270404577 M-0.39705940056592226 23.727103043212686 C-0.6547287341754147 17.853970332811464, 0.3756015959580234 9.944831648594322, -0.6921462910249829 -0.5936140669509768" stroke="#000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(140.75580855110945 59.579564489112144) rotate(0 11.799059530240754 8.987265604560992)"&gt;&lt;path d="M1.136923560872674 0.43720688484609127 L24.011657466153224 1.9123801793903112 L24.10175841019543 17.149581249828977 L-1.1594850327819586 16.47251379119937" stroke="none" stroke-width="0" fill="#e08fff"&gt;&lt;/path&gt;&lt;path d="M0.5405943002551794 -0.972532382234931 C6.077665226765294 1.0970884995034595, 13.87250859895412 0.12715755267652468, 23.55731719822677 -1.5466928984969854 M0.9070455180481076 -0.0057982997968792915 C6.702519104674329 0.7838796914875356, 14.165264305375258 0.5253001095115987, 22.683884605737944 0.988635073415935 M24.244574729335465 1.275412213644298 C22.579267645625464 6.663894741379943, 23.150877431059946 10.457398523244194, 24.68794516566749 17.112511783432044 M23.52781557531039 -0.028040412727310615 C23.72076882198848 5.884976510081964, 24.39764287054829 11.061753599772697, 23.307973534432683 18.11456145418283 M22.317575813243707 16.871443793203277 C19.542352360203246 17.55786598738598, 12.226927720029 18.277292798127405, 1.926615735515952 17.6427002996463 M23.399426475139876 18.884744183186037 C19.54458511392089 17.917754896770237, 13.182759995682854 18.322461523900746, -0.9337508631870151 18.886614308718187 M0.9891099762997153 19.35239950287033 C1.7768877133294114 13.580594595726321, 1.039713917294642 11.55274259122118, 1.469654225860444 -0.20063965317028365 M-0.4083136908088776 17.337832976046787 C0.7388284377620029 13.270741554876434, -0.09216481250280911 7.3663276438471055, -0.533496728629208 -0.3097243514335105" stroke="#000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(76.03758884250306 58.6756194766938) rotate(0 10.764576771620057 9.332093190767882)"&gt;&lt;path d="M0.41353840567171574 1.9123801793903112 L22.032792892954035 -0.8249499592930079 L20.369668510458155 17.162168963613148 L0.11721945740282536 16.9646146733185" stroke="none" stroke-width="0" fill="#f938c5"&gt;&lt;/path&gt;&lt;path d="M-1.110622862353921 1.1238113138824701 C6.198477395117356 0.25856936197007985, 13.364288086548594 -1.4420039035824408, 21.116250195256157 0.7424894664436579 M0.11027050483971834 0.8675391180440784 C8.20275901681495 0.610207860934309, 13.529749367474263 0.9898833982821026, 21.932782403840406 -0.4312699632719159 M20.551703393238842 0.5669818110523825 C21.21237088680448 4.065513260734155, 22.72792052130785 10.929052398793285, 22.04839139534486 20.525072203520956 M21.470420605635834 -0.007719740960441812 C22.15866508807139 4.671740479275288, 21.213275394319957 10.990506803212263, 20.67641585064616 18.159673017647346 M22.396499731293602 18.332029911490316 C14.316436369990583 18.93271403048709, 12.080266059835024 18.21361278031543, -1.8098313007503748 20.297467860194082 M22.41962458909927 18.59204104895347 C14.402842989282139 19.192708824840718, 7.397479732055336 19.772230629888707, -0.8366993917152286 18.34784889573806 M1.786022349504847 18.13928574000676 C0.9178582656589649 14.335331945336698, 1.4233365418697481 6.984006899559073, -0.41460257245070076 -1.5401364851359807 M0.6131625023375992 18.964572880368017 C-0.16491867132689514 13.160092163796408, -0.4691701671372353 8.626224684569598, 0.5304938310421726 0.20400276965145225" stroke="#000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(208.8363304496985 59.652533409828656) rotate(0 11.454231944033836 8.642438018354088)"&gt;&lt;path d="M1.9123801793903112 0.5036393497139215 L22.083513928774664 -1.1594850327819586 L21.406446470145056 17.402095494111002 L-1.6995717082172632 17.2589486811952" stroke="none" stroke-width="0" fill="#daaeff"&gt;&lt;/path&gt;&lt;path d="M1.1238113138824701 -0.5040675792843103 C7.645628783750388 0.08641145642274695, 13.436803934892719 -0.2856900293064706, 23.65095335451133 0.7090347018092871 M0.8675391180440784 0.9807671057060361 C9.559400651992314 -0.9394957335239609, 18.88951730526428 0.37836758181989594, 22.477193924795756 0.16760290134698153 M23.433544923949523 -0.35553717606091517 C21.321015155984547 6.855126118495292, 22.202166485051805 12.095095360758775, 24.631827558680307 17.121230710096498 M22.901314647119847 0.596510941645596 C22.207745601461262 4.771237626722614, 23.03772847853919 9.310782283756463, 22.4412347908325 16.975376094521167 M22.576307418022225 15.759910472919167 C17.88995899916615 19.310839616361513, 11.765082519396993 15.574392580095186, 1.6332814786583185 17.415132352401436 M22.836318555485377 16.757526956749082 C16.823660207808143 16.555118411495506, 10.985817038048388 16.652169882967293, -0.31633748579770327 17.358099095773817 M-0.48610972559688204 18.23001918463158 C1.1705819628566982 12.561519730014167, -0.32544157787897565 4.627668881975331, -1.4263181732648549 -0.6863122517991622 M0.2781875024861955 17.05361562926586 C-0.4757293221467767 12.371680590121654, -0.175386883646735 8.975829269946939, 0.1889266701740009 0.1786990019613386" stroke="#000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(65.4665685716327 108.59569422986644) rotate(0 6.281818150930462 6.5734725011127075)"&gt;&lt;path d="M1.3343888614326715 0.28473021648824215 L12.775916405797716 -1.027102867141366 L13.2811365209825 11.600022219789402 L0.49036903120577335 11.750450336110966" stroke="none" stroke-width="0" fill="#f41d92"&gt;&lt;/path&gt;&lt;path d="M-0.2980506168941339 0.6749647943930426 C5.399263025606478 0.5862991074577972, 9.511757369439357 -0.2663333328507295, 12.224003444817319 1.1277315766505231 M-0.600791329632999 -0.15659712891818633 C5.216611504887707 -0.5157867739841867, 9.499124237113 -0.11518759758012044, 13.139655175623721 0.42355575557897907 M11.256037798479353 -0.604401047626477 C13.110714932369563 3.5578859460965324, 11.152806729985327 4.184410098019729, 13.395859230540172 13.57478647815581 M12.385253984037703 0.5561128129199817 C12.067887480056767 5.339097537128964, 12.7816545723657 10.450401690611576, 13.030733187461486 13.370031010304153 M11.999712923962498 12.375982079214948 C9.795735347523918 13.674365335329437, 4.529039531708586 13.32486257075303, -1.1915241219968304 13.41304201253567 M11.985965636960055 13.39705155265722 C9.582689183077557 13.44765344698502, 5.1988831471128725 12.987005587482129, 0.03455255334209162 12.582346491201404 M1.142761063620029 13.41440666724438 C0.4320339571337861 8.704371412304816, 0.7430876350059443 2.0166435737942647, 0.9918849054178738 0.731960321674658 M0.3308142792836719 13.603620601365968 C-0.7247183180055076 10.541206419275358, 0.19644450980978093 7.723298287217505, 0.4571101709669103 0.6472655088192294" stroke="#000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(151.9938275604818 110.5153082979854) rotate(0 7.661128495758078 6.228644914905818)"&gt;&lt;path d="M-0.04964844323694706 -0.5800034906715155 L13.973758786793155 -1.3568401839584112 L15.289737135048313 10.691298270559493 L1.8214433398097754 11.348745787477675" stroke="none" stroke-width="0" fill="#fc54ee"&gt;&lt;/path&gt;&lt;path d="M-0.4890231598455168 -0.16782777315389708 C5.319297083654991 1.3825177738088672, 9.156932125724856 -0.2895407393278089, 15.8151542439081 0.24261378532555367 M-0.590077745967338 0.4328716391619536 C3.6584988883845524 0.5145905795509805, 6.494188914634845 -0.00536263128786485, 15.597254842254081 0.7500212199783822 M14.97713741243248 1.1122164419827383 C14.740783466711616 2.3677191647192926, 15.179779146011029 5.89868242110735, 15.911943253250962 12.295568911069552 M15.198159927491313 0.42130161325275217 C15.43830569993609 3.404264047443032, 14.781056149027693 6.581456422326292, 15.695154303370934 13.04535527703237 M14.776376176810397 13.462057177628509 C10.579013840452832 11.954694213834003, 6.867259514836083 11.309227036028453, 0.067446568787771 13.723226786272054 M15.97654671359191 11.908127536319864 C11.331840038725502 12.277938636190681, 7.2025748687736515 13.137036191152346, 0.5181835605168363 12.111400887887688 M-0.9296305611670861 11.909896680869137 C1.1071458288424063 7.310317839340089, 0.6812797724121024 3.192346754639145, 0.23051173510905376 0.18068946138513353 M0.006337664729383907 11.933374168533385 C-0.2866798420101678 9.541982932091097, 0.5676605867442337 6.346999107767979, 0.6111412676164819 0.2920831382663428" stroke="#000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(207.46039613211508 111.46971170204333) rotate(0 7.661128495758135 6.228644914905818)"&gt;&lt;path d="M-1.3484982047230005 -1.3568401839584112 L15.289737135048313 -1.765991559252143 L17.14370033132593 11.348745787477675 L1.4705324973911047 12.531471395826522" stroke="none" stroke-width="0" fill="#fc54ee"&gt;&lt;/path&gt;&lt;path d="M1.1950592019825934 1.4696172417327977 C6.376128139074211 -0.18565849408848867, 11.428240805780248 0.25939656264094335, 14.199044126368346 0.7303766291005642 M0.18503465607751624 0.4642831630159041 C3.6408633821248553 -0.142244375655332, 8.462203714946092 0.6634468921459915, 14.635521314481398 0.2851350004425315 M14.6944194180848 -0.2528091392177003 C15.098903139475848 4.017817172631002, 15.877431295342594 6.558661800053189, 14.629595752310603 12.372515352986369 M15.446783147618616 0.35973833770509445 C14.764714183905252 3.054801229467944, 15.670334890565968 5.712857900455789, 15.403124591767547 11.90258890173052 M15.374622514561498 12.021049151365606 C12.525349140914596 11.476058708341743, 7.523448116026293 13.82370352105374, -0.18250826386560304 12.898654895079002 M15.746008872314746 12.171244184492295 C10.367290921513428 12.920509280755605, 4.5131973231555635 12.001568429189334, 0.25832804430249157 12.957576659537894 M1.1884943930831493 11.46755613200225 C0.8555913927695539 7.873193274247615, 0.3234747912257624 5.370719159765075, -0.5333483123301106 -1.0212221980448937 M-0.38880206181465715 12.745821568704477 C0.4642708339938675 8.44689672651287, 0.6098737346706051 4.133813659114243, -0.015462126175040058 -0.18063178963993792" stroke="#000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(289.29564173616166 110.9925100000143) rotate(0 7.661128495758135 6.228644914905818)"&gt;&lt;path d="M-1.97082363627851 -1.6277467999607325 L14.356476086999578 0.7899580802768469 L15.388750035192174 14.143001114911023 L-1.0305569674819708 14.441912507599774" stroke="none" stroke-width="0" fill="#fc54ee"&gt;&lt;/path&gt;&lt;path d="M-0.32488331471691567 1.419771815704825 C4.815157983724492 0.4566664162766926, 9.302120985124073 -1.2559954181357016, 16.692964981359903 0.8752369825164386 M-0.05770336926112929 0.18698345366594482 C3.1063299484751568 0.316207911040457, 6.460936555457816 0.13481025420082446, 15.067507408919422 0.6263598639089083 M14.37529464013135 -0.04036218510101208 C15.437921363369561 2.266106634734104, 15.272207646034403 6.269377711116824, 14.649016726282278 13.054154737928483 M15.4076945475191 -0.4523968325485316 C15.408371749049355 3.4886535177004077, 15.039909117060152 7.730612040860872, 15.751101472014158 12.930697750861295 M14.807440321226842 11.378187135258814 C10.359905709361051 12.15886589244586, 6.162224875972772 11.82666784706842, -1.2000628449319772 11.548484244314745 M14.86703786550885 12.223609864507239 C12.338222649412208 12.268908535153864, 8.67840800115318 12.491201533809038, 0.012970510243453193 11.876478838532073 M0.2803017605941174 11.795362327052889 C1.1664129989227872 9.246297649534021, 0.9408064546032111 4.867992651307419, 1.0969654984976633 0.6654273833779427 M0.5073910179343025 12.45158214387215 C-0.31551810317729656 7.827619104442077, -0.331444194565933 2.9677313836548227, -0.10738630764373325 -0.1661580860239843" stroke="#000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(90.52787469172438 109.79410211494044) rotate(0 7.661128495758078 6.228644914905818)"&gt;&lt;path d="M0 0 C0 0, 0 0, 0 0 M0 0 C0 0, 0 0, 0 0 M-0.9855415314109871 6.104875547488325 C1.7837569587222952 4.932180392868143, 3.1944827736231227 2.487677039629971, 5.551522109242624 0.06308968996975961 M-0.4430160846235106 6.350717425768368 C1.4988444926306697 4.1860347776332985, 3.3916741988599117 2.2148890309990126, 5.092392704593486 0.6275978714076096 M0.2962450110446322 13.023106728910982 C3.1488409544488674 9.460784958652166, 6.920621684249594 4.6286478673248155, 10.762616249871385 0.34760843916682016 M0.6590376467536794 11.786425040334045 C3.0548091519057943 9.423868256438187, 6.172882898979238 6.001518376199493, 10.169267914396656 0.4301067384972932 M3.5758409941984874 15.480457930925578 C8.013590079766397 11.7734095533626, 8.51322905212178 6.343551420478306, 15.58034339044564 0.3937655381788483 M2.33218608386317 15.414281134826407 C5.761220127324565 11.405054314196358, 8.539724125972308 6.954283024158126, 15.874103708894406 0.7980973786138912 M8.39934875499379 15.962933720174263 C10.373039802720829 11.845152632003154, 11.430754881852373 11.524488008558258, 16.02887313909685 7.753400968057672 M7.776602889922597 15.097280868646706 C9.922554730482974 12.9795449061011, 11.748116921537898 10.624812347374696, 15.53925534336684 6.91278328476065 M-0.13035152897861035 12.343976974416462 C-0.13035152897861035 12.343976974416462, -0.13035152897861035 12.343976974416462, -0.13035152897861035 12.343976974416462 M-0.13035152897861035 12.343976974416462 C-0.13035152897861035 12.343976974416462, -0.13035152897861035 12.343976974416462, -0.13035152897861035 12.343976974416462 M6.543177192254394 12.6780481665292 C4.268047541074683 10.655356012896254, 2.686386669487847 9.4967170875931, -0.2715383813744019 7.783296766476174 M6.158545916496246 12.541736045003793 C3.7494885913637783 10.335128716741936, 1.854284278965847 8.973623352139883, 0.2134420472169688 7.073565300525566 M12.917822568082128 12.344241205429629 C9.134862759794474 10.109097269591013, 2.754645868802969 4.810150916117756, 0.9859576439690705 0.17389581499154816 M11.238799469881569 12.2202679662164 C8.540765792435549 10.096439147123853, 6.485162503370937 7.191588513956833, 0.2068276627194997 1.9905709169572137 M16.299668406971392 10.11407459163953 C11.046811758573647 7.0780266954063435, 5.687104394536872 2.964908445194016, 1.2905474721669812 -0.5608112632293618 M16.053550339207646 11.748825567866755 C12.430041167557496 5.7421700983916, 6.887212240406566 1.649515262977829, 2.2155003675475378 -2.1595983483825245 M17.29174834463902 6.604705439698552 C13.19769496484621 2.8878448872661613, 10.917674795492632 1.280137029873109, 7.315838638205338 -0.9017668718794847 M16.523748635008637 5.590721871382227 C14.5688033293149 4.348919797214966, 12.717747446420107 2.2139241360542186, 8.555095291907225 -2.0438360281145687" stroke="#ced4da" stroke-width="0.5" fill="none"&gt;&lt;/path&gt;&lt;path d="M0.32863426032948206 -1.16050865139706 C4.03329250966049 0.4574570621736611, 9.362066805710816 -1.1333591905818587, 13.866439489765483 0.23283888293376842 M0.5971500470663431 -0.7321561999014279 C3.9607174064863937 0.21148493090412762, 7.517364089120832 -0.7196843435550484, 15.09912584011393 -0.7493700001900138 M15.88564038491305 0.8160841826622598 C16.109894335929912 3.0865936196991637, 15.08971903039006 8.010372874837852, 14.138779171873637 11.439159416004774 M15.35862133532118 0.11110279187302885 C15.591138662261516 4.2549237868635705, 15.624450531475691 8.079864246372264, 15.113460969971412 12.857735144021456 M15.467799624723671 11.15868108051764 C12.199375767919626 11.133291998971648, 5.559716656554162 12.930811942776192, 0.24127098458311913 13.933673089274768 M15.137709607612562 12.530736286944132 C10.874645695005233 11.666211306068734, 7.526960006210287 12.287384680987214, -0.3696972877764938 12.632229589547448 M0.16319129735661364 11.874316158028892 C0.2915149107468349 9.0830452492131, -0.3044541875623684 3.0101157390312787, -0.4497359659243041 -1.131553702329326 M-0.40129058097077375 12.845341146685607 C-0.4546232719119787 10.10395521211974, -0.5578944677177882 6.520313775375806, -0.26667073712778694 -0.34557075104177193" stroke="transparent" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(120.87270227793113 109.79410211494042) rotate(0 7.661128495758078 6.228644914905818)"&gt;&lt;path d="M0 0 C0 0, 0 0, 0 0 M0 0 C0 0, 0 0, 0 0 M-0.17990700897293777 6.888908154566876 C1.2463909627291798 5.107747269437469, 3.132281327629543 2.6916787237737942, 5.053278610440439 0.5511590096947961 M0.0014893088815858824 6.270567310278408 C1.1993750614576435 5.08928891833048, 2.758411934994365 3.3781139782111325, 4.7566044427030745 0.5924081593600325 M0.49007783765901203 12.727480231642305 C4.227129699189883 9.605755282115588, 4.647878307489152 5.033243170213026, 10.599132487130296 0.022897164066112197 M-0.5572105078865184 12.671752403348266 C2.3303971076599193 9.295561396501908, 4.67018994862644 5.547543468048661, 10.846509597402942 0.36338713495878006 M3.60167854376571 16.28481309032747 C7.0107812625669625 9.172282119850104, 8.837743671975991 8.618406861172552, 16.77994793448918 2.104711063944267 M2.5260265950063774 14.789594528598055 C6.232670683247028 11.131686956928373, 9.385914467796443 7.064421628219126, 15.934244469137345 0.6527350655221364 M8.384125356189125 15.914250020208787 C9.824293061508781 13.441837364723604, 11.870725616326723 9.859611843594644, 14.830805864407175 7.247984422785799 M8.445919439521768 14.476446570214833 C10.729554174606625 12.08795102809125, 13.276549308889726 9.405689313978215, 15.105942469824532 6.967064523972208 M-0.13035152897861035 12.343976974416462 C-0.13035152897861035 12.343976974416462, -0.13035152897861035 12.343976974416462, -0.13035152897861035 12.343976974416462 M-0.13035152897861035 12.343976974416462 C-0.13035152897861035 12.343976974416462, -0.13035152897861035 12.343976974416462, -0.13035152897861035 12.343976974416462 M6.771090309663146 12.672138604418299 C4.879610405519318 11.55456663649899, 1.6895019600235655 8.905093459762362, 0.8051578476066164 6.586965909199257 M5.931578760562866 12.610151984811685 C4.582561921839856 11.54823757526541, 3.5547602773075497 10.095812258681901, 0.4155928569818308 7.4953034601820905 M11.511144225262914 11.373954979157046 C7.308858906544717 8.945116662170495, 3.021093015315299 5.654622062000635, -0.49615252258061204 2.8340462952619334 M11.314249771051918 12.681755760138826 C8.415442433731798 7.876431384558703, 3.981179292011053 4.602307516227686, 0.24380979372383316 1.5550166271394037 M18.412469541557726 13.137642208579075 C10.968736123752617 6.379713931429274, 6.8232449067461065 3.45660873616918, 0.27445189349647947 -0.5104892670173562 M17.016106433138845 11.29403572073121 C13.461660422786595 9.036213767699827, 10.09610427206879 5.154403474680287, 2.5276458093180914 -2.5869786419902363 M16.521333428958137 4.506310607683515 C14.000953745478322 4.055717757096405, 12.1557081715277 2.1355430865347023, 8.23977406050663 -2.214996966193106 M16.833772057547872 5.231351262754431 C12.799186656019536 2.1771499802033367, 9.73942584662509 0.17833581534983434, 7.619131442183885 -1.6548260234153385" stroke="#ced4da" stroke-width="0.5" fill="none"&gt;&lt;/path&gt;&lt;path d="M0.6929520992376381 1.003769820265283 C5.41866534890859 -0.6389968058993493, 8.650596775617139 0.9304389769527437, 13.866599176368728 -1.252283286682054 M0.04472753132668006 0.13665456554439193 C5.219327143213944 0.24133451703068487, 10.258728779704331 -0.052481640880421176, 15.065441396421095 0.49254100203142515 M15.440585953461115 -1.05579390650096 C16.478413435982205 2.5744875399601206, 14.655696346018257 7.611359405752805, 15.518414945539515 13.657617834569526 M15.172216416560815 0.05971338321625397 C15.176754207722166 2.8261689845081666, 15.878869509598957 6.7550430907653, 15.021685981274189 12.59951922140801 M15.522979542812433 11.740242017616833 C10.246552908093726 13.856743542312778, 4.074215476699009 11.826447838759469, -0.5531676746999261 11.065497805731091 M14.828676325583942 12.934586423554704 C11.456407713018752 12.933178743708217, 7.957498171655342 11.8972477512981, -0.3280005219603038 12.032243617629756 M-0.06962340821032598 12.642548192348771 C0.7055795318103719 7.440017518008115, 0.17225115013558298 2.6215059072792326, 0.2340742721675828 0.23731616223522178 M0.41184134132211103 11.861837393048491 C-0.3615948073035772 8.912785868461592, 0.22450442722757052 5.2560767257373024, 0.5860050160967799 0.2801154543877312" stroke="transparent" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(180.18304710551718 111.86306763218175) rotate(0 7.661128495758021 6.228644914905818)"&gt;&lt;path d="M0 0 C0 0, 0 0, 0 0 M0 0 C0 0, 0 0, 0 0 M0.36741164031905427 5.70742812832176 C1.9941659641708465 4.095148712769747, 3.2892458185013913 1.9981303918740876, 4.566991302701181 0.5765271279809289 M-0.6411611046315977 6.492456615393154 C1.933201686907118 4.399261303496065, 3.061696058164395 1.9636502463191765, 5.3195265389555155 0.6049163609591537 M-1.4502702153195037 10.986614905714571 C4.546974056741427 7.386263956875403, 8.112494362703098 2.900097586198785, 9.22516627008928 0.499634377200878 M-0.19904209663163552 12.219132087796508 C4.3371437349716775 7.836328935604952, 7.604652859491832 3.9154690970177044, 10.005276153189076 -0.41619851262422786 M1.8376595789982537 15.725364800456255 C5.540665366158851 10.195988750455157, 11.047001735337934 5.667365046228224, 15.223712478022506 -0.5370080389152296 M2.6601734522680354 14.992541406077608 C7.583140852165204 9.262623883770514, 11.148868072396276 4.332465756773966, 15.240170958967362 -0.2696743497229197 M7.582646445742958 14.725763873281906 C10.469161735529571 11.07602843976743, 12.133900636452223 8.797385242711304, 15.65084337241804 6.678533615214388 M7.890138432708719 15.37146669751128 C9.801442690995792 13.589765561938973, 11.949423537318514 10.925059864090898, 15.318101984251717 7.118985438622172 M-0.13035152897861035 12.343976974416462 C-0.13035152897861035 12.343976974416462, -0.13035152897861035 12.343976974416462, -0.13035152897861035 12.343976974416462 M-0.13035152897861035 12.343976974416462 C-0.13035152897861035 12.343976974416462, -0.13035152897861035 12.343976974416462, -0.13035152897861035 12.343976974416462 M6.655164330048888 12.009176644579432 C4.478012814696061 11.470817385642604, 1.0561147846853483 9.32326606913686, -0.08538971403889156 7.76561771889164 M6.216742067752808 12.928153350473305 C4.974019891585312 10.805768054131272, 2.7096541436186743 9.397657587678037, 0.24246148643739723 7.070915909828087 M13.443525664471899 12.558407036041812 C7.726189962252176 8.059476762409973, 3.6844192502108886 3.7113895882687733, -1.4044204974341277 3.030632141209199 M12.20968624871305 12.295968940680817 C8.321436974437704 9.69883460563286, 6.615100183290415 7.602661073840639, 0.37461004091621597 1.8137488506501458 M15.239292776115832 12.721997562888157 C11.81675593396629 6.947692396233367, 7.4392804101145416 4.36498607599588, 3.15495554797646 -3.7185838638801494 M16.759074515566823 12.106342601682138 C12.891379073417461 7.818396522288394, 8.451106851925806 4.164195325532596, 1.1453850099101324 -1.6980268635783708 M17.298469325640877 5.433179380190229 C12.96114924135062 3.013980272925081, 11.398811447044116 0.6831175753889231, 7.8492173912714005 -0.7759776199559236 M16.06352495576412 5.733501059240331 C15.163797580258239 4.461884145945868, 13.306182794361252 2.6708795083398114, 8.414163728827868 -1.4513174317634219" stroke="#ced4da" stroke-width="0.5" fill="none"&gt;&lt;/path&gt;&lt;path d="M-0.5594314353673195 -0.369094767807187 C6.21995327118297 -0.530040805382757, 10.675849953423619 1.1971392217347163, 15.094332980940775 -0.7393945755529876 M0.5520616875747457 0.1003612756481389 C4.5880327108290695 0.1767577478479005, 10.537633921346039 -0.18975823628866867, 14.951629827232598 -0.27658383734996306 M15.310575607632055 -0.8025811619415475 C15.998781890584402 1.8024327180082351, 15.99882922574577 4.201523573010781, 14.413943291230698 11.923948355556062 M15.019695175763939 -0.03481170410516299 C15.517103685462132 3.4279345424874914, 15.295519386150598 6.207315798510043, 15.16372710596754 12.574326965895427 M16.469756984061956 13.470405677267763 C8.968892039030631 11.500784550936933, 5.440374414696131 12.94256895455997, -0.48549818196419525 13.898842459255146 M15.142074138837888 11.763876581709361 C9.756004120015684 12.679712623206022, 5.358421299634263 12.272104988215492, 0.2897218734012925 12.997884626810238 M-0.588049678695103 11.89185574728466 C-0.1869595591162626 9.333873044151947, -0.7551682002479978 6.520621728306203, -0.6031024456379208 0.32817888171464604 M0.18741164192816273 12.520782934944457 C0.3718726584062983 7.772787374954364, 0.44728497702461295 3.4104925205127388, 0.026582263547794138 0.051591244972721384" stroke="transparent" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(235.3554608986209 109.10444694252658) rotate(0 7.661128495758021 6.228644914905818)"&gt;&lt;path d="M0 0 C0 0, 0 0, 0 0 M0 0 C0 0, 0 0, 0 0 M-0.5323107883008072 5.606076218982332 C1.0506465119020651 4.353168144563857, 3.5051128751055476 1.8891143054474946, 5.152168347271627 -0.34235441201950967 M-0.07287498597520087 6.2316902783262655 C1.5566417016122425 4.728592552443554, 3.1589401478755677 2.594220610280873, 4.987472982662267 0.0477005295303875 M-0.764476520465721 10.934059192657053 C3.616660441408613 7.3617053322105, 5.748174750345383 5.302484458508978, 11.391119629097116 -0.3657729555770992 M-0.1715098812152041 11.626702454357922 C3.027903014675699 9.078005131490022, 4.9928292024835805 5.392218668496865, 10.204121511381995 -0.3519131821498512 M4.2578534584908345 13.704121786828429 C5.516804442595644 10.892778457924608, 7.309740179758776 5.9611029218001494, 15.064564594257106 0.8354947214876285 M3.410065575963498 14.235244309769287 C7.540369349496765 10.445212424218505, 11.471352743355446 7.055605169261649, 15.140716430035834 0.26075959647307 M7.56453540176075 16.087559808130393 C9.979359888317914 11.992055580534126, 12.903836165910057 9.323181838782183, 15.871613230964595 6.6603755159728335 M8.1267703341778 15.429982511551136 C10.473280924457287 11.80688428728262, 13.086412208058738 9.419702213516878, 15.415498541191646 7.148185485969867 M-0.13035152897861035 12.343976974416462 C-0.13035152897861035 12.343976974416462, -0.13035152897861035 12.343976974416462, -0.13035152897861035 12.343976974416462 M-0.13035152897861035 12.343976974416462 C-0.13035152897861035 12.343976974416462, -0.13035152897861035 12.343976974416462, -0.13035152897861035 12.343976974416462 M6.633647534592479 13.37194316823866 C5.023614917110692 11.45585948764098, 3.3221635991854024 10.48494995004123, -0.13721852312139965 6.737174497143766 M6.244448700515955 12.755023460359237 C4.034671910479897 10.224055407653893, 1.677394811493194 8.797777616313343, 0.4211475665653517 7.626689766318937 M13.277695347332273 10.963168389176921 C9.435816621394908 8.211808570665102, 3.5970624880976834 4.486487411377835, -0.389752405302513 2.8741074263816615 M12.581683576815639 12.178993780737713 C10.097468489777464 10.47026327196649, 7.238810196222479 8.412225573839287, 0.6059591470492002 1.2664509959285999 M16.931407378890214 12.785988085064185 C11.659468726341824 5.962333188153747, 6.878246127659667 4.07739025064636, 2.0106928876816887 -2.421458952780477 M17.42590209925234 10.540634685288262 C11.524823911534295 7.127350790789602, 5.978157700384616 1.7667571167768479, 1.7462903929158706 -1.3942837935869015 M15.668211576269647 5.261359481517827 C13.046147850562134 3.0351585610070524, 10.025919934117846 1.0203475479969917, 7.19861298247825 -1.1013944741661215 M16.4499837862064 5.5877053385178055 C12.759607859827291 2.676591374245515, 9.904639962057052 -0.18832885707109137, 8.16365571983743 -1.4234194091378267" stroke="#ced4da" stroke-width="0.5" fill="none"&gt;&lt;/path&gt;&lt;path d="M0.6521740765027317 -0.014367905727007857 C3.056348090890494 1.077072126504154, 7.099095703064359 1.0771303479565812, 14.080290180893172 -1.117210575881672 M0.357916861836062 -0.37214594507393983 C3.897858242927035 -0.011793486382127877, 8.351069272310946 -0.284338448064834, 15.46398168662541 -0.19498909316523905 M15.140698370715334 0.9329395789905266 C16.1696712903839 2.7803563475847843, 14.586487268972359 7.857367940622688, 15.734954121811132 12.06257046386422 M15.388423066804354 -0.14649212706837195 C14.814521766094481 4.681144124933373, 15.545880879166326 10.312439493921179, 15.158464936130473 12.692839288941205 M16.15807650672912 11.733998614479841 C8.948915887683713 12.32666373948781, 4.152577924241559 11.62777666668441, 0.7536069429552348 11.715483964288488 M14.868031488798337 12.68780299760784 C10.400020840190674 12.940533995072634, 5.181051175981136 13.03328987873448, 0.11522471015014157 12.48998556693102 M-1.2398805346856494 11.987258365848346 C0.7891492271474708 8.220726543980104, -0.2715888590016168 5.9363170602361715, 0.009313864740602806 1.1271310204189275 M0.13851265328863815 12.774125065758799 C0.06660439087724673 9.70497212687148, 0.5193099107472823 6.6556894120831815, -0.257097009312332 -0.3776080572264844" stroke="transparent" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(260.1830471055174 109.10444694252658) rotate(0 7.661128495758135 6.228644914905818)"&gt;&lt;path d="M0 0 C0 0, 0 0, 0 0 M0 0 C0 0, 0 0, 0 0 M-0.5660293310554574 6.778298909266583 C1.5005700697896844 3.8740094959367646, 3.888061078483078 1.7924914670934593, 4.6823601457797945 -0.05404862427737178 M-0.530856939745123 6.184799850409023 C1.329800196194094 4.156057980507952, 3.4571234448483166 2.6005126571272275, 5.219830093490428 0.1577973157015854 M-1.4086926854122772 11.575561057447969 C3.4450884320537836 8.774673877878202, 4.745468169500141 6.261404238795016, 11.064754053902757 -0.7584531624719738 M0.9188013405631887 11.639688919156372 C3.878405681683727 8.14056674278562, 6.972964896043772 2.7219518266729446, 10.212298057180774 0.7381290706887604 M3.8977376992298822 15.556306501391619 C5.73435983873332 12.527384854568659, 9.193260449775455 9.979156380407193, 15.574860391955305 1.723945411287712 M3.4998199466566158 14.768472037238345 C7.389117309106338 10.529988369256705, 10.74889041979753 7.259601915433032, 16.04075838658348 0.7860441079759022 M7.15316062401902 15.815663437071274 C9.2839274020957 13.644911380718113, 11.125928054283278 11.158928847965003, 14.661932959629649 6.5839979067869585 M7.894978580203988 14.710226441678609 C10.821619687037325 11.524109719036767, 13.950275948433275 8.233747946626105, 15.607421272982338 7.250371754444774 M-0.13035152897861035 12.343976974416462 C-0.13035152897861035 12.343976974416462, -0.13035152897861035 12.343976974416462, -0.13035152897861035 12.343976974416462 M-0.13035152897861035 12.343976974416462 C-0.13035152897861035 12.343976974416462, -0.13035152897861035 12.343976974416462, -0.13035152897861035 12.343976974416462 M5.629200165315002 12.492980775133631 C4.5920169520594625 9.887211951553695, 1.794171426664012 8.220396201681634, 0.9009324981803468 7.325123753742715 M6.62384493869397 12.990836551309487 C3.6254380705323883 10.674185057531117, 1.6544275639062012 8.649133956521277, 0.25626296577699686 7.682125111640831 M13.048884932660375 12.45564176199396 C8.062756210212067 10.484691498035609, 3.3388711114235736 7.238739457159812, 0.8384021496605989 0.7226300507550979 M12.36858803551725 12.244119766181306 C8.172195278801519 9.08737216747763, 4.054883597707195 5.778521976792255, -0.7001612679827097 1.5278969906037592 M17.56418338967174 9.51680452013303 C13.987377469178806 9.371215018238136, 7.327485118167129 6.199560007345894, 1.8709136664151806 -1.7042487083930886 M16.370749539599416 11.271427678967905 C14.214905597243503 7.424003480531246, 10.714030395892983 6.08861716021963, 1.117068273688819 -2.575201002601198 M17.0745033708603 5.172576166879987 C13.134525068497116 3.3023025038347287, 10.959671749697593 1.7301966993347677, 8.072638368863869 -2.6219682539204623 M16.767710882678333 5.292379135316839 C13.78784838497269 3.3063549707083486, 11.385884543842904 0.9838796626769983, 8.278524597758684 -1.687813272312891" stroke="#ced4da" stroke-width="0.5" fill="none"&gt;&lt;/path&gt;&lt;path d="M-0.10473860489052567 0.5076105297725482 C5.124294762946779 1.5349700168285583, 11.908660485991804 0.8577719191306236, 15.4850233112432 -0.3603657053565359 M0.6076676953675075 -0.20146147356884891 C3.499572474651589 0.49936556471426696, 6.135264870903162 0.20709832575715237, 15.740166749122638 -0.3616456076658977 M14.607467808079651 0.6126969487164788 C14.704496491647758 3.499931748131162, 16.140650596488406 6.448576475733482, 14.583668839838397 12.832113113667962 M15.780049042830814 0.09367990699354001 C15.425315201603219 3.9157644041818527, 15.547482036942519 7.614240793899875, 14.70231672417333 12.22227409782999 M16.748749800361438 12.468745726807644 C11.104700037800413 11.544940029237884, 5.004281762865903 11.627521748335944, 0.34073646824630077 13.236693853555618 M15.614532229501496 12.141064832059188 C9.919315157083043 12.588012164334614, 5.472756907558488 12.204900828725892, -0.7078376429081014 13.059520093880272 M0.5179209595159548 11.239238192513948 C-0.7522856114352774 10.13209875287417, 0.5213260091281064 6.7596753566437915, -0.5214804406721543 -1.1825215681706165 M-0.5700580102239778 12.094583379098811 C0.41157710960632443 8.806468942745342, 0.47703382283467965 4.021015417475526, -0.21088164588019565 -0.6164083438358398" stroke="transparent" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(148.0210102608686 35.884815995363454) rotate(0 -23.971252877089796 9.31638414407385)"&gt;&lt;path d="M0 0 C-14.643344386807778 5.691109353397764, -29.286688773615555 11.382218706795529, -47.94250575417959 18.6327682881477 M0 0 C-11.275560284562957 4.382226143509122, -22.551120569125914 8.764452287018244, -47.94250575417959 18.6327682881477" stroke="#ccc" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(151.4333980754019 36.28321350685066) rotate(0 0.08027251050077666 11.80897408051031)"&gt;&lt;path d="M0 0 C0.051162149233997305 7.526517987769038, 0.10232429846799461 15.053035975538076, 0.16054502100155332 23.61794816102062 M0 0 C0.03292207247257026 4.843200967303777, 0.06584414494514051 9.686401934607554, 0.16054502100155332 23.61794816102062" stroke="#ccc" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(154.36003610032083 34.93365429834115) rotate(0 26.90922922078005 10.624632932971565)"&gt;&lt;path d="M0 0 C12.516916978464527 4.942083151380956, 25.033833956929055 9.884166302761912, 53.8184584415601 21.24926586594313 M0 0 C17.911966441767866 7.072222953325145, 35.82393288353573 14.14444590665029, 53.8184584415601 21.24926586594313" stroke="#ccc" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(71.20483722401627 80.15212228048075) rotate(0 -23.100814592801328 11.698068685788046)"&gt;&lt;path d="M0 0 C-13.340347275402854 6.755445704869569, -26.680694550805708 13.510891409739138, -46.201629185602656 23.39613737157609 M0 0 C-14.285602182183645 7.23411526782713, -28.57120436436729 14.46823053565426, -46.201629185602656 23.39613737157609" stroke="#ccc" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(81.4575686941929 83.8873990919624) rotate(0 -14.554766232255702 10.28985587741582)"&gt;&lt;path d="M0 0 C-10.98264786990617 7.764457493195205, -21.96529573981234 15.52891498639041, -29.109532464511403 20.579711754831635 M0 0 C-10.938602576674775 7.733318571951305, -21.87720515334955 15.46663714390261, -29.109532464511403 20.579711754831635" stroke="#ccc" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(92.71052560552914 84.93456877733809) rotate(0 3.1552117382235565 10.07488288030119)"&gt;&lt;path d="M0 0 C2.2683405328688284 7.2430211020196875, 4.536681065737657 14.486042204039375, 6.310423476447113 20.149765760602378 M0 0 C1.5530035148384072 4.958883847681873, 3.1060070296768143 9.917767695363747, 6.310423476447113 20.149765760602378" stroke="#ccc" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(143.81412547848242 81.49718680857619) rotate(0 -5.565999043464103 11.056965635904577)"&gt;&lt;path d="M0 0 C-2.3605525059325987 4.689283583419557, -4.721105011865197 9.378567166839114, -11.131998086928206 22.11393127180915 M0 0 C-3.976737750394021 7.89986708688782, -7.953475500788042 15.79973417377564, -11.131998086928206 22.11393127180915" stroke="#ccc" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(157.79351462670945 82.56881322977169) rotate(0 0.4877464437154231 10.995352828391818)"&gt;&lt;path d="M0 0 C0.3523996010162537 7.944205436419236, 0.7047992020325075 15.888410872838472, 0.9754928874308462 21.990705656783632 M0 0 C0.23704197662084248 5.343678465867512, 0.47408395324168495 10.687356931735025, 0.9754928874308462 21.990705656783632" stroke="#ccc" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(171.0109003680185 84.63672563239413) rotate(0 7.832070467882659 11.116733664246953)"&gt;&lt;path d="M0 0 C3.250898556179442 4.6142809320639095, 6.501797112358884 9.228561864127819, 15.664140935765317 22.2334673284939 M0 0 C4.5253930646618254 6.423280999854955, 9.050786129323651 12.84656199970991, 15.664140935765317 22.2334673284939" stroke="#ccc" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(218.44693905993915 81.66042333333068) rotate(0 -2.497168498125802 13.759874370826923)"&gt;&lt;path d="M0 0 C-1.8079588683835757 9.962197951500483, -3.6159177367671513 19.924395903000967, -4.994336996251604 27.519748741653842 M0 0 C-1.5365421491108837 8.466640097820521, -3.0730842982217674 16.933280195641043, -4.994336996251604 27.519748741653842" stroke="#ccc" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(224.8630249115887 83.77721257796638) rotate(0 8.393870813360422 10.293591016739073)"&gt;&lt;path d="M0 0 C6.589048878704789 8.08029761891666, 13.178097757409578 16.16059523783332, 16.787741626720845 20.587182033478143 M0 0 C6.510040000865417 7.9834072696066025, 13.020080001730834 15.966814539213205, 16.787741626720845 20.587182033478143" stroke="#ccc" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(231.67339697531736 82.31858859402979) rotate(0 15.20624662486216 11.55343980935841)"&gt;&lt;path d="M0 0 C8.612502377846084 6.543628436731532, 17.22500475569217 13.087256873463064, 30.41249324972432 23.106879618716818 M0 0 C9.289055824504032 7.0576618939422415, 18.578111649008065 14.115323787884483, 30.41249324972432 23.106879618716818" stroke="#ccc" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(233.9060627600553 81.27525822827378) rotate(0 27.449387833183778 13.390851929034325)"&gt;&lt;path d="M0 0 C13.340245188556029 6.507877301402344, 26.680490377112058 13.015754602804687, 54.898775666367555 26.78170385806865 M0 0 C16.504549297784564 8.051544797472674, 33.00909859556913 16.10308959494535, 54.898775666367555 26.78170385806865" stroke="#ccc" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g transform="translate(93.48756125873672 296.15035056425285) rotate(0 6.281818150930462 6.573472501112704)"&gt;&lt;path d="M-1.0481440220028162 1.0399139020591974 L14.247602601379867 -1.2627559211105108 L14.072568376392837 14.80711473218836 L-1.9469649586826563 11.20142443172373" stroke="none" stroke-width="0" fill="#f41d92"&gt;&lt;/path&gt;&lt;path d="M-0.9302226919813625 -0.3434217814047571 C3.126674595896559 0.8471740464244658, 8.923615430391688 0.3069009453128152, 13.605127155423105 -0.8420332267100237 M-0.46818925146349366 -0.4943776068716876 C4.385062233064431 -0.1613314331863579, 7.439812459435915 0.6084334649972717, 12.651749926654212 0.5418942168523657 M11.491539432112859 0.4463071512517389 C13.554224330711994 3.5212727741481733, 13.44494251160216 10.102772877534235, 13.091883152828864 12.33616175923646 M12.119324715333018 0.561483341972385 C12.1188182127935 5.289246859829869, 12.771397082599039 9.610384216516465, 12.312145744990739 13.335289376396702 M11.691406509554149 13.000615358163635 C7.715706545076113 13.197389991424775, 4.058742761156982 13.636908849246149, -0.6670739142189769 13.42187340754191 M13.029577927124652 13.193430516230412 C9.987684687483556 13.158171384218846, 7.660899297088668 13.512479694659788, 0.2611278410700292 12.95265963653631 M1.1558128573929323 12.38418377224846 C0.058841857271697495 9.010535033433078, 0.8336101418223658 2.6282846327386196, -0.16117039798512778 0.5057711768986115 M-0.6192909244193764 12.878278414654526 C0.6716345808381846 8.441358257382207, 0.19797994362715898 4.362724373226808, -0.26037401433240664 -0.03462371210619508" stroke="#000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(138.61318726750665 201.58598281536746) rotate(0 14.902507806102847 12.09071388042306)"&gt;&lt;path d="M1.6839662995189428 -1.2627559211105108 L31.313947686737574 1.660169729962945 L27.858050653523005 22.235907190344427 L-0.22345868684351444 25.60715513756046" stroke="none" stroke-width="0" fill="#fc54ee"&gt;&lt;/path&gt;&lt;path d="M-1.3548203613609076 1.4663367476314306 C12.23703141501568 0.6877490337264924, 23.049717923447933 -1.258958393775186, 29.94872760567645 1.1216368284076452 M0.8368502063676715 -0.40368842612951994 C6.308821332682734 1.0562618606503382, 13.645945771680902 1.097203903716363, 30.258644162489272 -0.8962492598220706 M31.49121331963519 -1.5504646692425013 C31.127773776666686 6.758283132989625, 30.411426737920806 9.174168615275462, 31.5352264026163 23.186491672746037 M29.227822600675918 0.316140447743237 C30.05712246369953 8.155130397104713, 29.258985424753376 18.277724968353397, 30.502997665478087 24.473121274127756 M29.483580646848477 24.194215004197453 C21.11037816198577 24.859169100537464, 8.84778091699123 24.584371005311176, -1.8407668378204107 24.52539933179984 M29.863602190090514 24.214128840579782 C21.598332696959524 24.800206495650094, 12.779698450105386 23.8942014409378, 0.6456922991201282 24.39439858855227 M0.1321082804352045 25.180232648602818 C1.0981460312018734 14.746798717522147, -0.4577739140381473 8.919403833682686, 0.30974639393389225 -0.2318184170871973 M0.9985529882833362 23.332725185766016 C0.5058130405034518 13.894355941444005, -0.1682833948050046 5.04189844791545, -0.5240720110014081 0.5199569510295987" stroke="#000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(171.42285698933347 249.62708576601804) rotate(0 11.799059530240754 8.987265604560989)"&gt;&lt;path d="M-1.2627559211105108 1.5089320745319128 L25.258288790444453 -1.9469649586826563 L21.652598489979823 17.75107252227847 L1.4257273767143488 19.260732587452573" stroke="none" stroke-width="0" fill="#e08fff"&gt;&lt;/path&gt;&lt;path d="M1.4663367476314306 1.5411449167877436 C5.938488469858327 1.817800987956855, 9.323991251045184 0.3035680544355844, 24.719755888889154 -1.4686559345573187 M-0.40368842612951994 -0.44401769805699587 C6.142344917649696 -0.057213197855086106, 11.503934787612994 0.2561476422270825, 22.701869800659438 -0.8480208711698651 M22.204675281184493 1.366020329997454 C25.05620003327256 4.6488064586976545, 22.550633692534493 9.408158870380177, 22.70394357214968 19.268779529007034 M23.88224287770284 0.37346853077435904 C22.99629756443374 3.8335934428187404, 24.009156134221953 8.982158745655758, 23.860271768380475 18.277922978274244 M23.61090630383285 19.22755502166931 C18.86943699426957 16.87469273652848, 13.153501360853754 16.09583806838807, 0.34397157095372677 17.826232716466826 M23.630820140215178 18.488351033094865 C15.365061821711071 18.404044855862477, 5.629276533297297 18.63404883864484, 0.21297082770615816 18.289355413082582 M0.8976524813403239 19.152525278445573 C-1.1709279814747136 10.781589534549676, 0.7957763081385391 4.307730194025288, -0.2083413686391542 1.701459044047568 M-0.7627515461519696 18.224375672868646 C-0.5948674996519207 13.919579681237186, 0.026126693215926047 10.105598531421434, 0.46729912218407166 0.7567126201453225" stroke="#000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(106.70463728072718 248.7231407535997) rotate(0 10.764576771620057 9.332093190767878)"&gt;&lt;path d="M1.5089320745319128 1.660169729962945 L19.582188584557457 -1.9455205705016851 L21.3056948563966 20.089913758250113 L1.2862013783305883 16.89091977736556" stroke="none" stroke-width="0" fill="#f938c5"&gt;&lt;/path&gt;&lt;path d="M1.5411449167877436 0.6062782611697912 C7.528200517594965 -1.4982741112611493, 11.8842231746766 0.9637918834783833, 20.060497608682795 -1.4906170163303614 M-0.44401769805699587 0.8216970907524228 C4.904674467107502 0.9495498349161614, 9.98244206409573 -0.8093385587243568, 20.681132672070248 -0.8154722405597568 M22.947585976521076 1.450664879471047 C22.436441785708272 5.081823940215335, 23.30115991825645 11.53723664279354, 22.8730601845474 17.402643983506568 M21.916951488283235 -0.7843355038412898 C21.064394186185222 7.460871216624397, 22.072788520449183 14.65918374900089, 21.844185988882305 18.016306300482693 M22.78217735578744 19.376641853513355 C14.259247027626172 19.103220250914596, 7.272398013551818 19.00953444511702, -0.14829849265515804 20.147647424401875 M22.042973367212994 19.260909376466806 C15.32677985582176 18.36194458742067, 8.938721834848007 18.88419747490808, 0.3148242039605975 19.543335763538416 M1.223191893596719 17.340045562217615 C-0.24113556688164858 14.865814647311284, 0.276729753526604 9.415062015753866, 1.7667414159061516 -1.7583645836808892 M0.25943061232083775 17.970211710200108 C-0.5560142539170538 13.778606770753926, -0.6754406753854238 9.476563601161759, 0.7857465218611658 -0.5892077966498611" stroke="#000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(128.44130931754898 297.1047539683108) rotate(0 6.281818150930462 6.573472501112704)"&gt;&lt;path d="M-1.9469649586826563 -1.9455205705016851 L12.34017761501741 1.4257273767143488 L13.849837680191513 11.373678398055212 L-1.352249899879098 14.692027938288824" stroke="none" stroke-width="0" fill="#f41d92"&gt;&lt;/path&gt;&lt;path d="M1.0414908535621796 -0.8420332267100237 C4.780494231132144 0.6665065608890127, 8.457850989728657 -0.9744638007822969, 11.57488108811755 1.0513881631940682 M0.08811362479328744 0.5418942168523657 C3.5468726401372694 -0.666242520795934, 5.991111577995895 -0.6154998662179277, 12.77688891401901 0.5296193682294073 M13.091883152828864 -0.8107832429889554 C13.800899167751494 2.270871312397336, 13.610189932491613 4.961156454836958, 13.686602985805695 12.388112524400375 M12.312145744990739 0.18834437417128747 C13.077085411385177 4.551455537444534, 12.840175861762242 8.26306023308607, 12.487074559048038 13.041297806941836 M11.896562387641948 13.42187340754191 C7.630176444219054 13.40777684516352, 4.916230676975727 14.123583409908859, 0.09297102800999335 13.22055104789701 M12.824764142930954 12.95265963653631 C9.159073124510334 13.355699510148446, 5.141052466133164 13.774180763562478, -0.3644593735262613 13.18843901192172 M-0.16117039798512778 13.652716179124027 C0.11745001034315858 8.393914656195808, 1.1583215110963072 2.70707285932228, -0.53733317514178 1.3127921218768859 M-0.26037401433240664 13.11232129011922 C-0.38104558003754274 10.66043838127585, 0.5169270233889314 7.07531265030333, 0.49594617489912385 0.5456540033545563" stroke="#000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(158.5583118961415 297.4859064979682) rotate(0 7.661128495758078 6.228644914905814)"&gt;&lt;path d="M-1.9455205705016851 -0.22345868684351444 L16.747984368230505 1.2862013783305883 L13.548990387345953 11.105039929932538 L1.5450829360634089 11.155696546620312" stroke="none" stroke-width="0" fill="#fc54ee"&gt;&lt;/path&gt;&lt;path d="M-1.0269200082730467 0.11009960483612535 C6.63666840806493 -1.2521734743158925, 10.412755192521713 -1.3328753026268696, 16.604500384053036 -0.618541780965719 M0.6608789249652576 0.3475306613067197 C3.229713695241281 -0.6711718651448846, 7.207939777262348 0.23858470070857954, 15.968165856809671 -0.5939154529599925 M14.55400536763256 1.0776868641406396 C14.79937031496967 5.5808922011800455, 14.577072405615612 10.431879623835457, 14.603230928279373 12.851115148258028 M15.500721300636569 0.43474823668108187 C15.399242597014057 4.4651197394247815, 14.785133718165758 8.43716125514713, 15.222151778393908 12.461272189725435 M15.65755193155697 11.047054702284392 C10.02914329084076 12.262321993184518, 5.345486076157367 12.489320221305457, 0.0897678602605192 12.50739526456957 M15.085312031374464 12.951962997039711 C10.101451642726984 12.762173529936303, 5.227854556805708 12.076498274605221, 0.05060492558838725 12.839888459176404 M0.47923971212509286 12.65021985996031 C-0.05601254256168478 9.082891474908488, -1.0852300890680031 2.8409469331335435, 1.243926398547002 -1.0572533957080235 M-0.03280744055883855 12.130863983174796 C0.41650049821296165 8.898233736980236, -0.3006251360266924 4.760224935960268, 0.517030387320713 -0.6063476694699271" stroke="#000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(182.8197522626466 296.9274893892056) rotate(0 7.661128495758135 6.228644914905814)"&gt;&lt;path d="M-0.22345868684351444 1.4257273767143488 L16.608458369846744 -1.7732666041702032 L13.970007091637058 14.002372765875045 L-1.3015932831913233 11.24791690144557" stroke="none" stroke-width="0" fill="#fc54ee"&gt;&lt;/path&gt;&lt;path d="M0.11009960483612535 0.8593003868005527 C3.935998480685373 -1.039288849179737, 8.916451316129898 1.384935393364184, 14.703715210550437 -0.6803353278410714 M0.3475306613067197 -0.6866280743724951 C5.206022810712122 -0.7569195469582554, 11.971481873389413 0.5137330803004635, 14.728341538556164 0.5822269940824538 M16.399943855656794 -0.6197103605800823 C16.269292376994336 3.863415519345308, 16.436369346976868 8.691821515322115, 15.716082309962548 12.974956263060147 M15.757005228197238 0.18168553182127212 C15.484863747893188 2.257764338682755, 15.202051881423534 5.2802701582973155, 15.326239351429955 12.847521849725576 M13.912021863988912 12.720810870208062 C10.066145221432839 13.627317890600573, 5.150645060266301 12.580587184076236, 0.05010543475793483 13.244577768836443 M15.816930158744231 12.620449517502118 C10.052456453964936 13.083428476032159, 3.855789793070943 12.460505763371685, 0.3825986293647672 12.959376022065092 M0.19293003014867516 12.312898369334468 C1.194471650214235 8.4302830530395, -0.4938744674786677 7.994420688781336, -1.0572533957080235 0.34631055030647984 M-0.3264258466368386 12.78115255171168 C0.5737844980667266 9.253217731657708, 0.5192731962967294 7.352701845503714, -0.6063476694699271 -0.6058978404149993" stroke="#000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(209.19345940515484 296.4246466615356) rotate(0 7.661128495758021 6.228644914905814)"&gt;&lt;path d="M1.4257273767143488 1.2862013783305883 L13.548990387345953 -1.352249899879098 L16.867339927579565 11.155696546620312 L-1.209372928366065 10.816664671486798" stroke="none" stroke-width="0" fill="#fc54ee"&gt;&lt;/path&gt;&lt;path d="M0.8593003868005527 -1.1251561830701284 C4.724869159905446 -1.216867971864718, 13.015943412361855 -0.6295517414493315, 14.641921663675085 1.2590253993689795 M-0.6866280743724951 -0.6496796861117045 C4.186855125821445 0.1905707474804157, 10.269106640866813 -0.6634215852213364, 15.90448398559861 0.5954575151963637 M14.702546630936073 0.8969817485071647 C14.403423559193655 5.301782037143338, 14.526405765891013 8.869271955753678, 15.839923424764667 11.410290566577928 M15.503942523337427 0.21026635723277942 C14.925526506313211 3.188045431149141, 15.257844640217991 6.525164240685831, 15.712489011430096 12.679171437443138 M15.585778031912582 12.343676449015796 C12.675623019158937 12.42965116438401, 7.845759773492527 12.366371751816846, 0.7872879390248073 13.371604137919698 M15.485416679206638 12.698480697823324 C10.762179211180383 12.111740075937858, 4.905651079935272 12.93882309262202, 0.5020861922534563 11.91376691614686 M-0.14439146047716878 13.636489898990527 C-1.2215329781959963 8.363672642707565, 1.1960024273344927 4.23109415692349, 0.34631055030647984 -0.9263777631291734 M0.3238627219000443 12.981731236230207 C-0.38670142422784204 9.68346390199827, 0.5235918754324808 5.3634300954178284, -0.6058978404149993 -0.06959224067496939" stroke="#000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(149.45728946832332 227.4707988107309) rotate(0 -11.697563280880559 8.772680379480363)"&gt;&lt;path d="M-0.8880353961139917 1.6433941815048456 C-4.543679509754321 5.494981965602701, -8.887153174860638 5.837573503404779, -24.155102059371984 15.813087198943379 M0.759975497610867 0.7772451741620898 C-6.951276746958386 5.0345740345273144, -13.988122486323556 10.979206876689743, -21.739014656409836 16.768115584798636" stroke="#ccc" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(152.86967728285663 227.8691963222181) rotate(0 13.692874450714157 10.239304725707782)"&gt;&lt;path d="M1.6433941815048456 0.2805354204028845 C12.115812330402305 8.450372697654618, 19.066658725791093 13.269041799010857, 25.653475341410967 20.02286041187736 M0.7772451741620898 0.40180197823792696 C9.719003543677257 8.09180576042503, 20.32391623107886 15.380801296378355, 26.608503727266225 20.19807403101268" stroke="#ccc" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(115.20521899557366 271.7381050958482) rotate(0 -3.5845992397433974 11.677531061903167)"&gt;&lt;path d="M1.7252782676368952 0.9072571005672216 C-3.7361799191971983 3.9832129191479577, -5.700508883514224 12.02579473949178, -6.631086028164418 20.623351611615277 M-0.6167084770277143 0.8651053952053189 C-3.452401432330565 9.090765261662712, -6.7581277887958455 17.56218569059391, -8.89447674712369 22.489956728601015" stroke="#ccc" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(119.30410431190398 274.44774088168884) rotate(0 7.987897081870074 10.214256913514816)"&gt;&lt;path d="M0.9072571005672216 -1.7924985196441412 C3.987023390061884 6.152750549626826, 12.016045240021429 17.381211332409038, 13.24408365154909 22.221012346673774 M0.8651053952053189 -0.49746804405003786 C6.391486559510588 7.234621547348584, 12.113870022848557 15.152643040063653, 15.110688768534828 21.116614365246164" stroke="#ccc" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(174.48117391670644 271.54470808548206) rotate(0 -5.626211785070041 10.643295373582745)"&gt;&lt;path d="M-1.6960417423397303 -1.6309444811195135 C-3.520622159013798 10.128240890278185, -10.049927853035678 18.50380781137655, -9.577507738604027 22.917535228285004 M0.7200456606224179 -0.675916095264256 C-1.6076053558089534 4.637823349292976, -4.798710074596285 10.900963868586292, -11.9724692307625 21.731347246657123" stroke="#ccc" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(189.46056306493358 271.6163345066776) rotate(0 0.10445925753458596 11.478089711611148)"&gt;&lt;path d="M-1.6309444811195135 0.6789518799632788 C1.8260051794139314 6.052580603754815, 1.9966574758691138 16.752308511036624, 1.7790968439067 20.757288702728204 M-0.675916095264256 0.854165499098599 C-0.30955146261400834 8.808755961378266, 1.0602328449291973 16.151783877641392, 0.5929088622788186 22.277227543259016" stroke="#ccc" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(196.1394872677812 271.658605883659) rotate(0 8.31480735110199 11.693242553687398)"&gt;&lt;path d="M0.6789518799632788 1.6861977074295282 C1.5563356962139068 6.3250550511791435, 7.956635522567357 10.28607534733735, 14.430723981709889 23.96367811890454 M0.854165499098599 -0.5771930115297437 C4.396232889873626 6.085425711676383, 7.159622623721761 10.851284920842774, 15.950662822240702 22.931449381766328" stroke="#ccc" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(150.48844094030744 146.0666644239527) rotate(0 0.4820803153887141 20.65912277933048)"&gt;&lt;path d="M-0.8983009401708841 -1.0846829887479543 C-0.4825426286831657 6.038177052176947, 1.3350375337525406 35.46382285523889, 1.862461570948267 42.40292854740891 M0.8309523368999363 0.9602544968202711 C1.114898973554357 7.801758659630474, 0.6219958722590605 34.099780903455894, 0.7398274347557663 40.850540399212875" stroke="currentColor" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(150.48844094030744 146.0666644239527) rotate(0 0.4820803153887141 20.65912277933048)"&gt;&lt;path d="M-8.103164856855276 20.841693136183807 C-2.7994453142706845 26.378954251456772, -4.147756390803014 32.07181256180475, 1.3637297761066793 41.240459749136605 M-5.728209069531861 22.332503024762758 C-2.8488482554373027 29.000476914302446, -1.1374554925382698 36.92471136188208, 0.5753684493153333 40.25085420407031" stroke="currentColor" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(150.48844094030744 146.0666644239527) rotate(0 0.4820803153887141 20.65912277933048)"&gt;&lt;path d="M6.047930538232997 20.871073095337536 C7.851798443471957 26.295134931080266, 3.0035538986470023 31.980726814380134, 1.3637297761066793 41.240459749136605 M8.422886325556412 22.361882983916487 C5.785470410464322 29.047978763015742, 1.9801009677463268 36.960759521411845, 0.5753684493153333 40.25085420407031" stroke="currentColor" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(86.91304841660292 85.70572291836558) rotate(0 -6.069917747407203 7.8656134531734105)"&gt;&lt;path d="M0 0 C-3.301496364709221 4.278195406017231, -6.602992729418442 8.556390812034461, -12.139835494814406 15.73122690634682 M0 0 C-2.4435118269815015 3.166388787970618, -4.887023653963003 6.332777575941236, -12.139835494814406 15.73122690634682" stroke="#ccc" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;/svg&gt;
&lt;figcaption&gt;Clearing bloat in Indexes&lt;/figcaption&gt;&lt;/p&gt;
&lt;/figure&gt;
&lt;p&gt;If for some reason you had to stop the rebuild in the middle, the new index will not be dropped. Instead, it will be left in an invalid state and consume space. To identify invalid indexes that were created during &lt;code&gt;REINDEX&lt;/code&gt;, we use the following query:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;-- Identify invalid indexes that were created during index rebuild&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relname&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;index_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;pg_size_pretty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pg_relation_size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;oid&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;pg_index&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;JOIN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg_class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;indexrelid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;oid&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;-- New index built using REINDEX CONCURRENTLY&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relname&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;LIKE&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;%_ccnew&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;-- In INVALID state&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;AND&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NOT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;indisvalid&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Once the rebuild process is no longer active, it should be safe to drop any remaining invalid indexes.&lt;/p&gt;
&lt;h4 id="activating-b-tree-index-deduplication"&gt;&lt;a class="toclink" href="#activating-b-tree-index-deduplication"&gt;Activating B-Tree Index Deduplication&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;PostgreSQL 13 introduced a new efficient way of storing duplicate values in B-Tree indexes called &lt;a href="https://www.postgresql.org/docs/current/btree-implementation.html#BTREE-DEDUPLICATION" rel="noopener"&gt;"B-Tree Deduplication"&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For each indexed value, a B-Tree index will hold in its leaf both the value and a pointer to the row (TID). The larger the indexed values, the larger the index. Up until PostgreSQL 12, when the index contained many duplicate values, all of these duplicate values would be stored in the index leaves. This is not very efficient and can take up a lot of space.&lt;/p&gt;
&lt;figure&gt;
&lt;p&gt;&lt;svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 307.18219907171897 313.43858782190574" width="auto" height="50vh"&gt;
  &lt;g transform="translate(10 107.6412908258085) rotate(0 3.2515151206274595 6.5734725011127075)"&gt;&lt;path d="M-0.4485612418502569 -0.4997053537517786 L5.225899289149993 -0.3881890568882227 L8.229065189857238 14.57994898475446 L0.5352406594902277 13.748819255006687" stroke="none" stroke-width="0" fill="#f41d92"&gt;&lt;/path&gt;&lt;path d="M0.40972277036861016 -0.5883272895292948 C1.0568773124552022 0.25752631693984657, 3.3622637583959856 -0.21277034508830905, 6.012274169592565 -0.04777237005700674 M-0.2872641045593696 -0.11420775746779133 C1.6014323836369901 0.11785649128669029, 4.083158833132231 0.10432876751219083, 6.682955778322728 -0.2615904198902685 M6.021882515627821 -0.08882281421537641 C7.463708399526013 1.9044728441130412, 7.35920955562554 6.18011459499136, 6.990542116238773 12.801108364339493 M6.123002482002408 -0.3876052728191664 C7.155053783548244 4.374456628539132, 6.115607548598652 8.635350305214427, 6.178960106540743 13.507330362256337 M6.90553007803129 13.763417261198372 C4.295392697003441 12.787946379765202, 2.6280506957903556 13.335979590829707, 0.056463214848035626 12.612511797688947 M6.7703396987202975 13.129161615514041 C4.634502191435737 12.982269663225896, 2.88706590760344 13.126123710662355, 0.12038776119571343 13.276742095112123 M-0.9162939349060555 14.304178639203204 C-0.587151479793689 11.006612650585243, -0.33109782446759545 7.2176473450205645, -0.6505719101596745 -0.9638430020250367 M-0.21321498499324226 13.789646635703976 C-0.42551721426394473 10.579754929318598, -0.6352463516175878 7.204710819764004, -0.4939289143367984 -0.4578624158084681" stroke="#000000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(137.17690806005203 10) rotate(0 14.90250780610279 12.090713880423053)"&gt;&lt;path d="M-1.277130952104926 -0.3881890568882227 L31.53105056080798 1.4330039825290442 L30.34025627169589 24.783302013627384 L1.0720560047775507 25.396834377995823" stroke="none" stroke-width="0" fill="#fc54ee"&gt;&lt;/path&gt;&lt;path d="M-1.2718049678951502 0.7023947332054377 C8.902038422181352 -0.47435249873357027, 13.702428671999067 0.12272171191019804, 29.664922544544734 1.5675309393554926 M-0.9347187532112002 0.19106374215334654 C7.22507303045762 0.254147921091956, 13.327710158669897 -0.6998299915724575, 28.890552454440094 -0.05108850169926882 M31.276680299824275 -1.2738639619201422 C31.02921134225174 8.088833235979468, 30.45815241328522 13.53265569327849, 29.972988376205958 24.39471881916294 M30.775587730853058 0.4867392284795642 C29.054515081910058 7.780296401193272, 29.172220278052254 14.50739677916821, 29.993785106389023 24.273681485890542 M29.39298892933706 23.241419010660742 C24.146448604055045 25.20943396320054, 16.986294005701737 22.820328822037666, -0.3206170592457056 23.675360911390875 M29.737760507313705 23.710259521245156 C21.627311828867033 24.13637996390211, 13.344046873870067 24.564316190031644, -0.7201772285625339 24.506427818774377 M-0.8282135520130396 25.485058539412115 C-0.2701521419483307 16.83812933247224, -0.8211572073894623 7.8300646427442775, -1.9115876350551844 1.75326825119555 M-0.4719980312511325 24.364461237429772 C-0.7126177489960982 16.049894760950014, -0.6729631662572219 8.947619048325235, -0.22428062092512846 -0.2498526768758893" stroke="#000000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(140.75580855110957 59.579564489112144) rotate(0 11.799059530240811 8.987265604560996)"&gt;&lt;path d="M-0.3881890568882227 1.7260349486023188 L25.031123043010552 0.5352406594902277 L24.19999331326278 19.046587213899535 L1.2154066171497107 18.867347642805022" stroke="none" stroke-width="0" fill="#e08fff"&gt;&lt;/path&gt;&lt;path d="M0.7023947332054377 1.2961665596812963 C7.822235226760403 -1.359254027730538, 16.985542288308533 0.009967957609580302, 25.165649999837 -1.7140263710170984 M0.19106374215334654 0.837897484190762 C5.1440539349252 0.7739351103996087, 9.184670584911409 -0.6938871135021399, 23.54703055878224 -0.09306552540510893 M22.45326368349604 1.1797531008921722 C24.53813161218691 5.401060441292348, 23.113146598477698 9.620027919336295, 23.78980939969863 17.770229102870857 M24.035564533132003 -0.5488541645047786 C24.21175954519893 3.9101404605429932, 23.984851741397428 8.87607183616862, 23.68102993347995 17.237181445481024 M22.658110310296138 18.81215101110522 C15.80355823823092 18.09956794929363, 4.874430741971366 17.605298876284138, -0.5060668494552374 17.621641155358 M23.126950820880552 18.21243743135367 C14.549917906276974 18.328911542200363, 5.958399632481502 17.238483040832794, 0.32500005792826414 18.05232746436034 M1.1716076057253306 17.580270795136506 C-0.06901650395889575 12.94546756458809, -1.4759854346175836 7.977335942277191, 1.575708744953857 0.010563147031878994 M0.16449704685835442 17.263591918655212 C-0.9512462743014427 12.489887145901578, -0.8017310785488132 8.154277459091253, -0.2245492369094172 -0.5738957539186418" stroke="#000000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(76.03758884250328 58.6756194766938) rotate(0 10.764576771620114 9.332093190767885)"&gt;&lt;path d="M1.7260349486023188 1.4330039825290442 L22.06439420273034 0.6018742527812719 L22.601209548017664 19.879592998685474 L0.8928164336830378 19.806256695451374" stroke="none" stroke-width="0" fill="#f938c5"&gt;&lt;/path&gt;&lt;path d="M1.2961665596812963 -0.7439976241439581 C3.0016946347653546 -0.22253323379590498, 8.881926307871874 1.4919211190597774, 19.815127172223015 -1.766955366358161 M0.837897484190762 0.14945937227457762 C6.255904940846837 -0.8456529468034902, 10.490628591655744 -0.09222290811292319, 21.436088017835004 -0.36597683001309633 M22.754171959746667 0.9886892063244848 C22.358591565597294 4.982187460523971, 21.82324441206047 11.1453607144933, 21.317012678766485 17.585166285590184 M20.959240686909233 0.7472384357416592 C21.25023366204446 5.010345366906043, 21.88646438356101 9.083533714140383, 20.763512800966286 19.241788798673088 M22.36677334522335 19.409647914381857 C13.20314542379133 16.919354809645334, 4.209216217569271 18.056931568506876, -0.35289005376398563 20.308401140185232 M21.7670597654718 18.635439727256234 C15.558407325299987 18.961937419635095, 8.126981333187256 18.887747905713358, 0.0777962552383542 17.967222992131646 M-0.4093875808984646 18.756809601820677 C-0.8463009823342184 10.053723702829119, -1.7066217347730308 5.816085192200483, 0.010968438769546562 -0.6053853908381921 M-0.738216939782809 17.85892575364117 C-0.6131163213568973 10.590086645170809, -0.12120617326576756 3.5828395895087928, -0.5959152530928642 -0.18113082272585945" stroke="#000000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(208.83633044969838 59.65253340982865) rotate(0 11.454231944033836 8.642438018354092)"&gt;&lt;path d="M1.4330039825290442 0.5352406594902277 L23.510338140848944 1.0720560047775507 L24.123870505217383 18.177692470391214 L1.1420703139156103 18.160999057133616" stroke="none" stroke-width="0" fill="#daaeff"&gt;&lt;/path&gt;&lt;path d="M-0.7439976241439581 -1.5093150530010462 C5.268860843935556 -0.2885589917878272, 12.399099454226985 -1.862492295143998, 21.14150852170951 -0.7024894747883081 M0.14945937227457762 0.5533590661361814 C3.926723545686624 -1.1064126003504355, 9.41139567045365 -0.28501496798997716, 22.542487058054576 -0.06756156217306852 M23.824087533310326 0.6409536454357037 C22.343684103943463 6.602509315831301, 22.982706459857805 12.718216443305142, 21.909184936482234 16.26567209378156 M23.600480323589117 -0.42606948646896065 C23.466410896191142 7.026227551886576, 23.07232587039827 13.017110588117907, 23.443380651897165 18.104159215557484 M23.653925420913765 17.458528050471962 C14.864226032601373 17.060201892390786, 9.601208544313394 17.027928897872506, 1.6442147586494684 17.175490805198372 M22.879717233788142 17.65512730951321 C16.58918540766793 16.619199469153056, 9.796527841496447 17.417172952953944, -0.6969633894041181 18.16510611052525 M0.08577823045794819 16.429540077048276 C-1.3798729439898347 11.163113889357431, 1.402585737024807 8.355665127824196, -0.5606465356252652 1.6899771104825243 M-0.7457507038276036 16.635485622903886 C-0.6336092861702043 12.721422835163999, -0.2475707368951354 7.993759651888079, -0.16774499317798974 0.7458575030404262" stroke="#000000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(124.72110028775455 108.09106587374295) rotate(0 3.418704253333999 5.925614611875506)"&gt;&lt;path d="M0.6018742527812719 1.0720560047775507 L8.052815123817709 0.8928164336830378 L7.9794788205836085 12.727352244176451 L-0.6656810436397791 10.761571687974516" stroke="none" stroke-width="0" fill="#fc54ee"&gt;&lt;/path&gt;&lt;path d="M-0.05022876986474489 -0.0478936766275021 C3.0302360340223 -0.5343759065791224, 4.384617347481149 -0.18856135762495851, 6.1983031112131615 0.13063808559151124 M-0.2750410955916792 -0.3126279086970385 C2.6066616428069875 -0.06965177061453479, 5.123137926686378 -0.060932719856709755, 7.088967823020158 -0.21774820723926447 M6.5256562882732565 0.09953418647572732 C7.026575529516304 4.1985172976505485, 6.215040297569077 7.967632892071869, 7.987655772295194 12.428075040641316 M7.162275577298287 0.11185752730092757 C6.986969553286865 2.3454515880968025, 7.2990647110641085 6.224958097032192, 6.7153329400435595 11.572722744481549 M6.275495367196217 11.74161973333754 C4.326819227625516 11.736523199755334, 2.7243569256027715 11.819770296910106, -0.04598506263051072 11.529072250799075 M6.973879615385016 11.60502192830691 C4.734627659937296 11.903141054641512, 2.1713598267545695 12.177469445500924, -0.1415708596467919 12.074065628124048 M-0.8688500903269519 10.718496061534658 C1.0330378848803383 8.562774612047857, -0.39028387244587615 6.419109233091924, -0.5593756861516365 0.2169171686613014 M-0.41273713716517735 11.718329171299567 C-0.0457895333663373 8.85496482178756, -0.01274940345849064 7.126869898533532, 0.4245714668874948 0.15858149363725882" stroke="#000000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(208.6725173442362 107.83334806567969) rotate(0 3.721734556364254 6.228644914905818)"&gt;&lt;path d="M1.0720560047775507 1.2154066171497107 L8.336285546411546 1.1420703139156103 L8.319592133153947 11.791608786171857 L-1.089657535776496 13.598380362576428" stroke="none" stroke-width="0" fill="#daaeff"&gt;&lt;/path&gt;&lt;path d="M-0.052138921102075075 0.5833934065169456 C1.31822944950221 -0.6521457852012162, 3.6508411987310283 -0.6902869174099404, 7.585686819056583 0.6236864043206861 M-0.34033891347780887 -0.019013784220704266 C2.3096538377412745 -0.07632277244874705, 4.663436091801682 0.33374135522465154, 7.206419936369244 0.24427495952384054 M7.54809338296182 0.1328514265779983 C7.348070896914918 3.6768961840747947, 6.755117957962409 10.003232079071989, 8.049814276799392 11.696520570019727 M7.561046927731962 0.05746156953791037 C6.871033129599475 5.120182795696993, 7.972516227447658 9.341013241737045, 7.150720076638054 12.718151645824001 M7.324143953853045 12.268945181666842 C4.801651073417531 13.008050866192422, 2.3817953760380943 12.346056648063778, -0.35071262383683843 12.634374591502384 M7.175438264903725 12.578246224452837 C5.260779563379984 12.227612109206918, 3.3477808800638025 12.332883832464718, 0.24258838586645654 12.3756558450437 M-1.1906600602483313 13.549338367539134 C0.06896526998816997 8.25422157242456, 1.2796187498938543 4.271292772357941, 0.2280101066360689 -0.9854361918769434 M-0.13969643490372208 12.301665469281774 C-0.30413602763974856 9.674140230293965, 0.631146673163188 7.579063458738153, 0.16669120059923215 0.18744305019994045" stroke="#000000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(251.7198841604038 107.96220696971129) rotate(0 3.1156739503036306 5.622584308845209)"&gt;&lt;path d="M1.2154066171497107 0.8928164336830378 L7.3734182145228715 0.8761230204254389 L5.565666856967482 10.155511081913922 L1.1410905327647924 12.8262757069864" stroke="none" stroke-width="0" fill="#daaeff"&gt;&lt;/path&gt;&lt;path d="M0.48839153140448877 -0.534034731431144 C0.8486253302073951 -0.2145079724807483, 2.2158462476289715 0.12342330807023508, 6.753470973510883 0.09313333456492678 M-0.01591751139044545 -0.028996183317601842 C2.1001952081184965 0.03593340085280666, 4.6577046779140465 -0.1414638573360066, 6.435844249439673 0.16504513736205512 M6.351272596378179 -0.12781482904974384 C5.551657554781466 3.1274690764815114, 7.293191662983952 8.128428954825841, 5.544603133294988 12.145590638319936 M6.283218335293994 -0.4612983963748014 C6.641003978574488 3.3521753122759543, 6.177165897951327 5.374070669100698, 6.46682729837771 11.454739633561825 M6.07367397061127 11.135219582907048 C4.820122934077816 11.201098363858968, 2.3424215534352237 10.941577913428246, 0.1482476438444822 11.227255577310995 M6.332607322040688 11.269407394278149 C3.9140760136078265 11.474542860204304, 1.9020840924799665 11.403376291957342, -0.06834041384439987 11.260630516148549 M0.9857898558368579 11.251777099888088 C-0.2823027865128511 9.527543975836796, -0.44832795266848535 5.816818038440428, -0.8895511215539019 -0.9703387393109475 M-0.14048177405253476 10.886129795108133 C-0.030805254607593854 9.201815757927404, 0.481185315732688 6.338456982986692, 0.16920443647929573 0.301386263533277" stroke="#000000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(148.02101026086848 35.884815995363454) rotate(0 -23.971252877089796 9.31638414407385)"&gt;&lt;path d="M0 0 C-12.698796441889426 4.935364306014655, -25.397592883778852 9.87072861202931, -47.94250575417959 18.6327682881477 M0 0 C-14.05884466792951 5.463944593128704, -28.11768933585902 10.927889186257408, -47.94250575417959 18.6327682881477" stroke="#ccc" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(151.43339807540178 36.28321350685064) rotate(0 0.08027251050077666 11.80897408051031)"&gt;&lt;path d="M0 0 C0.033157062113982214 4.877770542458591, 0.06631412422796443 9.755541084917182, 0.16054502100155332 23.61794816102062 M0 0 C0.05997692820951473 8.823269463435237, 0.11995385641902946 17.646538926870473, 0.16054502100155332 23.61794816102062" stroke="#ccc" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(154.36003610032094 34.93365429834114) rotate(0 26.90922922078005 10.624632932971565)"&gt;&lt;path d="M0 0 C17.17381313914491 6.780776185218786, 34.34762627828982 13.561552370437571, 53.8184584415601 21.24926586594313 M0 0 C12.717667797728016 5.021346059588701, 25.43533559545603 10.042692119177403, 53.8184584415601 21.24926586594313" stroke="#ccc" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(71.2048372240165 80.15212228048075) rotate(0 -23.10081459280127 11.698068685788044)"&gt;&lt;path d="M0 0 C-17.73171164169389 8.979197675815334, -35.46342328338778 17.958395351630667, -46.201629185602656 23.39613737157609 M0 0 C-16.89291917980909 8.554439847781541, -33.78583835961818 17.108879695563083, -46.201629185602656 23.39613737157609" stroke="#ccc" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(81.45756869419301 83.8873990919624) rotate(0 -14.554766232255702 10.289855877415818)"&gt;&lt;path d="M0 0 C-9.167928984288652 6.481496606496379, -18.335857968577304 12.962993212992759, -29.109532464511403 20.579711754831635 M0 0 C-10.274865373056617 7.264072961494574, -20.549730746113234 14.528145922989149, -29.109532464511403 20.579711754831635" stroke="#ccc" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(92.71052560552926 84.93456877733809) rotate(0 3.1552117382235565 10.074882880301189)"&gt;&lt;path d="M0 0 C2.2423200471191946 7.1599352845956865, 4.484640094238389 14.319870569191373, 6.310423476447113 20.149765760602378 M0 0 C2.1271286593114835 6.7921185302007965, 4.254257318622967 13.584237060401593, 6.310423476447113 20.149765760602378" stroke="#ccc" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(143.8141254784823 81.49718680857619) rotate(0 -5.565999043464103 11.056965635904575)"&gt;&lt;path d="M0 0 C-2.4440095408266695 4.855072610635041, -4.888019081653339 9.710145221270082, -11.131998086928206 22.11393127180915 M0 0 C-3.0467669246354205 6.052461907240674, -6.093533849270841 12.104923814481348, -11.131998086928206 22.11393127180915" stroke="#ccc" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(157.79351462670957 82.56881322977169) rotate(0 0.4877464437154231 10.995352828391816)"&gt;&lt;path d="M0 0 C0.2034426356074676 4.586242683395541, 0.4068852712149352 9.172485366791083, 0.9754928874308462 21.990705656783632 M0 0 C0.30084067805747333 6.781903677609435, 0.6016813561149467 13.56380735521887, 0.9754928874308462 21.990705656783632" stroke="#ccc" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(171.0109003680186 84.63672563239413) rotate(0 7.832070467882659 11.11673366424695)"&gt;&lt;path d="M0 0 C4.619216531648152 6.556452745208931, 9.238433063296304 13.112905490417862, 15.664140935765317 22.2334673284939 M0 0 C4.8662933406202615 6.9071501873755246, 9.732586681240523 13.814300374751049, 15.664140935765317 22.2334673284939" stroke="#ccc" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(218.44693905993927 81.66042333333068) rotate(0 -2.497168498125802 13.759874370826921)"&gt;&lt;path d="M0 0 C-1.4518210392148485 7.999810634930292, -2.903642078429697 15.999621269860585, -4.994336996251604 27.519748741653842 M0 0 C-1.4415344685922782 7.943129670237908, -2.8830689371845564 15.886259340475815, -4.994336996251604 27.519748741653842" stroke="#ccc" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(224.86302491158904 83.77721257796638) rotate(0 8.393870813360422 10.293591016739072)"&gt;&lt;path d="M0 0 C4.421930041653634 5.422711448091953, 8.843860083307268 10.845422896183907, 16.787741626720845 20.587182033478143 M0 0 C4.065784034826878 4.9859616555309065, 8.131568069653756 9.971923311061813, 16.787741626720845 20.587182033478143" stroke="#ccc" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(231.67339697531725 82.31858859402979) rotate(0 15.20624662486216 11.553439809358409)"&gt;&lt;path d="M0 0 C8.91827641956437 6.775950197216492, 17.83655283912874 13.551900394432984, 30.41249324972432 23.106879618716818 M0 0 C7.330473150815566 5.569565088047047, 14.660946301631132 11.139130176094094, 30.41249324972432 23.106879618716818" stroke="#ccc" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(233.9060627600552 81.27525822827378) rotate(0 27.449387833183778 13.390851929034326)"&gt;&lt;path d="M0 0 C20.509262176994724 10.005195549529535, 41.01852435398945 20.01039109905907, 54.898775666367555 26.78170385806865 M0 0 C21.79795480087572 10.63386866287188, 43.59590960175144 21.26773732574376, 54.898775666367555 26.78170385806865" stroke="#ccc" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(86.91304841660303 85.70572291836558) rotate(0 -6.069917747407203 7.86561345317341)"&gt;&lt;path d="M0 0 C-2.8687257014201695 3.7173959202648605, -5.737451402840339 7.434791840529721, -12.139835494814406 15.73122690634682 M0 0 C-4.232844064705801 5.485068596647267, -8.465688129411602 10.970137193294534, -12.139835494814406 15.73122690634682" stroke="#ccc" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g transform="translate(20.511979697757397 107.85943350261125) rotate(0 3.2515151206274595 6.5734725011127075)"&gt;&lt;path d="M-0.8196858074516058 0.946388503536582 L5.622484455604308 -0.8256191406399012 L5.041492412585967 11.254417323243992 L1.796407887712121 11.55209275401868" stroke="none" stroke-width="0" fill="#f41d92"&gt;&lt;/path&gt;&lt;path d="M-0.38481820062735256 -0.30378136238499065 C2.5249011509447694 0.43741566127245457, 3.417532931489477 0.30527252823361095, 6.668353311829706 -0.20877235249213555 M-0.26895931014899577 -0.2852865608539624 C1.1456036729190713 -0.019992757351968753, 3.108375164039332 0.021733284437424623, 6.337712334080037 -0.19228819481254883 M6.8365775197051315 0.9118283449767994 C5.339975719230488 3.110564883706303, 7.151402729680647 5.435810125445369, 7.651012127193148 13.007376746390577 M6.610484859853435 0.4720909910286373 C5.76867946831169 3.54177053573575, 6.923369530707925 7.137166266952054, 6.185578141102774 13.495713152006502 M6.423926847509626 13.326383188048796 C4.76059252886008 13.610518734491542, 4.2779833677426 13.567967653408127, 0.18231681988016468 12.561028923246296 M6.770093129795381 12.93248747511016 C4.4225939772703375 13.338190399506399, 1.87442684917397 12.902693115996104, 0.17785706168388893 13.184645040481374 M-0.42952780439938343 12.142567349063825 C1.1096744941103647 8.651832580272302, -0.8942503227559575 2.12099341254441, -0.43831440655484255 0.8185677727746472 M0.5777536229940521 13.277647327338636 C-0.14769565002710763 7.58747469919055, 0.055687231419579736 3.058178433119986, -0.5557089063085043 0.40196612292429723" stroke="#000000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(31.42107060684839 108.46549410867185) rotate(0 3.2515151206274595 6.5734725011127075)"&gt;&lt;path d="M1.171240458264947 0.948160907253623 L7.178184280891173 -1.6393527183681726 L5.307961891193145 14.002619170320408 L0.24776811338961124 13.161546432149784" stroke="none" stroke-width="0" fill="#f41d92"&gt;&lt;/path&gt;&lt;path d="M0.012468147842925492 -0.3306358143497638 C1.064852482341545 -0.3999144303560199, 2.6157867988567975 -0.01358438587305845, 6.668016710604478 0.4510285318158571 M-0.26974236150229436 0.2839199874725833 C1.3694024784719003 -0.18444659627667442, 2.5008866219214863 0.1117679569261476, 6.556181801118475 0.23351599864178052 M7.419661825338314 -0.6349042003042887 C7.241035600473209 1.5427821383082359, 6.905441875326629 4.487313002182269, 6.343109722900069 13.509708801992684 M6.976608419904325 0.18429171594030747 C6.01408542638882 3.4112232171923313, 7.187015631215586 6.476512928335731, 7.0429417867222766 12.713383845395635 M5.9933410096118935 13.502659125593192 C4.438421898951283 13.632588999286014, 1.6314659246618708 13.308651077280992, -0.21246246188724466 12.650137702046942 M6.268360630482741 13.03854066036039 C4.264833355256135 13.4075425624225, 1.7719232486544427 13.08539136150543, 0.28578116677972965 13.211595852492891 M0.624600531380977 12.035527189608406 C0.8757196229759756 8.83798534539094, 0.6938932574640739 5.027837760729067, -0.5427184717388694 -0.9607378726091331 M-0.365772064693377 13.310748176957489 C0.25223677506953385 10.414736113596145, -0.49900129240444874 7.920345080213589, 0.34617934324233235 0.6559798221231616" stroke="#000000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(44.14834333412091 107.85943350261122) rotate(0 3.2515151206274595 6.5734725011127075)"&gt;&lt;path d="M-1.9521041419357061 1.9809646215289831 L5.646276066798919 -0.380755165591836 L7.070431957740539 14.195201479566471 L0.3884177301079035 14.459195040834324" stroke="none" stroke-width="0" fill="#f41d92"&gt;&lt;/path&gt;&lt;path d="M-0.299089329926429 -0.6139092927352279 C1.7047641258511024 0.11320675086164175, 3.2898806960273355 -0.5967958766480737, 6.703207178736831 0.5386167327992468 M-0.292042415549828 0.0895159866654896 C2.166910503606255 -0.07405476697492555, 4.898346068214204 -0.22230980566128825, 6.758557405792482 0.25430080452066006 M6.956038471090809 1.161334706944889 C6.754038032016898 5.971062948901153, 5.672766270783421 10.164775140582723, 7.233468390394264 12.020346138978308 M7.050264784515254 -0.5979552519055283 C6.794731354856184 2.6988990025236808, 6.0727548741374235 6.322367531311375, 6.77475032387251 12.640024315656884 M7.124523052226328 13.447314445914403 C4.998650799560066 13.758997130029615, 3.669192240125631 13.101395762629284, -0.35509046726879073 13.2621093701448 M6.369769201400288 13.300804828687275 C4.10001216012668 13.052485205959043, 1.7456942551789116 12.879031743721843, 0.2920523704855198 12.88766069221487 M-0.06434650699909761 12.38511517442967 C0.31418206154881456 10.62676431742921, 1.2791990092999086 7.491777954123201, 0.6232709650461767 0.44381065135640774 M0.08143484400095924 13.151744107129653 C0.34991828267298297 10.83603375756053, -0.19005924045204664 7.847716902075161, -0.50186384019652 0.40612547455353565" stroke="#000000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(54.952959545848444 108.05339246724017) rotate(0 3.2515151206274595 6.5734725011127075)"&gt;&lt;path d="M-0.3225600626319647 1.7032166924327612 L6.475990663070434 -1.2274785432964563 L6.886266778011077 12.357810758722202 L-0.29906814359128475 12.828585707319156" stroke="none" stroke-width="0" fill="#f41d92"&gt;&lt;/path&gt;&lt;path d="M-0.02280959086273382 0.5715623335594593 C2.485108715358828 -0.11607597820976925, 4.356523562378963 0.08512742355048628, 6.8119838531842944 -0.5497538511718403 M-0.14315539682238737 -0.1342256559835044 C2.1109570520834855 -0.33845108351307635, 4.989187709581103 -0.2900562753029167, 6.322104041464424 0.08102391838681372 M6.849972937750149 0.6923586864846647 C7.808555234801332 5.454641502279372, 7.119866555601186 9.813270266996097, 5.425406239873165 12.361370108617294 M6.77439103456709 0.5555482546209853 C6.250700480242252 4.940249460477603, 6.0174533698585915 9.41924259866577, 5.86142509643879 13.798035825490311 M6.843872419891931 13.27323961448198 C5.257376168426965 13.042361274196018, 2.8053970787212665 13.13843098106358, 0.3672933409371284 12.902155620934488 M6.606573173217494 13.03877033914437 C5.008519245653804 12.783082829948274, 3.068515993985178 12.990784439683727, 0.26543262655495425 12.924777091856686 M-0.01799375915264978 12.268567091541499 C-1.3653842653456747 8.733391644412123, -0.9590600717513276 6.337225828259107, 0.1400514940734865 0.2665502769564383 M0.2897059798678753 13.138900337260827 C-0.5375612354487048 8.552646333329626, -0.3988350700454671 2.9574690922774844, -0.40220543036761 0.5500500115910041" stroke="#000000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(65.46493924360584 108.27153514404293) rotate(0 3.2515151206274595 6.5734725011127075)"&gt;&lt;path d="M1.7032166924327612 -0.027039578184485435 L5.275551697958463 0.3832365367561579 L5.713895997751706 12.84787685863413 L-0.3183592949062586 13.625420582217352" stroke="none" stroke-width="0" fill="#f41d92"&gt;&lt;/path&gt;&lt;path d="M0.5715623335594593 0.12930170053495127 C2.4863628495070142 -0.35280950818251733, 5.259968134615154 0.31123457101564833, 5.9532763900830785 0.39765859717131535 M-0.1342256559835044 -0.23761061746430612 C1.3767078744465089 0.32068344984999725, 3.1094896753238817 -0.15229512042601723, 6.584054159641733 0.18317735917619538 M7.195388927739584 1.3119596442463233 C7.1967589529348714 4.503007958714704, 6.870657909831753 6.6818499859553, 5.717455347646799 13.709420063613909 M7.058578495875905 -0.2686167298128587 C6.937680960657253 3.605089348473023, 6.910115536789302 7.572301232522957, 7.154121064519814 12.865352501925413 M6.629324853511483 13.5736250864835 C4.552891406060703 12.828064371727539, 2.821855158369675 13.548322561026243, -0.24478938129092742 12.924669068055668 M6.394855578173875 13.381067144831029 C3.727530844759222 13.190405617761646, 1.4660436193611248 13.433086215172885, -0.22216791036872896 13.026914446797557 M-0.8783779106839158 11.864450555883971 C-0.9880040447525349 7.497793058768385, 0.0745906281034045 3.7410331148790332, 0.2665502769564383 1.0480397864456452 M-0.008044664964588777 12.657800062212186 C0.29577124800493504 8.368279772593052, -0.3072827059339366 3.888246661813062, 0.5500500115910041 -0.10601698508342061" stroke="#000000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(76.37403015269683 108.87759575010352) rotate(0 3.2515151206274595 6.5734725011127075)"&gt;&lt;path d="M-0.027039578184485435 -1.2274785432964563 L6.886266778011077 -0.7891342435032129 L6.203962097663634 12.828585707319156 L0.4784755799919367 13.641666793001072" stroke="none" stroke-width="0" fill="#f41d92"&gt;&lt;/path&gt;&lt;path d="M0.12930170053495127 -0.08603903384087053 C1.8961880487966722 0.05800813456343909, 4.8115106440603 -0.6069100845277862, 6.900688838426234 -0.26652207970926245 M-0.23761061746430612 -0.30767911822070443 C2.5506810957857144 -0.23243917881384393, 4.336331250809895 0.10786904958351459, 6.686207600431114 -0.015914240185207318 M7.814989885501243 0.7699116944595272 C7.160995475950619 3.6260643242282864, 5.460100509522688 5.578932452135637, 7.065505302643412 13.309814690227334 M6.23441351144206 0.433690898421486 C6.055438559720834 4.359807185029636, 5.915697255101224 8.558454244217097, 6.221437740954917 13.021800821691688 M6.929710325513002 13.023912121589788 C4.01466479445457 13.132611289683007, 2.5995223853145033 12.914785082160146, -0.22227593416974706 13.66522476498249 M6.737152383860533 12.840634440863269 C5.150389307172106 13.026701117982514, 4.017676941356815 12.903142159240478, -0.12003055542785823 13.351948902213365 M-1.2824944463414438 12.192152133421079 C-0.8743708768318317 8.978551546127415, 0.1418508699887515 4.86110475888265, 1.0480397864456452 0.48416295435474765 M-0.48914494001323006 13.544769620442896 C-0.4083599705835674 7.977098463710935, -0.460146626341185 3.7677606082302386, -0.10601698508342061 0.5598024045571448" stroke="#000000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(89.10130287996935 108.2715351440429) rotate(0 3.2515151206274595 6.5734725011127075)"&gt;&lt;path d="M-1.2274785432964563 0.3832365367561579 L5.713895997751706 -0.29906814359128475 L6.18467094634866 13.625420582217352 L0.4947217907756567 13.862507533949987" stroke="none" stroke-width="0" fill="#f41d92"&gt;&lt;/path&gt;&lt;path d="M-0.08603903384087053 -0.35509046726879073 C2.043263849433127 0.29602344187587987, 3.306445111855644 0.38472842711782024, 6.236508161545657 0.30771965292371906 M-0.30767911822070443 0.2920523704855198 C1.4053139687211562 -0.13340407629763348, 3.410220475850216 0.2306994826690566, 6.487116001069712 -0.18841648793720334 M7.272941935714446 0.6232709650461767 C7.016076761259794 3.2134021957811, 5.7866912162952655 9.1445274557142, 6.665899929256837 13.156543212033892 M6.936721139676405 -0.50186384019652 C7.020265366732652 3.845199942475838, 7.2652307154440114 8.692017674283932, 6.377886060721192 13.333434981245533 M6.379997360619292 12.793980153863838 C4.184315816473365 13.556906351588353, 1.6747383579370159 12.967337076481476, 0.5182797627570752 12.571428893075469 M6.196719679892773 13.16969703136913 C4.246426353326602 13.459202057301493, 1.9648724582829389 13.07373887531868, 0.20500389998795032 13.229083202964146 M-0.9547928688043372 12.270774749478319 C0.19563959511999382 9.360906754783926, 0.3543880680248299 6.342805305641109, 0.48416295435474765 1.0205337021254657 M0.3978246182174816 12.796526227615423 C-0.20362040272847226 8.22124411145443, 0.5516589445839218 3.9939370963352436, 0.5598024045571448 -0.00888719618187006" stroke="#000000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(100.81501000078788 108.16246380564154) rotate(0 3.2515151206274595 6.5734725011127075)"&gt;&lt;path d="M-1.2321522738784552 -1.2224123869091272 L7.434701750774138 0.7154360022395849 L5.314294348258727 11.676654183042423 L-0.4081327822059393 12.169415139329807" stroke="none" stroke-width="0" fill="#f41d92"&gt;&lt;/path&gt;&lt;path d="M0.14693217811379866 0.35838861855894333 C2.8496969913305596 -0.07978627489817579, 4.483342512127156 0.13735878415267744, 6.343898704618293 -0.05908401625209314 M0.3167664452615834 0.05807295920052624 C1.9189009456011623 -0.17439104941656802, 4.086321155905166 -0.17344507555060967, 6.460395428060921 0.184003669712536 M6.763130126687492 -0.05090740954199813 C7.525842128862617 6.074786832804691, 7.4424721604253925 9.450814709879284, 7.245675136798296 12.269065708302762 M6.921213472979419 0.303422156427853 C5.869715499909763 2.458682561186232, 6.538139890065207 5.805813026185644, 6.418324127144416 12.998006466245046 M6.706677283871458 13.412826464229012 C3.8905265416704635 13.334889098212228, 1.6827318920966476 12.833946995477689, 0.26325350312372153 13.735702875276633 M6.441447471037673 13.020167123173307 C4.470351766498922 13.42348910748538, 1.8666767745738087 12.871262364165414, 0.07665925414291669 13.341594460907384 M0.62279566094977 14.311852619940343 C-0.7701963023042613 10.166397566147745, -1.1988275425084185 7.656453133329493, 1.2430244077162267 -0.6258052868016647 M0.31871600533787947 13.332197062824447 C-0.20937246680492447 8.654102561018863, 0.2730583896099861 4.142979712222109, 0.21176989975134508 0.5950916669202637" stroke="#000000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(136.10236632262672 109.11335199790902) rotate(0 3.418704253333999 5.925614611875506)"&gt;&lt;path d="M-0.7134236600250006 -1.6734930668026209 L5.653580877355125 -0.5434945616871119 L7.811421308091667 12.42316674567062 L-0.003879418596625328 12.587814146049801" stroke="none" stroke-width="0" fill="#fc54ee"&gt;&lt;/path&gt;&lt;path d="M0.5800189464733781 0.27421211232041465 C2.4412431776519585 -0.350892726748624, 3.898047543971332 -0.5321672011976656, 7.3431936210012045 -0.26504163187949253 M0.26735502449740695 -0.11416882151384633 C2.2934808537051405 0.1326495748581104, 4.8555962046515235 0.3169758306745296, 7.015193950275245 0.2497316464262192 M5.787056185544101 0.37197207992781456 C7.656839089408684 3.301658856827218, 7.35969900211877 5.138237417466598, 5.71116744151604 11.02746846423648 M6.489439138871285 -0.08217409282387866 C6.70933928546969 3.539849045695814, 6.477200785389395 6.363580496864367, 6.2876988940865 11.720752280764101 M6.176643130120872 11.188939822295236 C4.989375987002836 11.83309388777104, 4.437614409797442 12.307108012289357, -0.40820063892022085 11.642022421606583 M6.515908586772752 11.594683544648724 C4.753179250264562 12.020574736100368, 2.009243360274268 11.97780006672931, -0.1427801690606661 11.817463217680114 M0.5138778333209741 12.625356149630127 C0.9506866756736718 7.699465885242861, 0.4833646474336429 3.2585818331513323, 0.8371176970937675 0.55719511180147 M-0.2194035523066621 11.280790172769361 C-0.5313797901641131 9.231162790299498, -0.3385471725824598 7.128249429646121, -0.5824975795230847 0.4857951128286738" stroke="#000000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(146.40539662565675 107.90123078578783) rotate(0 3.418704253333999 5.925614611875506)"&gt;&lt;path d="M-0.21320042945444584 0.6020698044449091 L5.34893886687496 -1.9179824497550726 L7.906576379558828 13.653621537007872 L-0.7206467781215906 13.510602516450469" stroke="none" stroke-width="0" fill="#fc54ee"&gt;&lt;/path&gt;&lt;path d="M-0.4772669326391108 -0.18788305536461442 C2.4030384421414355 -0.6559330805209902, 5.349325732336909 0.44160151294354316, 7.168300893161164 0.3201168351872167 M-0.14750279654938453 -0.31401424195501954 C1.9371938259941406 -0.15323050450643172, 3.688088362948572 0.036440798167459446, 7.123460203650199 -0.14501418823351347 M5.818797666904361 -0.984042732344001 C5.72746240093523 2.9610789746271706, 5.579525298873979 7.546334287223387, 6.905975082249551 12.765199849593891 M7.037446096805332 -0.3670579930864536 C7.283083547984413 3.888287077300943, 6.321986679804827 9.233945891801783, 7.027648190757878 11.393494441664604 M7.076026389308503 11.876671830670121 C4.7937335954425855 12.14095097906669, 2.913659291321575 12.395506270690701, -0.10546400445449788 12.13264426567479 M6.495687642307391 11.891408009427739 C4.861288359532921 11.756793901129685, 3.161320897276587 11.584839132191172, -0.14383153202100094 11.66394470870019 M-0.4227473664301846 10.859581726799217 C-0.5849911139152131 7.936708870854959, 0.6936619647316058 5.005204514897179, -0.0022987939521743606 0.4364718378460888 M0.5780657787471678 11.945227806528823 C-0.5395985307125286 9.046313617749064, 0.45783456351872587 4.778098470264037, -0.4712881982623274 0.06699959163851277" stroke="#000000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(157.38143027488786 108.69712647980357) rotate(0 3.418704253333999 5.925614611875506)"&gt;&lt;path d="M-0.06811509467661381 0.016264865174889565 L7.958715363762167 -1.3967012073844671 L6.67342685105541 12.292731148519103 L-0.2605895195156336 12.934532684602324" stroke="none" stroke-width="0" fill="#fc54ee"&gt;&lt;/path&gt;&lt;path d="M-0.20920680214442844 0.2308180349287804 C2.154987285601084 0.6367127238268169, 4.514051569429478 -0.4722712943125542, 7.233031167719488 0.21951249271175222 M-0.03376600607089825 0.1193089413202526 C1.5357759103892146 0.0993114311844464, 3.1943466921242485 0.251867132108961, 7.112923630744932 -0.05273200222724894 M7.394603618469468 -1.184604456535371 C6.911219620411112 2.994050920403584, 6.33312933606018 6.945408623358075, 5.802505562287294 11.35262497802785 M7.323203619496672 -0.2113736832150923 C6.3452945236070954 2.555385742997991, 6.680091306272592 6.100843340229306, 7.006862573516333 11.850079826774925 M6.5386336213477065 12.518243893435809 C4.642831809208529 11.4181557711533, 2.8858346532321777 12.5690671904787, -0.37945337189062583 11.307422353585496 M6.582976632242049 11.523378485811131 C4.891635352268613 12.179904523138996, 2.457161412135446 12.155457536444633, -0.0669703829713752 11.850614765128551 M-0.0914145177704988 12.578804848878377 C0.7937777650713524 10.120350659269885, -0.569411698589887 7.7117109731439095, -0.6894490402492769 0.22728426474766117 M-0.21147827835260918 12.232108204074613 C-0.5003163334138555 9.076048892298068, 0.037894286875527206 5.850099521324699, -0.3145476965927946 0.26301923900922797" stroke="#000000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(168.76269630976003 109.71941260396964) rotate(0 3.418704253333999 5.925614611875506)"&gt;&lt;path d="M0.016264865174889565 1.1213068570941687 L5.440707299283531 -0.16398165561258793 L7.278910431436088 11.590639704235379 L1.0833034608513117 11.992587977417294" stroke="none" stroke-width="0" fill="#fc54ee"&gt;&lt;/path&gt;&lt;path d="M0.2308180349287804 -0.4235384190089021 C3.1606879069868383 -0.6220791595760508, 4.61649913589951 0.41654334126593195, 7.05692099937975 -0.5281679450707394 M0.1193089413202526 0.012721303459554578 C2.2559411033423524 0.154990679479108, 4.593682510236906 0.28226832529111395, 6.784676504440749 0.14070752096188854 M5.652804050132627 0.13928317973760773 C5.87202783412027 2.4618410025670414, 5.780242174414072 4.766393269980027, 6.338804260944836 11.201991514533614 M6.626034823452906 -0.49582374847589816 C6.370163675853466 3.4314394554648504, 7.009490215176876 7.354386909398838, 6.836259109691911 12.069465142674057 M7.504423176352795 11.9596916789724 C4.3956392479543585 12.475856187144874, 3.6010398233391703 11.441898363328356, -0.5438068701655165 11.928538263878997 M6.509557768728117 12.03398766148001 C5.16716518954184 11.677158938590036, 3.1643825723459322 11.733372465894448, -0.0006144586224609005 11.591553637613135 M0.7275756251273644 12.72154808300476 C0.9483682032884603 7.037578910394265, 1.194909766712771 2.520220765603224, 0.22728426474766117 -0.6292717143296647 M0.38087898032360146 11.26347069699709 C0.1392311085442275 9.120734483561836, -0.16062120963350324 6.174787859708945, 0.26301923900922797 -0.020181190015251316" stroke="#000000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(179.06572661279006 108.50729139184845) rotate(0 3.418704253333999 5.925614611875506)"&gt;&lt;path d="M1.1213068570941687 -1.3967012073844671 L6.67342685105541 0.44150192476809025 L6.576818987152365 12.934532684602324 L0.1413587536662817 11.37962744883496" stroke="none" stroke-width="0" fill="#fc54ee"&gt;&lt;/path&gt;&lt;path d="M-0.4235384190089021 0.5958926599347948 C1.000022073289685 -0.5361731616974543, 3.6816664872118463 0.1964306892188742, 6.309240561597258 0.13396315396963954 M0.012721303459554578 0.07075539737231318 C1.9138996921439955 0.19944931003277724, 3.806839551835069 -0.07659379049129947, 6.978116027629887 -0.18886617266786468 M6.976691686405606 -1.0490924422571044 C6.454320913234821 3.2516083622557685, 5.858225073467441 8.28636868271498, 6.1881707974506 11.88832687790096 M6.3415847581921 -0.350745314909915 C6.773540563423332 2.6789548324926287, 7.104021596082068 4.7795978191821815, 7.055644425591042 11.759614565753811 M6.945870961889385 11.354970808977958 C4.808840784840071 12.572583695271838, 1.0918107872697351 11.374123820553976, 0.07730904012798356 11.778342302252158 M7.020166944396996 12.159321537126399 C4.4571721842658345 12.06930440284406, 2.2563332994438676 11.785044617540574, -0.2596755861378772 12.07202603671939 M0.8703188592537476 12.775531908820172 C-0.48372915263044197 9.707359887312183, -0.6803075884722269 5.449086318634075, -0.6292717143296647 -0.8744219334006675 M-0.5877585267539223 12.002145945093709 C-0.0072522507940177144 8.030068731805988, -0.27225204092227273 4.769798672540261, -0.020181190015251316 0.004818966137025482" stroke="#000000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(189.43569965595998 108.50729139184845) rotate(0 3.418704253333999 5.925614611875506)"&gt;&lt;path d="M-0.7246999647468328 -1.9919982943683863 L7.087741050294426 -0.19778660871088505 L7.48001942353585 11.32279735184509 L0.06511122919619083 10.835373753078763" stroke="none" stroke-width="0" fill="#fc54ee"&gt;&lt;/path&gt;&lt;path d="M0.1955285238827872 -0.0013262584856745807 C1.8983234975262335 -0.13901032513010173, 3.9248634154449733 -0.3320735023905801, 7.504423176352795 0.1084624552213872 M-0.18972668594531292 -0.27190343508275827 C1.5936186572913378 -0.10190876120466198, 3.212843204517681 -0.3198971748811838, 6.509557768728117 0.1827584377289973 M6.605249974960512 -0.0021300731048006227 C5.894518238308598 5.38880793172595, 6.554050573690611 9.155379462033498, 7.564984131795363 12.72154808300476 M6.492683986543359 0.11364213237383058 C6.473485589062417 4.04246048549413, 6.846549510219592 8.747864626036318, 7.218287486991599 11.26347069699709 M6.474460316934868 12.15472010191493 C4.588760642490287 11.839604419215485, 2.7700275032540556 11.356553107007954, -0.05606047835115058 12.002165674556995 M6.75679515697873 11.733407517821648 C4.860102734636713 11.819797535506346, 2.7744379735248628 11.80427890259564, 0.14134196916697905 11.519474079000911 M0.3453364438968507 11.647686440460154 C-0.4323155283999318 8.397493946373032, 0.9003788694481824 5.385756670873423, 0.3085115384136843 -0.09417179857266111 M-0.10965230110254248 11.58397403012427 C0.5570818461889648 7.65870142843556, 0.14702786596220474 3.946061314345894, 0.5708745835228232 0.20883016434479484" stroke="#000000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(218.82963904989947 108.20426108881814) rotate(0 3.721734556364254 6.228644914905818)"&gt;&lt;path d="M-0.36202881671488285 0.5069883558899164 L8.278396274884017 -1.2309555914252996 L7.9861154220893695 10.537288987970534 L-0.3606365118175745 12.172227883672896" stroke="none" stroke-width="0" fill="#daaeff"&gt;&lt;/path&gt;&lt;path d="M0.3303919862920167 -0.025350630166797616 C2.1768544126383516 0.45683260089537225, 3.82178700827766 -0.021517666368502962, 7.6077844097395815 -0.09698450198076902 M-0.12826529642611406 -0.01651581737959462 C2.5967387691567083 -0.13344368297029066, 5.216062107503459 0.17142213696617983, 7.082307545370359 -0.2810090237684325 M7.229517344827572 -0.5725194322341011 C6.717232008340659 3.3845576569505065, 6.348078450060772 5.436748456739996, 7.344481457822363 12.840578571052468 M7.1625467423419344 0.6019678026770555 C7.135852912835255 4.072104200290424, 7.353827599806211 8.40238886532985, 7.66297865332846 12.231594892298997 M7.682631838288908 12.260621513975984 C5.976029938917584 12.071854296300122, 4.326357045401201 11.781693375597584, 0.6286047229123713 11.789684583850125 M7.586326436461866 12.693377731053042 C5.675948776805768 12.743143751590324, 3.2846048881698637 12.230046158985985, 0.2814949204946174 12.445731739596873 M0.6863837173952303 13.477680847946784 C-0.7794858207804899 6.431975643662891, 0.6776962333921731 2.351694819225727, -0.6319813491458556 -0.9603693768623351 M-0.032689544682295346 12.838899907866432 C0.030531035684077085 8.357416899132083, -0.30994790279408857 3.6956810019019963, -0.14558589076329698 -0.41934003836482237" stroke="#000000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(230.11471456070376 108.01880457724889) rotate(0 3.721734556364254 6.228644914905818)"&gt;&lt;path d="M1.9124569986015558 0.2117794957011938 L6.251506105054887 -1.2463434133678675 L9.200562432397874 13.108923589772168 L-0.986772945150733 11.940454458779278" stroke="none" stroke-width="0" fill="#daaeff"&gt;&lt;/path&gt;&lt;path d="M-0.34232348916682825 0.20428468532138977 C2.0134654479408503 0.6451674526323373, 4.452923987815517 -0.34165154699865724, 6.70210022389156 0.09316712781970382 M0.01211633558534081 -0.1890372204736293 C2.092545619493964 -0.31445084924133326, 4.633161977808211 -0.3141357913971906, 7.334643532066865 -0.26345126563467297 M8.273130356193052 0.8825383985634379 C7.062562997856755 3.2736196917942175, 8.433498160481578 8.178328587285897, 8.58020490842008 11.960641303415805 M7.028893776018471 -0.57569572075684 C7.826123174844326 2.6458430246774185, 7.196116809895103 5.169250882239449, 7.727560182349631 12.832666854423756 M7.569672851984621 12.702865604651711 C5.586338299401819 12.322597490901263, 4.116590025695319 12.001766296338356, -0.1347375157567441 12.645977438190616 M7.0861824386620835 12.39018016139573 C4.846644406983376 12.435578570006035, 2.592467903862625 12.232344942414775, -0.3059380796702856 12.362965718817087 M0.12460175023914521 12.72884579052805 C0.8391682179128391 7.904146608717549, -0.6619557162551849 4.806662988703214, 0.47964141129841975 -0.34831290188354114 M-0.4252059971895157 11.969323092349251 C-0.4871743917598916 9.566103293316102, -0.6517348203732559 7.399923726172492, -0.5795698380345411 0.165848025634748" stroke="#000000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(240.27183626636725 108.38971760038734) rotate(0 3.721734556364254 6.228644914905818)"&gt;&lt;path d="M0.2117794957011938 -1.1919630076736212 L6.19712569936064 1.7570933196693659 L8.09510287268904 11.470516884660903 L-0.5168353710323572 12.297094726896468" stroke="none" stroke-width="0" fill="#daaeff"&gt;&lt;/path&gt;&lt;path d="M0.20428468532138977 -0.08183539392444406 C2.6144326566391367 0.29655465300161077, 3.5249414041957086 -0.7071365399202648, 7.536636240548211 -0.07361092564253957 M-0.1890372204736293 -0.06688418868204882 C2.138085778554054 0.31187909433911754, 4.610604295777149 -0.11124884777871061, 7.1800178470938345 0.11093564535685241 M8.326007511291946 -0.4287229341291733 C6.687603753958948 5.15756737001335, 7.483804891422478 9.567446078726974, 6.946820586332677 11.544820666203453 M6.867773391971668 0.31401569037629506 C7.366633453884132 4.107579991765793, 7.065323526110855 9.131232402298565, 7.818846137340629 11.907858437521986 M7.689044887568583 12.176608203676016 C5.069197432113718 12.295929491086193, 2.5543907610195276 12.335172687980503, 0.18868760837898002 12.768027556955765 M7.376359444312601 12.404243585029185 C5.626650260085769 12.622388208057831, 3.648504894871692 12.079894415328065, -0.09432411099454985 12.50818426792337 M0.27155596071641397 13.17471064402926 C-0.6277301176262975 9.153873797869903, 0.18232570600163656 7.897505417127391, -0.34831290188354114 -1.0537325066304444 M-0.48796673746238384 12.058515990920395 C-0.2707613131096651 7.272763084677038, 0.2465135620597722 2.635335036517112, 0.165848025634748 0.5956007779657811" stroke="#000000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(260.647820868081 108.20426108881813) rotate(0 3.1156739503036306 5.622584308845209)"&gt;&lt;path d="M0.4510419461876154 0.24578442238271236 L6.491200736515793 -0.6437578592449427 L7.395724287979874 12.85776348109028 L1.5666511747986078 11.064027321288798" stroke="none" stroke-width="0" fill="#daaeff"&gt;&lt;/path&gt;&lt;path d="M-0.11236257853983844 -0.08881600798073275 C1.4157131230086593 0.4558892303816591, 2.561315196935474 -0.4524150845078648, 6.073419831820775 0.08521321047408659 M0.06791845131748053 0.17943303662360632 C1.0989321979075237 -0.20224359515569768, 2.5619372207677076 0.11372959805449995, 6.144231886628351 -0.2635474462221986 M5.350374861117218 -0.7199445657793659 C5.913205615066607 3.1544992137283505, 6.847091439743296 7.296728051162809, 6.530769209520002 12.320463688858249 M6.725318167024747 0.1831932876933947 C5.88691807168069 3.3473278698824576, 6.119293251970332 6.76658487720356, 5.724601011433939 11.681375394225832 M5.96132815865984 11.855743563977033 C4.884541535182534 11.541708924113218, 3.390861302088614 10.90356224621934, -0.012086803246702527 11.057252592952063 M5.92772236483275 11.067193474550495 C5.027585611644389 11.261593670385134, 3.619128063066844 11.449837947330126, 0.225720606744997 11.492772593646299 M0.7833472603041991 11.196776915887884 C0.723114948189205 7.901542416875713, -0.6849455215015297 7.265407183608593, 0.7911008439923994 0.8852484039887782 M-0.3587348242176842 11.191623187329284 C-0.5140448645915481 7.513589172397766, -0.05053360483175305 3.5950981148002303, 0.5364425512163262 0.36861380756230466" stroke="#000000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(270.9508511711117 109.41638230093935) rotate(0 3.1156739503036306 5.622584308845209)"&gt;&lt;path d="M-0.5410631205886602 -1.6578939352184534 L6.033200970642838 -0.7504563126713037 L4.954678944103989 13.157969248244974 L0.7992374990135431 11.238483500430796" stroke="none" stroke-width="0" fill="#daaeff"&gt;&lt;/path&gt;&lt;path d="M0.06232791123688797 0.13583690263496107 C2.3134836539034778 -0.31400126384293375, 3.517213553248512 0.09120241407574399, 6.471272678963639 -0.17423202795782156 M-0.2126952598928631 -0.2440892478696049 C1.142836032250031 -0.1354394064087605, 2.402829589320305 0.12331026960104108, 5.941437226389092 0.08295999856130609 M5.530580808663786 0.9879405328349713 C6.609641956688697 3.894024248695208, 5.952660336559635 8.807620265749446, 5.794016086000006 10.231674839343773 M5.938932593498803 -0.24364050737787424 C6.694088208776425 4.2765811127119076, 6.439974313551244 8.420637013076783, 5.952355858956074 11.234262619283173 M6.548046988844056 10.637917546141395 C4.193909268883592 11.49741746755678, 2.8378814974616837 11.330459068391825, 0.34599884163205796 11.696609831180412 M6.15907438961183 11.462208907253704 C3.853327796927916 11.534530797438828, 1.2535295509144257 11.144402603276744, 0.25297111173557213 11.46435717550473 M-0.7757686595794064 10.527698969255049 C-0.125587032595487 5.8829283943443595, -0.36463045437987895 2.5511519245680456, -1.0660917896230862 1.0728851024326524 M0.07305222388941213 11.064189475786097 C0.3019800065689012 7.587737946032505, 0.4150712019501732 2.972688106285366, 0.036119874451467915 0.5413239047932843" stroke="#000000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(281.25388147414174 108.20426108881816) rotate(0 3.1156739503036306 5.622584308845209)"&gt;&lt;path d="M1.95912523008883 0.9339816179126501 L6.458026162140641 1.9863624777644873 L5.934512070172104 10.682797682235453 L1.7925746534019709 12.616262984225962" stroke="none" stroke-width="0" fill="#daaeff"&gt;&lt;/path&gt;&lt;path d="M0.3096771103189987 -0.5798213484363376 C1.6363774763292902 0.652217118392925, 3.006898544209821 -0.3150189360423753, 5.843027929990984 0.5474529884346374 M-0.024955785455949897 -0.12117055677291338 C1.7916257279601 0.25782059495593684, 3.900531358661885 -0.11228137383931165, 6.069310178274739 -0.13500987097371075 M6.088765373249016 -0.5579840833023741 C6.215659298659563 4.136286179016825, 5.254993265718927 9.12795752867276, 6.80286704944896 10.14931571367001 M6.29944831252905 0.31219692574851143 C6.6824357446847475 3.1780274740748284, 5.811519992452787 5.997826952646276, 6.100922215715407 11.636842247842518 M5.911135440537594 11.751110841161562 C4.827136209224514 11.685997503864078, 2.6486641536752598 10.98667513179713, -0.4298810069083596 10.847593166443392 M6.182467958305264 10.949788927933179 C4.718548283256062 11.430250107633702, 2.6814898522487685 11.26427729207245, 0.040480835587636876 11.144881638072782 M-0.10184822108316283 11.317408366593353 C0.9919783038318041 6.912720290668518, -1.0228343483436557 3.0772967841035612, -0.4219503888099506 -0.7178178842425084 M0.3662283673579432 10.925105481861468 C-0.07540355654382347 7.051757494386256, 0.03832680285571177 2.5895780464518, 0.0017652106283896352 -0.2547992514489215" stroke="#000000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(290.9508511711117 108.20426108881816) rotate(0 3.1156739503036306 5.622584308845209)"&gt;&lt;path d="M0.7010932061821222 -1.52984438277781 L7.113146832936081 1.3162624444812536 L7.5358054552009435 10.715786409328196 L-1.8085798528045416 9.287093889663431" stroke="none" stroke-width="0" fill="#daaeff"&gt;&lt;/path&gt;&lt;path d="M0.4834353798694532 -0.4886660958239201 C1.5627242415492348 -0.2679141495996812, 3.368965856593136 0.6667362413519787, 6.265279809540792 0.32894785088930456 M-0.09395801236917731 -0.2722130105935556 C1.8394779693899088 0.1595582244430675, 3.277163104985585 -0.17676646281525224, 6.352563960639456 -0.015244856128964979 M7.125006384917892 -0.9271550255314837 C7.364648263371464 3.2489000393669936, 7.077134546061163 6.9711111164988715, 7.061548946557917 10.347937976651355 M6.673972102601651 0.09248085936609829 C6.134272106986565 2.4078068317773424, 5.963916044936522 5.537836892772957, 5.679627283582184 11.072101476437087 M6.639871943950593 11.385698581913518 C4.5362818272498995 11.385582668442456, 2.4874849702156197 11.667402715115733, 0.5024319808288397 11.733286043143679 M6.147059087092857 10.986895770374092 C4.387513190876219 11.188253124383152, 2.4065303934005273 11.603145138431858, 0.12450817278911602 11.244127185405391 M0.5112554862669827 11.411697827821726 C0.5489869504391666 7.142691718924185, -0.4455858757320532 4.134688655420415, 0.5251390389625528 0.12745176364538913 M0.5039451059325779 11.630623301251859 C0.04372520856237548 6.214735431718187, -0.5174657091585682 2.088746505044885, 0.24240505112041466 0.5362759266579591" stroke="#000000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(153.48917286035044 133.46298868842794) rotate(0 -0.4981727561221305 24.042046276215796)"&gt;&lt;path d="M1.0755447920411825 0.8226566199213268 C0.9128721321853238 8.702696603609981, -0.5829034724671449 39.12649996764392, -1.1541374573962457 46.942817340858944 M0.1810670785233377 0.2089474000409246 C-0.11899421959050127 8.222030032760264, -2.032367679572484 39.769758785845646, -2.0718903042854437 47.875145152390665" stroke="currentColor" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(153.48917286035044 133.46298868842794) rotate(0 -0.4981727561221305 24.042046276215796)"&gt;&lt;path d="M-8.104929504262003 25.083817094841447 C-9.349974970753527 29.547809114928846, -5.942386310339137 38.83665686928682, -0.16431223270610662 48.57623835857279 M-8.823795093500706 24.722958953396823 C-8.62452149085801 29.075417016937948, -5.177948397128917 34.93767405594667, -2.0798954716430673 47.457711632542974" stroke="currentColor" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(153.48917286035044 133.46298868842794) rotate(0 -0.4981727561221305 24.042046276215796)"&gt;&lt;path d="M8.339106951219872 25.67918843834183 C2.4065836066291695 30.120791247485947, 1.125020291247444 39.23986397615207, -0.16431223270610662 48.57623835857279 M7.6202413619811695 25.318330296897205 C4.141519878984737 29.64594157077442, 3.912722876149891 35.375128482009075, -2.0798954716430673 47.457711632542974" stroke="currentColor" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g transform="translate(86.76456876456905 288.6038096144189) rotate(0 12.039393908506213 5.967411895052081)"&gt;&lt;path d="M-1.4324995186179876 -0.18426320888102055 L25.509431921269652 -1.3784433994442225 L25.23745324495053 13.873695790306698 L-0.7096782233566046 11.057304143444668" stroke="none" stroke-width="0" fill="#f41d92"&gt;&lt;/path&gt;&lt;path d="M0.9559627380222082 1.2773270700126886 C8.720613084813934 -1.2283543716678411, 15.689691150721872 0.9647815455188959, 22.90110448407745 0.04783589579164982 M0.7712464081123471 0.8353659911081195 C8.483504285246484 -0.9636778766569958, 17.625057702236578 -1.0225103730616436, 24.73691656007236 0.532556246034801 M24.027245875149845 -0.7637977908691629 C23.808436589507238 1.3483213435562094, 25.12735319104351 4.061448754888094, 24.205011175540356 10.941536064699763 M24.353946523291643 -0.07948520286848215 C24.339557154491594 3.4241639944578384, 23.73099402936093 7.398576280117092, 24.571305360394167 12.0801704826894 M25.949628327365872 11.277099061296354 C17.65146696957668 10.223084487789746, 14.439447198091198 10.341662146919843, 1.016716381534934 13.851276862905394 M23.170538950104174 11.853769261860322 C14.940127929343364 11.437696138237051, 6.9374133273447605 12.246373483751348, -0.3661751104518771 12.296067167543839 M0.04219220473927443 12.513800167850306 C1.256549936826116 8.629821220982548, -0.0498245549369435 6.770594026226293, 1.0924033041889916 0.9614270686075603 M-0.46087712813711224 11.446516237714256 C0.2620707966023077 8.520818042514438, 0.2518958269596929 6.208912882619414, 0.36632236736942425 0.1120678711508124" stroke="#000000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(140.3447402278848 194.0394418655335) rotate(0 14.90250780610279 12.090713880423053)"&gt;&lt;path d="M-0.18426320888102055 1.430644104257226 L28.42657221276144 1.1586654279381037 L31.743887612408198 23.471749537489508 L-0.8775196466594934 25.430563860437964" stroke="none" stroke-width="0" fill="#fc54ee"&gt;&lt;/path&gt;&lt;path d="M1.2773270700126886 1.854996582493186 C9.682447489627005 1.8159957244231808, 23.017997367859486 -1.3201457545921695, 29.85285150799731 1.086525758728385 M0.8353659911081195 -0.04824321996420622 C7.723455141414892 0.33169257946873704, 16.13654867862352 0.42825863071347275, 30.337571858240462 -0.9774476541206241 M28.525067446565426 -0.3779036197811365 C28.020186462953806 8.178198013238495, 28.521654823377848 12.903326974749337, 28.1404954770563 24.129983966103886 M29.67181682474742 0.36133060324937105 C29.72347220130277 7.510712170524872, 30.43511032409978 17.16373009796883, 30.048582999779082 23.422793317451273 M29.147290883397854 23.151043479195927 C19.312660063147675 25.614762426591163, 10.61854170932816 25.476946295476203, 1.9164530728012323 25.287704949132298 M29.72396108396182 23.598294902457987 C18.80885418685277 23.880403613160635, 9.02757736023932 23.54454893381455, 0.3612433774396777 23.760500151529108 M0.9702302906662226 26.1677885100759 C-0.405682363841079 18.166914083286205, 1.6368283244638222 14.38865585217522, 1.611129054799676 -1.1893957648426294 M-0.8182903425768018 24.48830123604754 C-0.8101341061531185 18.816380804285867, 0.12625262809408913 13.4855601115989, 0.18779979180544615 -0.7162497593089938" stroke="#000000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(173.1544099497114 242.0805448161841) rotate(0 11.799059530240811 8.987265604560996)"&gt;&lt;path d="M1.430644104257226 -1.3784433994442225 L24.756784488419612 1.9388720002025366 L22.888440837124904 17.09701156246249 L1.2491360995918512 19.023194607372922" stroke="none" stroke-width="0" fill="#e08fff"&gt;&lt;/path&gt;&lt;path d="M1.854996582493186 -1.4599664714187384 C6.7680080430174305 0.5824568643813215, 8.441416460939136 -0.10287668559502733, 24.684644819209893 -0.315100422129035 M-0.04824321996420622 -0.7484708921983838 C9.75010792182362 -1.004434049775497, 19.035219203076778 0.3354255848623356, 22.620671406360884 -0.3757120566442609 M23.258487040091698 -1.6047935172840495 C25.237088803099212 4.244205045326619, 23.78045618679301 9.300682799865715, 23.551885155776016 18.654493343322457 M23.922856470727343 -0.1891374985970542 C23.089206363305045 6.800982080875894, 24.422759782316984 12.920003474637445, 22.916314136525738 17.935690842422567 M22.567734778831323 16.29487167301361 C17.98760964761288 16.33584399984656, 11.084751590979403 19.81337894962607, 1.1062771882861853 19.08068697157089 M23.014986202093382 17.568323955896837 C17.37941706581939 18.35787395733224, 10.855180822916976 18.31664724248277, -0.4209276093170047 17.377901867989046 M1.7851951639802883 17.632768929001305 C-0.18863436189972185 13.887202273636134, 1.638867830643985 8.260045161072476, -1.0689415647580678 -1.1889835311210035 M0.27579534286298935 17.156259951828943 C0.39929274686180416 13.351336233490205, 0.6905240525099907 8.850353175534758, -0.6437126826112809 -0.08280111996812167" stroke="#000000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(108.43619024110512 241.17659980376573) rotate(0 10.764576771620114 9.332093190767885)"&gt;&lt;path d="M-1.3784433994442225 1.1586654279381037 L23.46802554344265 -0.7096782233566046 L20.65163389658062 19.913322481127615 L1.0486633982509375 18.695083352537985" stroke="none" stroke-width="0" fill="#f938c5"&gt;&lt;/path&gt;&lt;path d="M-1.4599664714187384 1.9584581460803747 C8.22096893716485 -1.377365864168383, 15.023434878585352 0.8868432274949776, 21.21405312111108 1.7875234093517065 M-0.7484708921983838 0.5615626918151975 C7.311870908361318 0.6685150999602302, 16.7709048399635 -0.9670612972203271, 21.153441486595852 0.003313724882900715 M19.86278656198225 1.7099148445582164 C20.211315553541056 5.716045898493422, 19.856361885716705 10.989297027067646, 22.2352047808192 19.659142877295224 M21.33275912913927 -0.6144998799125952 C21.972005987194603 7.2300856883352695, 21.73159242919767 12.211766770634737, 21.488822931288617 18.539856465076554 M19.84949400713174 19.818718894185658 C11.495444973174148 19.569986256029757, 6.500353229865972 19.65954706278101, 1.106155762448907 17.90899555272066 M21.122946290014966 18.63386595042997 C17.42826050367139 18.17246770264042, 12.883670656964046 18.117715203775294, -0.5966293411329389 18.769260553443964 M-0.3548751742194991 18.474912605613046 C1.4606720395305495 14.707295368711847, 1.4129778532844048 6.223755402832478, -1.2346030041749803 -0.7744982867731891 M-0.8496670694265589 18.934651516951263 C0.1769049919134569 15.079279618783335, 0.32798375051416473 9.680220827387803, -0.08597807184538053 0.6675452051875538" stroke="#000000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(122.11225621732115 287.9521524124162) rotate(0 13.251515120627346 5.967411895052109)"&gt;&lt;path d="M1.1586654279381037 1.9388720002025366 L25.793352017898087 -0.8775196466594934 L27.752166340846543 12.983487188355156 L0.030896971002221107 11.362511038319298" stroke="none" stroke-width="0" fill="#f41d92"&gt;&lt;/path&gt;&lt;path d="M1.9584581460803747 0.7331694457679987 C8.46587763766741 0.24130357350779347, 20.373647699933155 -0.12163274441289135, 28.290553650606398 1.5424928162246943 M0.5615626918151975 -0.8073033886030316 C10.82301082792176 0.7309237129411275, 19.35231651560303 -0.1773445897379344, 26.506343966137592 -0.043186177499592304 M27.596436187204453 -0.7663124159783408 C26.60670253422397 1.8233616207576957, 26.4497805444289 6.084783527239248, 27.139255695339106 12.485141202662653 M26.110087970111685 0.2990792685888721 C27.03867195543397 3.5668234238795407, 26.093445991901163 6.817164031581614, 26.423527410836805 12.493027585865644 M27.657562753904585 10.37374191312594 C20.440422469313933 13.62869652550299, 13.450659122589494 12.818399215150665, -0.7551908288151026 10.118326056287714 M26.4727098101489 12.337293882393368 C18.40076511975012 12.188409936379465, 10.609922609832296 11.230537217806848, 0.10507417190819979 11.970175970816143 M-0.12103121548121965 12.9267489311712 C0.9738689765608123 8.736827343934369, -0.8282957711328355 2.82650922071928, -0.49525333649261516 -0.9217542562742289 M0.17294907297668305 11.950284269672673 C0.3296975588455664 9.44305962900358, -0.4640405418767296 6.672181002376462, 0.426862132266537 -0.41128697692497596" stroke="#000000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(160.28986485651967 289.9393655481342) rotate(0 12.206583041212525 5.622584308845219)"&gt;&lt;path d="M1.9388720002025366 -0.7096782233566046 L23.535646435765557 1.2491360995918512 L25.461829480675988 11.27606558869266 L-0.5723127517849207 13.136327207783694" stroke="none" stroke-width="0" fill="#fc54ee"&gt;&lt;/path&gt;&lt;path d="M0.7331694457679987 -1.1776833329349756 C9.636102703056018 0.8474649387996928, 18.8614331923997 1.5484625894230142, 25.955658898649745 1.670731982216239 M-0.8073033886030316 0.6581287430599332 C7.738729096628604 -1.114542886730405, 14.036633644543345 -0.13378150772688016, 24.369979904925458 -0.6399740828201175 M23.691135117992108 0.11892952716358418 C23.37687702526996 3.1321463817128996, 24.738163236171168 6.9216032707173305, 24.931683339680397 11.095384335226456 M24.694963020904023 0.4640573602008764 C24.52644838041623 3.3533243295760946, 24.365201377509646 7.484837132497045, 24.939114007318313 11.060262980703726 M22.852084205446772 12.261884999225373 C20.26341646033108 12.492374775871031, 13.386916455083547 10.630906758769743, -1.8164977338165045 11.08305956120276 M24.8156361747142 10.878993507238562 C18.87407156713207 10.816838831170669, 12.015860955826794 11.342840612395873, 0.03535218071192503 11.73028376302355 M0.9346066321208069 12.274447270661668 C0.9272521165591658 6.08602215790965, -0.7224668445210969 1.7977362258205167, -0.8684905800177749 -0.920181288050381 M0.014567093969312328 11.590323004713422 C0.2079768646468909 7.123816169299145, 0.05058307703832611 4.210094507095764, -0.3875207114173175 0.32573470271631066" stroke="#000000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(195.77349721401788 289.4841663177622) rotate(0 15.236886071515642 6.228644914905814)"&gt;&lt;path d="M-0.8775196466594934 1.2491360995918512 L31.52243554128222 0.030896971002221107 L29.901459391246362 14.348448419904884 L0.11630239151418209 10.490030610895332" stroke="none" stroke-width="0" fill="#daaeff"&gt;&lt;/path&gt;&lt;path d="M0.04783589579164982 1.086525758728385 C11.811111585546191 1.966965677074237, 25.794916831575147 1.8501742499387694, 30.37728570310287 -1.4969417843967676 M0.532556246034801 -0.9774476541206241 C10.477712937705487 -0.19724292868497917, 21.663663811199903 -0.8405307363879974, 30.284820333140715 -0.8928152276203036 M29.437001655475644 -0.03204251305246286 C30.865035553184917 5.542535735645636, 30.96819468531654 9.590987395187915, 30.923892147937092 12.195125480678188 M30.62548162003589 -0.47252645681236716 C30.50928812693139 2.833915857017503, 31.118846593184834 5.628961413142117, 30.152877252216317 11.934189688393904 M32.390225215832515 13.563567018097814 C20.67864822646009 11.85701491772144, 6.854715051174583 12.450096690048863, -1.1662657167762518 11.644875323361333 M30.83501552047096 12.036362220494624 C20.827408949868598 12.45077663291623, 12.409656619683716 12.830817606341142, 0.9931803746148944 12.267152865378733 M1.0035150794435013 11.716457441561873 C-0.9275625720796831 8.329498586732592, -1.0656403515559816 4.216207037521594, 0.3822811821665699 -1.1342095204497615 M0.11697382182493632 12.01116328769938 C-0.060612962318156285 8.804611195445549, -0.43251918088979346 4.621680924867485, 0.6038272612357398 -0.2210166828564754" stroke="#000000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(151.18884242870126 219.92425786089694) rotate(0 -11.434738171350318 9.095722503524513)"&gt;&lt;path d="M1.086525758728385 -0.315100422129035 C-5.876227252857398 7.420545618529638, -13.554051615357972 11.654247418336954, -23.95600210142902 18.567157063693287 M-0.9774476541206241 -0.3757120566442609 C-4.806472633913799 3.9013678664170133, -10.374350986848492 7.580549149423564, -23.351875544652557 18.36017922038633" stroke="#ccc" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(154.60123024323457 220.32265537238413) rotate(0 14.015916574758307 9.35257060469435)"&gt;&lt;path d="M-0.315100422129035 1.7875234093517065 C8.125825105257778 6.1866284834520755, 13.065551472174075 7.683077342820599, 28.407545206160876 17.72930175470802 M-0.3757120566442609 0.003313724882900715 C10.04082305466567 6.702967619047348, 19.87604946132599 13.526377254618879, 28.200567362853917 18.7018274845058" stroke="#ccc" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(116.93677195595183 264.19156414601423) rotate(0 -4.072183551724152 11.723443794739033)"&gt;&lt;path d="M1.7875234093517065 1.5424928162246943 C-0.7238842615719037 6.595589764494374, -6.406636384765367 14.52038169055623, -9.93189051280001 23.490073766977645 M0.003313724882900715 -0.043186177499592304 C-3.7948387909079853 7.612824472466724, -6.985807196729778 16.550949142817963, -8.95936478300223 22.279576835640825" stroke="#ccc" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(121.03565727228215 266.9011999318549) rotate(0 8.033809814705933 10.563099880350755)"&gt;&lt;path d="M1.5424928162246943 1.670731982216239 C5.11299638242185 5.70576043333631, 11.574705594182726 12.92302566553109, 16.110805806911458 21.766173843521642 M-0.043186177499592304 -0.6399740828201175 C2.7255466480029824 3.2935925416078984, 6.822920839461268 7.73122474167714, 14.900308875574638 19.86880128387736" stroke="#ccc" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(176.2127268770846 263.99816713564815) rotate(0 -5.146763086143324 10.949628782273727)"&gt;&lt;path d="M1.670731982216239 -0.09648643992841244 C-4.463510496552928 7.494754446635733, -7.745390050624151 13.973660476733974, -10.066885594858604 20.159035963567902 M-0.6399740828201175 -0.18895180989056826 C-3.8134392692756465 6.7146723775509845, -6.48406192556211 11.707617713691214, -11.964258154502886 22.088209374438037" stroke="#ccc" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(197.87104022815902 264.112064933825) rotate(0 3.53116285350643 9.672422305658294)"&gt;&lt;path d="M-1.4737507421190548 1.1057256099541988 C1.63880030090153 8.057148159825726, 7.355716316570035 11.594017081564703, 7.423862215947794 17.924590495964555 M-0.8789834835232732 0.9019543254892166 C1.667033129987356 5.417585877157989, 3.893638270926999 10.586178827544083, 8.536076449131933 18.44289028582738" stroke="#ccc" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(200.89985845474848 289.00108782190574) rotate(0 0.5 6.75)"&gt;&lt;path d="M0 0 C0.24032276617363096 3.244357343344018, 0.48064553234726193 6.488714686688036, 1 13.5 M0 0 C0.23496605260297657 3.1720417101401837, 0.46993210520595313 6.344083420280367, 1 13.5" stroke="#000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(206.39985845474848 288.75108782190574) rotate(0 0.5 6.75)"&gt;&lt;path d="M0 0 C0.21157085103914142 2.856206489028409, 0.42314170207828283 5.712412978056818, 1 13.5 M0 0 C0.3721991633065045 5.024688704637811, 0.744398326613009 10.049377409275621, 1 13.5" stroke="#000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(210.89985845474848 288.75108782190574) rotate(0 0.5 6.75)"&gt;&lt;path d="M0 0 C0.35065756542608145 4.7338771332521, 0.7013151308521629 9.4677542665042, 1 13.5 M0 0 C0.2696682636626065 3.6405215594451876, 0.539336527325213 7.281043118890375, 1 13.5" stroke="#000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(214.89985845474848 288.87608782190574) rotate(0 0.5 6.75)"&gt;&lt;path d="M0 0 C0.33321742760017514 4.498435272602364, 0.6664348552003503 8.996870545204729, 1 13.5 M0 0 C0.267552615236491 3.6119603056926284, 0.535105230472982 7.223920611385257, 1 13.5" stroke="#000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(220.39985845474848 288.62608782190574) rotate(0 0.5 6.75)"&gt;&lt;path d="M0 0 C0.33844768805429337 4.5690437887329605, 0.6768953761085867 9.138087577465921, 1 13.5 M0 0 C0.2322900806553662 3.1359160888474436, 0.4645801613107324 6.271832177694887, 1 13.5" stroke="#000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(164.28933362396356 289.93858782190574) rotate(0 0.5 6.75)"&gt;&lt;path d="M0 0 C0.32418523179367187 4.37650062921457, 0.6483704635873437 8.75300125842914, 1 13.5 M0 0 C0.2805317829363048 3.787179069640115, 0.5610635658726096 7.57435813928023, 1 13.5" stroke="#000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(169.7893336239622 289.68858782190574) rotate(0 0.5 6.75)"&gt;&lt;path d="M0 0 C0.3453239123336971 4.661872816504911, 0.6906478246673942 9.323745633009821, 1 13.5 M0 0 C0.3496941183693707 4.720870597986504, 0.6993882367387414 9.441741195973009, 1 13.5" stroke="#000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(174.289333623964 289.68858782190574) rotate(0 0.5 6.75)"&gt;&lt;path d="M0 0 C0.33057225989177824 4.462725508539006, 0.6611445197835565 8.925451017078013, 1 13.5 M0 0 C0.284787807893008 3.844635406555608, 0.569575615786016 7.689270813111216, 1 13.5" stroke="#000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(178.28933362396538 289.81358782190574) rotate(0 0.5 6.75)"&gt;&lt;path d="M0 0 C0.25355723602697255 3.4230226863641295, 0.5071144720539451 6.846045372728259, 1 13.5 M0 0 C0.39227480338886384 5.295709845749662, 0.7845496067777277 10.591419691499324, 1 13.5" stroke="#000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(183.78933362396356 289.56358782190574) rotate(0 0.5 6.75)"&gt;&lt;path d="M0 0 C0.26134025799110533 3.528093482879922, 0.5226805159822107 7.056186965759844, 1 13.5 M0 0 C0.29703438384458425 4.009964181901887, 0.5940687676891685 8.019928363803775, 1 13.5" stroke="#000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(126.14985845474848 287.43858782190574) rotate(0 0.5 6.75)"&gt;&lt;path d="M0 0 C0.2823092435486615 3.81117478790693, 0.564618487097323 7.62234957581386, 1 13.5 M0 0 C0.3148826132528484 4.250915278913453, 0.6297652265056968 8.501830557826906, 1 13.5" stroke="#000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(131.64985845474712 287.18858782190574) rotate(0 0.5 6.75)"&gt;&lt;path d="M0 0 C0.34949533743783834 4.718187055410818, 0.6989906748756767 9.436374110821635, 1 13.5 M0 0 C0.29862432824447754 4.031428431300447, 0.5972486564889551 8.062856862600894, 1 13.5" stroke="#000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(136.14985845474894 287.18858782190574) rotate(0 0.5 6.75)"&gt;&lt;path d="M0 0 C0.2894334618933499 3.9073517355602236, 0.5788669237866998 7.814703471120447, 1 13.5 M0 0 C0.29494868917390704 3.981807303847745, 0.5898973783478141 7.96361460769549, 1 13.5" stroke="#000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(140.1498584547503 287.31358782190574) rotate(0 0.5 6.75)"&gt;&lt;path d="M0 0 C0.24263905389234425 3.2756272275466474, 0.4852781077846885 6.551254455093295, 1 13.5 M0 0 C0.2681751136668027 3.6203640345018364, 0.5363502273336054 7.240728069003673, 1 13.5" stroke="#000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(145.64985845474848 287.06358782190574) rotate(0 0.5 6.75)"&gt;&lt;path d="M0 0 C0.229770437348634 3.101900904206559, 0.459540874697268 6.203801808413118, 1 13.5 M0 0 C0.28091181023046374 3.7923094381112605, 0.5618236204609275 7.584618876222521, 1 13.5" stroke="#000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(89.14985845474848 288.93858782190574) rotate(0 0.5 6.75)"&gt;&lt;path d="M0 0 C0.37955497251823545 5.1239921289961785, 0.7591099450364709 10.247984257992357, 1 13.5 M0 0 C0.2154642234556377 2.908767016651109, 0.4309284469112754 5.817534033302218, 1 13.5" stroke="#000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(94.64985845474712 288.68858782190574) rotate(0 0.5 6.75)"&gt;&lt;path d="M0 0 C0.29807842774316673 4.024058774532751, 0.5961568554863335 8.048117549065502, 1 13.5 M0 0 C0.2735304270870984 3.6926607656758286, 0.5470608541741968 7.385321531351657, 1 13.5" stroke="#000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(99.14985845474894 288.68858782190574) rotate(0 0.5 6.75)"&gt;&lt;path d="M0 0 C0.34378559039905665 4.641105470387265, 0.6875711807981133 9.28221094077453, 1 13.5 M0 0 C0.38724592132493857 5.227819937886671, 0.7744918426498771 10.455639875773342, 1 13.5" stroke="#000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(103.1498584547503 288.81358782190574) rotate(0 0.5 6.75)"&gt;&lt;path d="M0 0 C0.2742341528646648 3.7021610636729747, 0.5484683057293296 7.404322127345949, 1 13.5 M0 0 C0.3478682761080563 4.69622172745876, 0.6957365522161126 9.39244345491752, 1 13.5" stroke="#000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(108.64985845474848 288.56358782190574) rotate(0 0.5 6.75)"&gt;&lt;path d="M0 0 C0.35679293023422365 4.816704558162019, 0.7135858604684473 9.633409116324039, 1 13.5 M0 0 C0.3495560119859874 4.71900616181083, 0.6991120239719748 9.43801232362166, 1 13.5" stroke="#000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;
&lt;/svg&gt;
&lt;figcaption&gt;B-Tree Index Deduplication&lt;/figcaption&gt;&lt;/p&gt;
&lt;/figure&gt;
&lt;p&gt;Starting at PostgreSQL 13, when B-Tree deduplication is activated, duplicate values are only stored once. This can make a huge impact on the size of indexes with many duplicate values.&lt;/p&gt;
&lt;p&gt;In PostgreSQL 13 index deduplication in enabled by default, unless you deactivate it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;-- Activating de-deduplication for a B-Tree index, this is the default:&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;INDEX&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;index_name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;table_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;column_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;WITH&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;deduplicate_items&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;If you are migrating from PostgreSQL versions prior to 13, you need to rebuild the indexes using the &lt;code&gt;REINDEX&lt;/code&gt; command in order to get the full benefits of index de-deduplication.&lt;/p&gt;
&lt;p&gt;To illustrate the effect of B-Tree deduplication on the size of the index, create a table with a unique column and a non unique column, and populate it with 1M rows. On each column create two B-Tree indexes, one with deduplication enabled and another with deduplication disabled:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;=#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;test_btree_dedup&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n_unique&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;serial&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;n_not_unique&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;integer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;TABLE&lt;/span&gt;

&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;=#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;INSERT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;INTO&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;test_btree_dedup&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n_not_unique&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)::&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;generate_series&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1000000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;INSERT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1000000&lt;/span&gt;

&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;=#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;INDEX&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ix1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;test_btree_dedup&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n_unique&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="k"&gt;WITH&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;deduplicate_items&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;OFF&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;INDEX&lt;/span&gt;

&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;=#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;INDEX&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ix2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;test_btree_dedup&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n_unique&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="k"&gt;WITH&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;deduplicate_items&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;INDEX&lt;/span&gt;

&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;=#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;INDEX&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ix3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;test_btree_dedup&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n_not_unique&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;WITH&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;deduplicate_items&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;OFF&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;INDEX&lt;/span&gt;

&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;=#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;INDEX&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ix4&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;test_btree_dedup&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n_not_unique&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;WITH&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;deduplicate_items&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;INDEX&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Next, compare the sizes of the four indexes:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Column&lt;/th&gt;
&lt;th&gt;Deduplication&lt;/th&gt;
&lt;th&gt;Size&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Not unique&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;6840 kB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Not unique&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;21 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unique&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;21 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unique&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;21 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;As expected, deduplication had no effect on the unique index, but it had a significant effect on the index that had many duplicate values.&lt;/p&gt;
&lt;p&gt;Unfortunately for us, PostgreSQL 13 was still fresh at the time, and our cloud provider did not have support for it yet, so we were unable to use deduplication to clear space.&lt;/p&gt;
&lt;h4 id="clearing-bloat-in-tables"&gt;&lt;a class="toclink" href="#clearing-bloat-in-tables"&gt;Clearing Bloat in Tables&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Just like in indexes, tables can also contain dead tuples that cause bloat and fragmentation. However, unlike indexes that contain data from an associated table, a table can not just simply be re-created. To re-create a table you would have to create a new table, migrate the data over while keeping it synced with new data, create all the indexes, constraints and any referential constraints in other tables. Only after all of this is done, you can switch the old table with the new one.&lt;/p&gt;
&lt;figure&gt;
&lt;p&gt;&lt;svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 285.30256543554844 135.73045230480716" width="30em" height="auto"&gt;
  &lt;g transform="translate(22.673209503165367 14.920893655339711) rotate(0 4.375420648808699 9.594111612924156)"&gt;&lt;path d="M0.8274188842624426 0.33696223236620426 L10.254759846663319 -0.34771900437772274 L8.006780980563008 18.652658693344826 L-0.23554847575724125 21.027749948055977" stroke="none" stroke-width="0" fill="#f41d92"&gt;&lt;/path&gt;&lt;path d="M-0.7212407404954484 -0.6647660147306157 C2.4220520806157584 -0.2533850251695737, 3.494795551723251 -0.414854797894422, 9.17304400743558 -0.5623765174982176 M0.43047284510851164 -0.39303908930895154 C3.317387033400377 0.5146720902907246, 6.200620459826327 0.4342160202835075, 8.863374918634467 -0.41185744595629076 M7.172910073302858 1.2527884402652683 C8.601072085210854 7.576502704249869, 7.563271859464784 15.456118112834455, 8.620167323001736 20.512591212095018 M8.980682284833168 0.023624706921751892 C9.28682093416313 7.543541007398155, 8.752749602079486 13.603205771305223, 8.426565253327865 19.811796972814452 M8.450619929846667 18.59576841930935 C5.523127368689286 19.01469574965876, 3.384117341196169 19.39274446522339, -0.3994007866634297 19.56639710608713 M8.734565974374416 19.38764348988301 C5.8393005699118685 19.57561329060369, 2.38378615564748 19.59343911214239, 0.2988339317948095 19.314149900576005 M-0.40782254301627785 20.303279007470596 C1.7986539441708702 10.926477409796798, 0.3782626060284646 3.625453195123502, 1.8367175910200106 -0.9094012277727832 M0.813918030037509 19.938396242027007 C-0.5329160369557114 12.522799365047204, 0.6104175717327626 6.344884937962021, -0.32766010242479304 0.37160091957861485" stroke="#000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(34.55481513997495 14.107190599746815) rotate(0 3.340937890188002 10.628594371544882)"&gt;&lt;path d="M-0.34771900437772274 -0.7440603170543909 L6.146311247872518 -0.23554847575724125 L8.521402502583669 21.051596429308383 L-0.14657854102551937 21.764434900244204" stroke="none" stroke-width="0" fill="#f071ff"&gt;&lt;/path&gt;&lt;path d="M-0.15069683449311344 -0.3969232997634849 C1.1798550475458567 0.2907127427973598, 2.4782769877663666 0.2607766957722901, 7.167455133221267 -0.6192963653022694 M0.3310582897718325 0.14058922887522624 C2.931579116683631 0.03871767468797886, 5.009427464405206 0.1555718204502795, 6.862517860394668 -0.07404880973683409 M7.364990985172199 -1.3459492828696966 C8.502829382261076 4.492883080472155, 8.201059470018187 10.386534550869671, 5.93361923672478 22.165570154895512 M7.416189898887978 0.07681469153612852 C6.609085327879419 6.548408831524864, 7.337064838901987 13.733789096696706, 7.229858656565057 20.938605264869956 M6.5706628391389374 21.116304242753586 C5.084006474054464 20.903609190613057, 3.4199921419200914 21.648720927954397, 0.37648464801170856 21.181885449617358 M6.985205359556748 21.284907838862893 C5.01910856147761 21.52967731088082, 2.8186134477433304 21.509302621424716, 0.16163390757211182 21.061192411440533 M-1.367270389571786 21.74821372341129 C-1.8567035396866145 17.24624977651266, -1.0714029868416133 10.563875444482191, 1.2477255072444677 0.9579601977020502 M0.20999691355973482 22.018203185049323 C0.7619157819622576 12.735152944227638, 0.22110981630025503 5.309870762182808, 0.16848111618310213 0.7519592745229602" stroke="#000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(44.34431667403794 14.107190599746389) rotate(0 3.68576547639492 10.973421957751771)"&gt;&lt;path d="M-0.7440603170543909 -0.5355645325034857 L7.135982477032599 1.8395267222076654 L7.165938639008459 21.800265374478023 L0.5072461571544409 23.226095917519253" stroke="none" stroke-width="0" fill="#f938c5"&gt;&lt;/path&gt;&lt;path d="M-0.43789086871132477 -0.30226953477171975 C2.0071288849805393 -0.4571094869379356, 3.625576952891625 0.5523224299333185, 6.688315269675325 0.725243161001419 M0.1550998381811084 0.29745318692135014 C1.6440790322618437 -0.419985895587877, 3.3222770185355657 0.12624110742452477, 7.289839357355601 -0.3030965588762574 M6.025581669920143 1.6821665968745947 C7.20633208914226 9.397347112667386, 8.470898212627826 15.285644312420139, 8.279912364595589 22.425973190810602 M7.448345644325968 -0.07802485954016447 C7.172565052889436 7.28805011543337, 7.471541623257249 13.824173804090986, 7.052947474570033 21.603766767192703 M7.2161053673507105 22.142612932809314 C5.322696451779057 22.253140756213647, 4.432325873590679 22.34991744765606, -0.08307555796070243 21.919423912106847 M7.402111014316135 22.296497102741046 C5.075828141690694 22.119849403792685, 2.5059160782790246 22.19208792825014, -0.2162256637019158 21.868507415461156 M0.4910249803215265 20.213669015910547 C1.6980799227952434 13.139553120063661, 0.941066303849168 7.475736525364237, 0.9579601977020502 1.6967032756656408 M0.7610144419595599 22.874971745420318 C-0.24699943891357967 14.034067309832816, 0.5082320905606788 5.515128091835646, 0.7519592745229602 -0.17385950218886137" stroke="#000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(55.66031502161354 12.28632084886354) rotate(0 3.6857654763949768 11.318249543958633)"&gt;&lt;path d="M-0.5355645325034857 -0.23554847575724125 L9.211057674997619 -0.20559231378138065 L7.224952411764434 23.143745245071706 L1.2792520020157099 21.40988838824874" stroke="none" stroke-width="0" fill="#daaeff"&gt;&lt;/path&gt;&lt;path d="M-0.3022695347717244 0.35565498652385585 C2.0870943787608054 0.3664182428493844, 5.657355229148095 -0.6394265962433922, 8.096774113791383 -0.6621762900182322 M0.2974531869213547 0.09479603644311518 C2.385340957156624 0.1926910592618869, 5.663849263716174 -0.11271156307046264, 7.068434393913692 0.24064158145378767 M9.053697549664548 -0.1362022664397955 C8.829244320973217 7.620235753016812, 6.700591092690288 13.892125203747833, 7.850660228097013 22.685747434976157 M7.293506093249789 -0.33799486327916384 C8.187708613011768 8.185909373451002, 8.085736837718418 16.1312718545049, 7.028453804479113 21.959472975353357 M7.567299970095729 22.300051978282248 C6.014901480351943 23.106526232925738, 4.436482704510095 22.537880971412648, -0.027420003396695702 22.972474247093235 M7.721184140027463 22.88823072183104 C4.777337294472156 22.871869115737546, 2.149304087776706 22.426389436520605, -0.07833650004238724 22.8506843230891 M-1.7331748995929956 24.550920834424552 C-0.7931232721156043 14.771556238886827, 1.4024765818768579 5.549757332436016, 1.6967032756656408 1.563819656148553 M0.9281278299167752 22.294977000574228 C0.3397859148321689 17.234187979457644, 0.12094516676049835 11.756417261258541, -0.17385950218886137 -0.37203015852719545" stroke="#000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(66.9310344827586 13.835594199804149) rotate(0 3.6857654763949768 11.318249543958633)"&gt;&lt;path d="M0 0 C0 0, 0 0, 0 0 M0 0 C0 0, 0 0, 0 0 M0.34322934899014734 6.404043390651338 C0.6624300982644543 4.481709919376768, 2.102082363092603 2.929648813544574, 4.934326984318441 1.0372144747373873 M-0.40120653991120553 6.139250233446353 C1.488549329046423 4.5581762812239965, 2.909793415498857 2.6493006605083895, 4.956180079238957 0.7079593829659039 M1.4327498647477137 11.248899830363273 C2.649045165702563 7.630088356332252, 6.7936722034483 6.388321735390163, 10.096647083078576 1.7101541454215734 M0.30706777685318465 11.648835073548883 C3.8360702587056545 8.791321417211156, 6.8719991901069255 4.273260110664813, 9.73160963670264 1.3875801326018957 M0.27039420860351515 17.159160381090516 C4.032425946573632 15.149862703097563, 6.696312015421077 9.839038391031536, 8.317191855706351 8.807011948040904 M0.21332125407175628 18.790572178550683 C3.1513909416209462 15.529562547607389, 5.456526753549495 11.987816070841976, 8.50728783850007 7.930197383630308 M-0.3209813630582181 22.28888302271853 C4.952756580839749 21.226999079212543, 7.876987736267955 15.747448815615574, 9.832110296822183 14.635123356593468 M1.0704283054078578 23.824989548685426 C3.922561173065596 19.568054720140402, 7.659986147368928 15.979765264129192, 9.062317568835832 13.870350727460973 M5.8215113547764386 23.894220058731875 C6.8687038848770605 22.574245062889702, 7.64673834736125 21.87058670165667, 8.94044955886346 20.259779638263197 M6.019476823754948 23.88537312035463 C7.085885682792281 22.58684324144952, 8.049396438575107 21.310415294368173, 9.164982628509446 19.69558451965152 M0.11254258591050537 22.73433086528881 C0.11254258591050537 22.73433086528881, 0.11254258591050537 22.73433086528881, 0.11254258591050537 22.73433086528881 M0.11254258591050537 22.73433086528881 C0.11254258591050537 22.73433086528881, 0.11254258591050537 22.73433086528881, 0.11254258591050537 22.73433086528881 M5.154914029936921 22.60556192271389 C3.6969444563240272 19.791904554876552, 2.0943971435021576 18.196808337256012, 0.2550059190003878 16.98184214077915 M5.529157211508285 22.098179554044975 C4.051250120862574 21.065030629808405, 2.1310395191277633 19.401826510498633, -0.17878061067473822 16.87226819276835 M8.664201152777373 19.153788725701162 C6.820648636185988 17.290046791397568, 5.978489345500897 17.816305474268862, -0.623087389970201 12.40959661078245 M8.99208290268302 19.533738421131858 C7.689198470699609 18.231427825152608, 5.523383263083713 16.67110611556701, 0.303927809728307 12.360301257451777 M9.307349779801259 15.651035585059317 C5.7507843663399605 11.091575325869439, 2.066915540322647 9.813402668997822, 0.4000587232476538 7.695587443626791 M9.63923595372569 14.928537091278002 C6.928233149053461 12.087873349512567, 3.3331132466270876 9.526868840173425, -0.3868663421300731 6.735756595359489 M9.58058611154077 8.441964676693136 C6.280102485794384 5.954771907290144, 3.852731651767772 4.828067304363285, -0.32648926592360117 2.3674739472724733 M8.545789345535313 8.967272827304955 C5.491572577894129 6.783750429819175, 2.691651516175046 3.555525097142441, 0.044499105485874546 1.547150254078978 M8.823332010748404 5.29913711765926 C7.156662293843425 1.3238206331626325, 3.083964013598174 0.051264217982659455, 2.546328189216408 -2.651453008929086 M9.154374024345147 4.028175446057279 C7.335729850575792 1.9776862374470778, 5.192528646920037 0.7706968467230366, 2.281088480625155 -2.0374264172816696" stroke="#ced4da" stroke-width="0.5" fill="none"&gt;&lt;/path&gt;&lt;path d="M-0.3318240854747156 -0.617975727987058 C2.1380129566140633 0.01073319755071734, 5.759681939247164 0.18444185471795393, 6.7537409977222485 -0.5476046566960793 M-0.07251587977784135 0.3260167237690098 C1.2946083662070693 0.23138485495592137, 3.0656635811973376 -0.21407443714576596, 7.1819619345208725 0.3324439266189426 M6.051644527279905 -0.23764579556882381 C8.182432136010792 9.772904270303998, 9.204192420911458 15.889481939326235, 8.07328524526315 21.017951061608848 M7.1287744715075405 -0.09810798335820436 C7.391380596729638 5.6355844980710845, 7.582915443750741 10.950525262886604, 6.420023299680224 22.410575828174707 M6.69882546880465 22.06392867305334 C5.093871544329115 21.944800785638876, 1.688852744714333 22.043337227842024, -0.24321155493599284 22.471726185885817 M7.161794870496661 22.526681270104667 C4.8829520859719295 22.73687869683377, 2.40678627943932 22.71131594549412, -0.22882631997998196 22.463186994674015 M-0.4307693038135767 20.97143470275646 C1.7581314163380628 17.38230765908363, -0.0291559977358814 8.425697476372715, 0.323884567245841 -1.7680544760078192 M-0.16971688997000456 22.23250334582722 C0.9092588839477871 16.98188137279433, 0.16385639881556965 12.85688024174142, 0.07439181674271822 0.9673859877511859" stroke="transparent" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(78.01698592322589 13.502516930389078) rotate(0 3.68576547639492 10.973421957751771)"&gt;&lt;path d="M1.2554447744041681 1.5747052635997534 L7.969310176485237 -0.5990929994732141 L8.55335338126963 21.697289063479822 L1.737737962976098 20.296054734733026" stroke="none" stroke-width="0" fill="#f938c5"&gt;&lt;/path&gt;&lt;path d="M0.12701584375485553 -0.45765263995995686 C1.5688841382110517 0.06155833299447492, 3.567021902373667 0.6689852920610172, 7.212759489961164 -0.6137036826800424 M-0.11040909015224959 0.059688127814591485 C2.5037540019137823 -0.31984458593317544, 5.35350954333268 0.25603505880549593, 7.308977287408584 -0.1489033558806039 M7.018858515583929 0.14878363348543644 C9.119101528854813 9.545621116704847, 6.045116727085556 17.392846554636172, 5.835295223557409 22.3119581368529 M8.120037205443737 -0.8546781437471509 C7.194632533556559 6.855634245385087, 7.120044562584498 13.474038179622305, 7.540651954636928 21.586724076304655 M8.101307632638928 21.924839718791883 C4.012935709200087 21.45805445436708, 2.202371883681761 22.21554042374826, 0.5571828551717339 21.952611561260603 M7.048484799775023 21.922569268940446 C5.705870589631688 21.870750695170127, 3.618940025213984 21.76696117659838, -0.12877116950375875 21.708620191957667 M-1.9885263238102198 21.79266727238528 C1.8363539481168487 13.451780908127846, -1.3647937870020173 6.533084256394826, 1.8565778341144323 -1.131369462236762 M0.3958599613979459 22.50304055574916 C0.3685855455714068 16.286728846068964, 0.22510223463352536 9.987724990087498, 0.24846056569367647 -0.5600334005430341" stroke="#000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(89.33298427080138 11.681647179506228) rotate(0 3.6857654763949768 11.318249543958633)"&gt;&lt;path d="M1.5747052635997534 0.5977792236953974 L6.772437953316739 1.1818224284797907 L7.121976100766233 24.374237050893363 L-1.6507891807705164 21.391954114320335" stroke="none" stroke-width="0" fill="#daaeff"&gt;&lt;/path&gt;&lt;path d="M-0.4576526399599639 -0.3466241864864994 C2.3944513850948166 -0.27669633139691435, 5.391478976432508 -0.1714730472041638, 6.757827270109901 0.6181264234795958 M0.05968812781459243 -0.3258317074027615 C2.4594553907375336 -0.2835800572699514, 5.818697592270858 -0.04047184735080653, 7.222627596909347 0.2659403527415318 M7.52031458627539 1.9347719755023718 C8.744157341332441 3.7208969470214512, 8.423791137547498 8.183983817344242, 7.736645174139312 23.06507784280425 M6.516852809042803 -0.16867681872099638 C7.1048020336275135 6.462885384995293, 6.668855241263393 13.58142135122379, 7.011411113591066 23.29174111842608 M7.349526756078293 21.972376821110913 C5.2264661482068195 22.929263640360926, 4.3710208294268975 23.26665961107392, 0.005767645757062234 22.402657411607386 M7.347256306226857 22.948456334286195 C5.56824302446088 22.7745341487926, 3.7363564622913086 22.44311868781054, -0.2382237235458787 22.927426157132626 M-0.1541766431182623 24.37575912627822 C-0.33120365819393405 15.385111444120803, 1.0664744190269826 12.914077027954594, -1.131369462236762 -0.33531163074076176 M0.5561966402456164 22.804520384068248 C0.35605415069101565 17.938754926728762, 0.2731518682145905 13.440395360807733, -0.5600334005430341 0.6277223872020841" stroke="#000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(100.60370373194644 13.230920530446838) rotate(0 3.6857654763949768 11.318249543958633)"&gt;&lt;path d="M0 0 C0 0, 0 0, 0 0 M0 0 C0 0, 0 0, 0 0 M-0.1957550689251969 6.687206199069612 C0.6824230711572109 5.422426855034799, 1.8719805533338358 2.4575357315330746, 5.664970709713644 1.1404830265638641 M0.0829556595860173 6.732782163892977 C2.1188497557710617 3.886008973449713, 3.9567827235703823 1.9899367041124933, 4.8638106777840315 0.344581752255302 M0.8968301805760368 11.06982517936039 C2.4902429966498163 7.364390327024551, 6.120375579574652 5.476856066649113, 9.829335682903482 0.72155629342992 M0.6178433518949555 12.096617914873688 C2.650085016481502 7.868621539301731, 6.723822717589006 5.43961653470112, 9.687760940351588 1.567158571003118 M-1.3490122233098607 17.191285490524642 C3.0063263846061985 14.342482621456398, 5.157437449657136 10.735572155399584, 9.485223053492682 7.2409602920331375 M0.09074712437109544 18.414757397957764 C3.1211115806126584 15.874891965141751, 5.025176208363602 12.086848239319288, 9.653047811430024 7.909850685465368 M-0.09294758498681466 23.907384307437045 C3.301273797188709 20.689821873596124, 3.894663824061067 19.24669730333069, 9.511903699374788 12.34245797337375 M0.8334506820047857 23.378130258979674 C4.448132782443175 20.21254896068377, 6.833544402347294 15.725559554509545, 9.748636921522237 13.99182425292551 M5.635629904004153 23.210709829740367 C7.430430604364139 22.774134488627883, 7.857945747593954 20.841972334551706, 9.028249271879016 19.439726612183065 M5.972980946548622 23.48288449381492 C7.136270914802845 22.58801297492195, 8.128127685902022 20.864720486174257, 9.424810994848652 19.87065607949792 M0.11254258591050537 22.73433086528881 C0.11254258591050537 22.73433086528881, 0.11254258591050537 22.73433086528881, 0.11254258591050537 22.73433086528881 M0.11254258591050537 22.73433086528881 C0.11254258591050537 22.73433086528881, 0.11254258591050537 22.73433086528881, 0.11254258591050537 22.73433086528881 M6.093573960166108 22.25905401580013 C4.500946948496016 21.23148074017254, 3.2211458580362833 19.21610642600191, -0.40150092933487824 16.339766149148595 M6.053391279901993 22.600894986688864 C4.293200214913044 20.862442315061188, 1.7430336152780552 18.902267893840204, -0.43500880729567937 17.280989103317513 M9.28056087539166 21.053959040013144 C5.539345979974032 18.392080809253983, 2.1033110312715473 15.648656791086532, 0.8130343718286579 11.443160403487985 M8.864138618005514 19.935172753264197 C6.230648858602779 18.134327929612382, 4.2155070040628075 15.729422825920341, 0.10401449276320385 11.544578034957652 M9.91930146177281 13.970676033748777 C5.532784824774005 11.746392058067856, 3.599893450570887 10.763628705674154, -0.13580577596365728 5.780346014417082 M8.95505416963231 15.28963711973326 C7.075222996895217 12.8049450929475, 3.3516328339312222 10.302960568257399, -0.07489195823944428 6.851241379903003 M9.914210391039823 8.819560375526601 C7.351242665736041 7.821970066860937, 4.44496284258774 5.585628139576228, -0.11096915817747699 1.7385949604369935 M8.553151991399801 9.169551351942177 C6.432862980987809 6.823314034426687, 4.842541479864386 4.870687586403165, 0.5994117620315136 1.3359943686209854 M8.835978060009497 3.736580999886204 C8.347659821152826 3.4720363895725868, 7.108715680154763 1.2058362280737867, 1.1894054137094339 -2.670749506035638 M9.083029723240601 4.167416828536947 C7.1289149785426495 1.612810758842918, 3.7447169526498083 -0.736433303491734, 1.6729480865335487 -2.58238961320166" stroke="#ced4da" stroke-width="0.5" fill="none"&gt;&lt;/path&gt;&lt;path d="M-0.3466241864864994 0.004850752823676752 C1.380388737107566 0.6580429758862247, 3.129995505429311 -0.5679384186840555, 7.989657376269549 0.5719032709576586 M-0.3258317074027615 -0.32390716605711073 C1.8651716092972808 0.24003485311865097, 4.279113303814108 -0.16084098144352466, 7.637471305531485 -0.31438709722256647 M9.306302928292325 1.3780294749885798 C6.1924017800073425 7.794582133117894, 5.795378877041797 13.838951945177872, 7.800109707676938 22.56157623756176 M7.202854134068957 -0.19871648121625185 C7.297701774151899 5.476162073332922, 7.710087565692045 11.861530854627702, 8.02677298329877 21.824553778986093 M6.707408685983602 22.104365571495244 C4.946417998556207 22.859651121205268, 2.6389141305767048 22.64006631400316, -0.23384167630987995 22.153136845678098 M7.683488199158883 22.561308203186012 C5.371739314452907 22.452663755301643, 2.8378879236555026 22.39342043505877, 0.2909270692153608 22.473439022458976 M1.7392600383609533 22.457810809495506 C-1.236821325181765 15.539385362619496, 2.08164420569058 4.872922577486651, -0.33531163074076176 -1.8277274873107672 M0.16802129615098238 23.192485591987726 C-0.06507085133817472 14.31931004934167, 0.15885172914240084 4.49591916703497, 0.6277223872020841 0.7873526317998767" stroke="transparent" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(113.44827586206873 13.145939027390327) rotate(0 3.6857654763949768 11.318249543958633)"&gt;&lt;path d="M0 0 C0 0, 0 0, 0 0 M0 0 C0 0, 0 0, 0 0 M-0.7288802548916885 6.601354410548799 C0.1307399341667509 5.446333101498398, 2.5339963734626307 3.685793048281024, 4.3960674780928555 1.112589459478598 M-0.11087012175935002 6.568490841661684 C1.3743267155048193 4.559271304353472, 2.97511008037005 2.919962652303248, 5.190431292312688 0.24826566500936087 M0.9048826429154384 13.370234764597893 C2.149936808269546 9.450704941687233, 4.549994473829592 7.6092131995589956, 9.185732375894737 2.146323674382755 M0.057424880200962924 12.336570774156183 C2.3031140783307813 9.736671879327615, 4.686117832241708 7.3781105394577535, 8.790116459288699 1.3704131842834144 M-0.5600273345178227 19.480852370989197 C2.67674904016384 17.518290666756872, 4.090111348584494 12.7886346118244, 8.17081267098345 7.337386188916149 M-0.12720756787343424 18.523808062263452 C3.6419393606286765 14.506146586167686, 6.871419965449026 10.661317912947352, 9.714020063918161 8.301450540292295 M1.7455731356142672 22.741088442258935 C2.971506825795825 19.563762877401658, 5.266644716714533 17.663629320639323, 8.588778626061075 13.572044047129967 M1.4007615144456387 23.539323011400576 C2.15814810817845 20.85661566186247, 4.12908009898633 19.421967979493218, 10.01403673368416 13.606157121084326 M5.946273174615916 23.272025530152163 C6.949001223554608 23.20588902768108, 7.8682662822926 21.735310773533232, 9.626406752238093 20.09946102521083 M6.094293408342045 23.856727724122024 C6.974621503419702 22.552231523011216, 8.451177561837145 21.183436602212385, 9.426913928672715 19.88137470081149 M0.11254258591050537 22.73433086528881 C0.11254258591050537 22.73433086528881, 0.11254258591050537 22.73433086528881, 0.11254258591050537 22.73433086528881 M0.11254258591050537 22.73433086528881 C0.11254258591050537 22.73433086528881, 0.11254258591050537 22.73433086528881, 0.11254258591050537 22.73433086528881 M5.005240949492585 21.73629579417385 C3.895579427011634 21.610770598672232, 2.0355012261312817 19.057325867840902, -1.039723313787698 16.70704622708286 M5.7123769304413905 22.297232173979058 C4.289855913299945 21.38813618960238, 2.855769281299908 20.034441500902005, -0.2059429718197578 16.917933361530554 M10.311413944220245 19.76244446453173 C5.842689971237482 18.167559276547543, 5.208536332339324 16.029726454267834, 0.23993961045704526 12.385934234378642 M8.79379541565299 20.398451994587667 C7.424459454318667 18.600533953461728, 4.965966874032738 15.801559337332986, -0.2500695161689098 12.355382872899776 M10.245945599274526 15.00983737911144 C4.557634894486528 10.988886122897988, 1.5329844528330594 9.278923550139261, -0.185635923587673 5.429290055550009 M9.22640738908183 14.980893167518543 C6.194439806060802 11.718346821497732, 2.1031870027601163 8.305777312483393, -0.5480599275258861 6.260195954070732 M10.107376539702392 9.142722466304953 C7.574923475598925 8.634021473684244, 3.8486310924248137 4.950124268563841, -0.385837753777605 0.7566167466499149 M8.97538882139591 8.96357209412014 C5.8070628410215 6.730006974433245, 2.428176578440914 3.743225226414562, 0.42249030497928275 1.5603410003863512 M10.425120054724234 5.209807874952961 C7.659912855922 2.5665881224313796, 5.607550261954416 -0.5914069596914173, 2.04687416776255 -1.7965226080806973 M9.351751944495904 4.657759043240507 C6.586356361689889 1.8169320144231618, 4.823675997639515 -0.1229408694693293, 2.2223247381782554 -2.622038071563286" stroke="#ced4da" stroke-width="0.5" fill="none"&gt;&lt;/path&gt;&lt;path d="M-0.3892816547942453 0.4919462791317373 C2.6911196114404436 -0.07522285232662045, 4.709373966138839 0.3850977131170454, 7.742139628844251 0.42008558635712 M-0.28918891042532224 0.030270157602468983 C2.6887961815893053 -0.23988074860606007, 5.412565545969817 -0.36117647214401133, 7.077201512538528 0.3123480307626846 M7.957674228512815 1.7220624182373285 C9.24828715885782 10.476796354456308, 6.83862044180536 18.4537688313247, 8.903783940636686 22.020475437524375 M6.773377723918429 0.5454891426488757 C6.7699815981347795 6.169376358632517, 6.655013149257445 13.23046888925239, 7.7857416643958 23.0017590137521 M7.004117950186225 23.146922815943345 C5.615467452018722 21.952637206089783, 3.092277505064931 22.946080835388454, 0.5845872242818122 23.025915115330523 M7.33514844475029 22.315126459891548 C4.734551637783485 22.62072071746868, 2.603057080248773 22.379054544451666, -0.17140091309227312 22.749364622245366 M-1.6635945830494165 23.262380709531364 C-0.01805405426005205 18.705986644907053, 0.8940909047128789 11.085820378744659, -1.5918935928493738 1.7043795678764582 M0.3914818214252591 21.855501106599924 C0.6088518867555469 16.015586707932236, -0.117858581775108 8.063500182494533, 0.3322915779426694 0.04675887059420347" stroke="transparent" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(126.41533860447021 12.425543420240587) rotate(0 3.340937890188002 10.628594371544882)"&gt;&lt;path d="M-0.9847520384937525 1.0343498680740595 L7.784357583301471 1.8991090152412653 L6.573150491493152 20.97876907896015 L0.40439279936254025 21.702006772269932" stroke="none" stroke-width="0" fill="#f071ff"&gt;&lt;/path&gt;&lt;path d="M0.4626702333435717 0.053225398801870716 C1.6711114256439332 -0.5556020172239233, 4.848922866846407 0.5631991571878138, 7.034859378861481 0.08804253366663639 M-0.2913061063479764 -0.29766729990549823 C2.240153419433423 -0.13054552680806225, 4.2538057425122515 -0.14876949402900078, 6.784182002122344 -0.1507593583465518 M7.307757401990102 -0.06824306584894657 C8.412244828896377 6.749750082697048, 6.5615594112442475 10.219802786296754, 8.386255348252462 21.363309707601992 M5.900877799058662 0.4464438306167722 C6.852842973721117 4.5717695636387825, 5.81181653047809 10.036363981048, 6.728634650970207 20.354631195885567 M6.590489195536083 21.309742139516256 C4.611526009299493 21.835193217584685, 1.4329768112072243 20.89854602741276, 0.3058446212155237 21.74655027489418 M6.498969256864388 20.93886092470857 C4.781681152824069 21.5031845001482, 2.1353103559249176 21.04526379894313, -0.1078528730421916 20.94057461440266 M0.5089259836822748 19.623347070177523 C0.6513333412343466 15.455195141635713, -0.6522062329119241 4.813004135335113, 1.8812052104622126 -0.3432857785373926 M0.4267670614644885 21.73001269541445 C-0.33461315425288407 14.312780228341484, -0.16370836885821546 6.349623574561974, -0.27960427571088076 -0.7779928399249911" stroke="#000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(136.2048401385332 12.42554342024016) rotate(0 3.68576547639492 10.973421957751771)"&gt;&lt;path d="M1.0343498680740595 1.1024818029254675 L9.270639968031105 -0.1087252888828516 L7.093111288660225 22.351236714866083 L0.4448180291801691 21.757930471446436" stroke="none" stroke-width="0" fill="#f938c5"&gt;&lt;/path&gt;&lt;path d="M0.05871894175208603 -0.6687671280275931 C1.4108231851597772 0.2736341284684559, 4.7056107199581065 0.3383736546106434, 7.468660603716863 0.25169392399795165 M-0.3283903782424522 0.00821273786043547 C1.8202522037335573 -0.1691789095514301, 3.771695492401156 0.1771400599332419, 7.205211297501096 -0.08171975953095667 M7.303287886940893 1.8389684055000544 C8.032879592649511 5.451109284494298, 5.597084106676152 12.630032161169206, 7.477651917302069 20.51192188530795 M7.817974783406612 0.29014770220965147 C7.177182219181729 4.60270315285127, 7.789740435038281 10.753922658363495, 6.468973405585643 22.591482821736555 M7.4295085329133554 22.349464378689202 C6.216601712667501 21.51942315334391, 3.440176794530306 22.27417373426137, 0.5398699103918245 22.171064136624466 M7.0203476259128825 22.286204368691784 C4.959875384975806 21.762016749730883, 1.7906832737158787 21.78750228047774, -0.34929276245483043 22.138655774584677 M-1.63384167291224 22.775450768765133 C1.8096446536970872 13.057609370517483, -1.3491389609383804 6.999445477762997, -0.3432857785373926 1.25218422152102 M0.47282395232468843 21.63184658053897 C0.6810213537988495 15.01617596249763, 0.42591713984583135 8.094088832920646, -0.7779928399249911 -0.49237601924687624" stroke="#000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(147.5208384861088 10.604673669357297) rotate(0 3.6857654763949768 11.318249543958633)"&gt;&lt;path d="M1.1024818029254675 1.8991090152412653 L7.262805663907102 -0.27841966412961483 L7.775923752152494 23.081317117097434 L-0.18891344405710697 23.595641007306632" stroke="none" stroke-width="0" fill="#daaeff"&gt;&lt;/path&gt;&lt;path d="M-0.6687671280276033 -0.6496885626783846 C2.1687227845043253 0.5787153301066035, 4.077508593879771 0.09125775675181594, 7.623224876787909 -0.2399110830902425 M0.008212737860435582 -0.15129599901315827 C1.9052408346010832 -0.10572283744378166, 4.39025407384342 -0.10064157964025477, 7.289811193258996 -0.18829948096712432 M9.210499358290008 0.8439018931239843 C7.4306527844219366 3.3161582704326955, 9.146749364450471 9.922224691076368, 5.93660892259436 21.515179516407308 M7.661678654999605 -0.28026663791388273 C6.443861610839026 8.009419093680757, 7.241499222466604 14.451697484271978, 8.016169859022966 22.00114186167216 M7.774151415975618 23.22467358769744 C4.24785838524712 22.197150167475083, 2.29627527567968 23.18216675632145, 0.22422022112092765 23.08904793000878 M7.710891405978202 22.888851648031693 C4.825270363423301 22.313430999324822, 2.4489655066381966 22.216969660087035, 0.19181185908113718 22.94387208930801 M0.82860685326159 24.317912878128347 C-0.5325342337118868 15.690783295325186, 1.8236036976350065 11.356521373044487, 1.25218422152102 0.18455704115331173 M-0.3149973349645734 23.40014301299548 C-0.24069767154584507 15.5019001353388, -0.37620939768682105 6.572228920584957, -0.49237601924687624 0.5171749340370297" stroke="#000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(158.79155794725386 12.15394702029792) rotate(0 3.6857654763949768 11.318249543958633)"&gt;&lt;path d="M0 0 C0 0, 0 0, 0 0 M0 0 C0 0, 0 0, 0 0 M-0.013008253243739332 5.658745958705537 C1.1582632845551535 3.7839145756650368, 4.009100879140464 2.2009422307441544, 4.89662575903768 0.36136894708846146 M0.11947054977995658 6.143046715532534 C1.6984690175865513 4.778406975115222, 2.74078888659107 2.4411618675461995, 5.280430777328557 0.613406866028648 M0.2578982087876307 11.184614981196402 C1.5533114393351806 9.499575112159896, 3.5175849329780453 6.99746953015816, 10.015513383661462 2.5053495818992344 M-0.19185702688064105 12.24963403804931 C3.8776687282704296 8.679757956655553, 5.960312610568196 5.12691821954405, 9.02275212422858 1.5265954972488074 M0.8167141224676511 18.167722516786927 C3.0388044264607164 16.58581435285724, 4.6490191043578335 13.98912383333935, 9.673196522988455 8.908306322506848 M0.3918111537070711 19.07651747483822 C3.961021379795617 14.374404927294238, 6.82956459985793 10.20803774523026, 8.93231475595765 7.404529978501828 M-0.1981439864636747 23.771888617925743 C1.5973173768516802 22.545256650451222, 4.214424483280778 18.062823025858112, 10.006333934403054 13.584333022845605 M1.2188467676843167 23.832582095625277 C2.7093921191758845 20.48591076875468, 4.7305312437861655 18.930299741316357, 10.000637383041003 12.906102225206489 M5.509095397325572 23.263537947945437 C6.1870068669120535 22.5770900206534, 7.015562042794304 21.659339845951955, 9.335419618258296 19.859516686412306 M5.764661662526768 23.70272547488115 C6.920128527209017 22.470023918098068, 7.773192300422957 21.437998780339203, 9.419969331905289 20.15874414041339 M0.11254258591050537 22.73433086528881 C0.11254258591050537 22.73433086528881, 0.11254258591050537 22.73433086528881, 0.11254258591050537 22.73433086528881 M0.11254258591050537 22.73433086528881 C0.11254258591050537 22.73433086528881, 0.11254258591050537 22.73433086528881, 0.11254258591050537 22.73433086528881 M5.412531408171784 22.854026697799338 C4.065781921735993 20.09063008743878, 2.83427516438454 18.463323104440946, -0.3614184508116238 16.761086945161473 M5.795738842215073 22.260075401365533 C3.778604451883542 20.64980105885142, 1.8392325528012485 18.900505555495435, -0.4842113090695136 17.427025978565467 M7.993788230871855 19.251635424462147 C6.109472205258845 17.11799980910361, 4.671540317263059 15.357419985632335, 0.5363550348039692 11.854882884738748 M8.930067226184606 19.975018166233788 C7.383945809262959 18.229407751154696, 4.935615171334181 16.740971247865225, 0.10796590520255012 12.68320789464355 M10.663626029686817 13.861893810881766 C5.840092333575545 12.256077127518786, 2.2473504831014885 9.497972046610403, 0.5296206481820414 6.1792659514316615 M9.963995282091016 14.686076613925861 C6.428192114589025 11.288537221705399, 2.2626819040911696 8.72444633242534, -0.09819443136728623 6.517561025844261 M8.955967867369628 8.694700291469747 C6.718783011369739 6.049542821471571, 3.7815261016505857 5.340587563411138, 1.1512087740849433 1.929563199744397 M9.145104845794712 8.92578584877407 C6.933457071454239 7.18972351906894, 5.321359347715506 6.143196194647029, -0.06229959103206972 1.467270636387938 M10.174803196432608 4.164741279875447 C7.044376627742389 2.0726424514207276, 4.0998592576806745 -1.0833575550212828, 1.7593487523420177 -2.9358503725918057 M8.988257190658318 4.4024965491532875 C7.175815235363304 2.9619738370298974, 5.4804752947498745 0.9763005661916502, 2.196342954311373 -1.7887271098399582" stroke="#ced4da" stroke-width="0.5" fill="none"&gt;&lt;/path&gt;&lt;path d="M-0.6496885626783846 0.32467650127106795 C3.306470238144878 0.45629274021601685, 5.540895678653157 0.31857063680071585, 7.131619869699711 -0.07276501607932684 M-0.15129599901315827 -0.23345343515657627 C1.3973131166932775 0.11122298675601391, 2.9711084042823552 -0.08336230710304507, 7.183231471822829 -0.30658097404606177 M8.215432845913938 -0.011717012152075768 C5.771913439451348 9.203717732092223, 7.469927996812951 13.563754298235937, 6.250211381279996 23.419462730767783 M7.091264314876071 -0.7508787410333753 C7.804881784378251 5.069802587793861, 6.570032659231385 10.690726340632327, 6.736173726544848 23.307831010440943 M7.959705452570127 22.219160210859346 C4.815743839515644 23.014172838095387, 3.690119993542236 22.90098135535676, 0.45254884209151336 22.826830189612238 M7.623883512904381 22.492028886082572 C5.310809883115598 22.585386507467074, 3.4064588974205035 22.896183037636053, 0.30737300139074375 22.421147444890742 M1.6814137902110815 22.161566367032584 C-1.2924475467309904 17.15073944373224, -0.15836350084399692 9.154753600143806, 0.18455704115331173 0.7529335115104914 M0.7636439250782132 22.49240653834736 C0.4015089214697626 14.599893866071803, -0.8850236295804236 6.630121176401225, 0.5171749340370297 0.5512409014627337" stroke="transparent" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(169.87750938772115 11.82086975088285) rotate(0 3.68576547639492 10.973421957751771)"&gt;&lt;path d="M-0.1087252888828516 -0.27841966412961483 L7.77592375215238 0.4448180291801691 L7.182617508732733 22.90598583489291 L-1.2604091558605433 20.736481371220272" stroke="none" stroke-width="0" fill="#f938c5"&gt;&lt;/path&gt;&lt;path d="M0.32467650127106307 0.5845872242818031 C2.659594255002289 0.16209850719486066, 4.792050379176028 -0.17494222682240132, 7.298765936710514 -0.6427452560514255 M-0.2334534351565727 -0.17140091309227048 C1.667544063680147 -0.13606045548611248, 3.027637299173139 -0.15804028116449023, 7.064949978743782 0.11534264366276564 M7.359813940637764 -1.5918935928493738 C9.277708283471558 6.615288143438178, 6.138406685399507 11.897014786341941, 8.154494595640358 20.38484795286886 M6.620652211756465 0.3322915779426694 C7.481968086712415 6.661298934205447, 8.079848122351224 14.492355736574181, 8.042862875313517 21.81007605593286 M6.954192075731926 21.501697237049015 C5.439505790688921 22.545531275873383, 3.056877441989829 22.458210207573064, 0.19033110169496958 21.54327427612387 M7.227060750955148 21.694012492286497 C5.7434536496189645 21.648021665071596, 4.5451575477049 22.304687428917166, -0.21535164302652104 22.040633006538364 M-0.47493272088468075 20.469474091079157 C1.7902786840829692 16.812388371217594, 1.1612576951417766 12.54906142515974, 0.7529335115104914 0.853534122928977 M-0.1440925495699048 22.455383626628738 C-0.21047230226820998 13.547098552809345, -0.4232440458042055 7.220513804002161, 0.5512409014627337 0.9495545076206326" stroke="#000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(181.19350773529663 10) rotate(0 3.6857654763949768 11.318249543958633)"&gt;&lt;path d="M-0.27841966412961483 0.40439279936254025 L7.816348981970123 -0.18891344405710697 L8.33067287217932 21.376089932056722 L-1.2103625442832708 21.226123990150747" stroke="none" stroke-width="0" fill="#daaeff"&gt;&lt;/path&gt;&lt;path d="M0.5845872242818122 0.38941602741325654 C1.6398218087364076 0.21922627387084875, 2.8454732325285232 -0.10523266620643364, 6.728785696738518 -0.6567807564849145 M-0.17140091309227312 0.11286553432810104 C2.061565106269111 -0.03502907249964269, 4.26747004214859 -0.25989028701474653, 7.486873596452721 -0.012576396805469792 M5.77963735994058 1.7043795678764582 C7.5731668394721465 7.43741802787012, 6.34572630344996 18.527643758981945, 5.809534990155271 23.52938674915081 M7.703822530732623 0.04675887059420347 C6.405530850650645 8.0923814522512, 6.672731171609736 15.56681701456005, 7.2347630932192715 22.715149751524088 M6.926384274335419 22.97391079177682 C5.297321114159136 22.8189854213324, 2.595920296836166 22.785096301906446, -0.403569639379679 21.934132434163338 M7.118699529572905 22.517514416829425 C4.132057998121635 22.799416906631464, 1.8985435697537305 22.392253404523803, 0.09378909103482408 22.335401226321498 M-1.477369824424386 24.517704298379478 C-0.5335923871800159 16.159895669158207, -0.0057495674893115745 7.931857318867602, 0.853534122928977 0.9456479046493769 M0.508539711125195 22.356894812206384 C-0.9508550900602565 16.066217030857402, 0.3443126839017644 11.031927913754025, 0.9495545076206326 -0.0543626444414258" stroke="#000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(192.4642271964417 11.54927335094061) rotate(0 3.6857654763949768 11.318249543958633)"&gt;&lt;path d="M0 0 C0 0, 0 0, 0 0 M0 0 C0 0, 0 0, 0 0 M-0.567355200615699 6.777249366839521 C1.9037561241922645 4.341789029725279, 3.27065785341736 2.5307012737222676, 5.705021855565256 0.04934442973111208 M-0.016461285543615534 6.177423811380856 C1.6438679629538473 4.255618784818018, 4.179019914204079 1.395638612041059, 4.959164331065959 -0.039220675841230734 M0.19373443267304769 10.734978154159664 C3.287109871059163 9.535394042660874, 6.902778428962417 6.031215373286549, 8.220309403975559 0.6140461636950462 M0.23047158923928013 11.674262762765913 C2.4900306541884705 8.98688005447295, 4.811316347948462 6.0217594996020845, 9.640729035035772 2.0900570712295563 M0.39219238231421905 19.27880357047605 C3.4840081940074823 15.09209514747888, 7.6786140733975285 11.542246420949079, 8.441332623086947 9.205994176752151 M-0.21844932600392508 18.592901345343016 C2.8434398304496673 14.27241369406649, 6.183048459181963 9.803682102738042, 8.849655428905116 7.356153825651141 M2.126710600741079 23.0276675786231 C1.4832697620247632 21.85898232796814, 4.604364718868101 18.39458922054696, 8.640875116809301 14.121748954778234 M0.4519616590904655 23.02150516182783 C4.09866092766867 20.525034623427164, 6.131695308343058 16.094319623308156, 9.200961694055266 13.960919194783711 M5.669233822742816 23.305471263751528 C6.3523683349978235 23.033964067742314, 7.330258679350298 22.00510726840174, 9.004792740302204 20.16949086787811 M5.801038281772625 23.638513091644413 C6.652850983537542 22.997535484168356, 7.740627058888571 21.80889189373379, 9.030031701516403 19.979387638510005 M0.11254258591050537 22.73433086528881 C0.11254258591050537 22.73433086528881, 0.11254258591050537 22.73433086528881, 0.11254258591050537 22.73433086528881 M0.11254258591050537 22.73433086528881 C0.11254258591050537 22.73433086528881, 0.11254258591050537 22.73433086528881, 0.11254258591050537 22.73433086528881 M5.120616082351338 23.009334019049778 C3.489772369413227 20.916992910472807, 2.286960567652815 19.63576735791621, 0.20025094207215377 17.104350837990893 M5.631942789133322 22.0607970921693 C3.5884767136729607 20.488886810414424, 2.3581107497105074 18.995806581254037, -0.11535949018013408 17.473175587803375 M8.41702828974098 20.672517095920632 C7.075918833355789 18.789453019549875, 5.220787351741552 15.951173780353281, -0.05698293691315004 12.437650127677028 M9.220752543477415 20.045969207440503 C7.183178509479742 17.970574775799328, 4.897212841951202 16.357144606780516, 0.43841625860146927 12.252216813867694 M10.020160729968737 15.348411119347901 C6.794016666434738 12.523701647400676, 1.635596342067878 9.12657105269637, -1.222762144164138 5.309496609001727 M8.946990627441371 14.754392020907716 C5.8377932512083 12.09453520757226, 2.287382619752878 8.717850704892236, 0.2083445430333235 6.860394317676619 M8.128458099807476 9.57071029888099 C7.2354192272202935 8.219067008508931, 4.381863765005492 4.037941828394149, 0.20718048521389387 1.5400311052955922 M8.7708608923432 9.795908790729994 C6.827550300438322 6.812951417491322, 3.5785689194234074 4.76737689850458, 0.07747152214010378 1.9586745703749593 M9.374862127917426 4.20942298354726 C5.6910310011291605 1.7835134050983985, 3.130940389529428 -0.7145401340147588, 2.7960190143360224 -1.8256336600126746 M9.86127079415934 4.6421511469795895 C8.145470372041883 3.2753665534918586, 6.76298471061984 1.2153854405988382, 1.8595674903056048 -2.277150132281524" stroke="#ced4da" stroke-width="0.5" fill="none"&gt;&lt;/path&gt;&lt;path d="M0.38941602741325654 0.09712965092702464 C1.813464647156557 -0.29836980551842374, 3.050776430237876 -0.7012039784796167, 6.714750196305039 0.016425475720871163 M0.11286553432810104 -0.16631965528874623 C1.8271475282797118 -0.15401929834866968, 3.511153601575278 0.14962282628122203, 7.358954555984484 0.33890031305866086 M9.075910520666412 0.10612096451222897 C5.935282761919892 6.624779376564859, 8.153168434966004 13.93020193351495, 8.264418614023498 23.216794492336568 M7.418289823384157 -0.9025575472041965 C7.846197199657581 5.521166950778951, 7.872890215948246 12.17628049447743, 7.450181616396776 23.182682052845713 M7.708942656649507 23.176368998309098 C4.796117388293722 23.044533262163323, 1.9625944832506073 22.18841478069213, -0.7023666537539254 23.315219994293763 M7.252546281702114 22.287206325462428 C4.847178405805248 22.8933058046646, 1.7238504976317437 22.67972189430868, -0.3010978615957674 22.789201614580058 M1.8812052104622126 22.293213309379873 C1.3059382920366607 14.127040316269163, 0.8066875820261321 6.102001585243414, 0.9456479046493769 -0.6299946699291468 M-0.27960427571088076 21.858506247992274 C-0.459758510577728 14.634101794226462, 0.583858410131882 6.5469091400822315, -0.0543626444414258 -0.13920983206480742" stroke="transparent" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(203.2398338093226 11.464291847884084) rotate(0 3.6857654763949768 11.318249543958633)"&gt;&lt;path d="M0 0 C0 0, 0 0, 0 0 M0 0 C0 0, 0 0, 0 0 M0.11800852570218356 6.769682457347505 C0.7760396345729886 5.16557043832652, 2.0534504663091457 4.628771424501106, 4.676252462300009 1.121962122976523 M-0.4818170297564814 6.1902949121344015 C1.2155433274190546 5.1921395720845025, 1.982141480171994 3.484191375847998, 4.587687356727666 0.3957510404137188 M-1.1727349189017309 12.292908288023948 C3.094476424575242 10.543743620507419, 5.0479318069874 5.892090901598098, 8.457093619619561 0.8249195153136938 M-0.23345031029548174 12.100250320273965 C2.775807684083158 8.594849168975648, 5.519288640451704 5.381025146998058, 9.933104527154072 1.947437113640943 M0.7118496562773127 18.655075042974822 C2.973785881127019 16.474777970176962, 5.884645679154769 11.858683303956061, 10.389800791539328 8.912953541687907 M0.02594743114428122 18.06876564640614 C3.2396313530760206 13.849690266304034, 6.318178245250688 9.978408112229207, 8.539960440438318 7.269365544784101 M0.6230100417135866 22.097982762508494 C4.413742535625726 20.037420852671428, 5.897568003235523 15.879505029059082, 10.057083337641352 14.108776738656381 M0.6168476249183179 23.631961638095255 C4.739213082321844 19.072555639569597, 6.921407658909388 15.725589120844472, 9.89625357764683 12.995432043097608 M5.527721759410914 23.376662169508776 C6.797060165278413 22.836431687864476, 7.304377471493449 21.893227264640167, 9.445584409603892 19.624159729691954 M5.860763587303799 23.638736386941886 C7.137065160859093 22.79087824327103, 7.866639370106391 21.23044469402011, 9.255481180235787 20.15117490186988 M0.11254258591050537 22.73433086528881 C0.11254258591050537 22.73433086528881, 0.11254258591050537 22.73433086528881, 0.11254258591050537 22.73433086528881 M0.11254258591050537 22.73433086528881 C0.11254258591050537 22.73433086528881, 0.11254258591050537 22.73433086528881, 0.11254258591050537 22.73433086528881 M6.425222381453644 21.65999798648037 C3.471523986771593 20.049683507136013, 1.340307040142407 18.950212010869386, -0.2689652094633572 17.1715000673386 M5.476685454573166 22.03400053888537 C3.881085573609633 21.186803394626164, 2.324118740630918 19.547706783260953, 0.09985954034912653 16.710998265743505 M9.83013044640988 19.72672166046221 C7.451487454164194 17.78747447928472, 4.057052209790512 16.015838179376882, 0.41145686337910337 11.69525003504545 M9.203582557929753 20.43359683625866 C6.400720315019209 18.209647360735307, 4.166740926374808 15.631770449938866, 0.22602354956976822 12.643759144624477 M9.853147252993473 16.04086404646793 C8.23740949689022 12.929123322239496, 6.073788556186298 10.486246971238785, -1.4682244233721424 7.3998169594177305 M9.259128154553288 14.944963240288661 C7.175561229373053 12.803727113177535, 4.488597732035945 11.112298161294314, 0.08267328530275009 6.3831124051672665 M9.323918664450622 10.04762618811434 C7.9552941302264735 7.7949856742995225, 3.8166796656294966 5.569243657293415, 0.10943285607804665 1.56422508882063 M9.549117156299626 9.804026231445427 C6.919052330380272 7.480973717678037, 5.213006303791429 5.712458720949668, 0.5280763211574138 1.9029279349528487 M9.309754132273214 3.381365241800953 C7.177030857031283 1.778123143326903, 4.999109750726371 1.5559887840228923, 2.288191976390632 -3.144582321921182 M9.742482295705544 4.602542283797224 C7.43125151233723 2.8082631929275466, 4.391726119010941 0.31859004178466765, 1.8366755041217826 -2.296215211560768" stroke="#ced4da" stroke-width="0.5" fill="none"&gt;&lt;/path&gt;&lt;path d="M0.09712965092702464 0.25169392399795554 C2.2962247040178116 -0.1117066188206525, 4.4295263181646725 -0.6957223592262402, 7.387956428510825 -0.30259199802631653 M-0.16631965528874623 -0.08171975953095795 C1.556252934556709 -0.32915408091168197, 3.6044474747104345 -0.03514950367109, 7.710431265848614 0.1555212231570372 M7.4776519173021825 -1.4349220301955938 C6.070036891967015 7.56065176248102, 5.62936050084229 14.448263900494547, 7.9518263572092565 22.0759658120895 M6.468973405585757 0.6446389062330127 C6.811392906555066 5.762555055245011, 7.3099822732294895 10.26109692904949, 7.917713917718402 23.43439914904034 M7.911400863181786 22.860719309038192 C6.029959386102401 22.860571359998193, 3.379720495852142 21.967873604549293, 0.6787209063764972 23.14120420814612 M7.022238190335118 22.828310946998403 C5.7563850720130985 22.397350510673174, 3.620282279489581 22.31160429210393, 0.15270252666279238 22.946363932891988 M-0.3432857785373926 23.888683309438285 C0.3517694445688072 17.136023578208984, 1.0207465263444726 11.075328950129096, -0.6299946699291468 1.5272878501564264 M-0.7779928399249911 22.14412306867039 C0.40205934696647794 16.722965349696302, 0.8344389205500808 9.652587164108445, -0.13920983206480742 0.20219639968127012" stroke="transparent" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(215.51724137931035 10.387318337735152) rotate(0 3.6857654763949768 11.318249543958633)"&gt;&lt;path d="M0 0 C0 0, 0 0, 0 0 M0 0 C0 0, 0 0, 0 0 M0.061922028693382924 6.618385345538251 C2.484142354807133 4.557275987195287, 3.7104214876341066 2.1590077460211443, 4.4293491337024635 -0.3486126091482824 M-0.5984903420926878 6.252835131113522 C1.6925950878817 4.252451465079096, 3.7084753522121745 1.8807468315635174, 5.310030171977824 0.6057859808054977 M-1.0003993735555898 12.302017691589475 C3.6532821995576628 10.257945722745205, 6.012188566867276 4.982218835132635, 9.50887060507114 1.551784248488137 M-0.25092944893058067 12.564749265413313 C3.5650478446472214 8.675524597507952, 5.848050082562786 5.864321862154854, 9.357543481485052 2.040201624414065 M-0.24774983455894994 18.22805288573789 C1.9342538418395172 14.281024764131303, 5.686799659177284 10.270719679036546, 10.292630335412996 8.397865867502274 M0.4332222981797299 18.83387231454315 C2.09100354024471 16.685532834026738, 4.216603599378313 13.517580899569154, 8.981598201770412 7.765742806325885 M1.818886775858572 23.06381667511205 C2.5178705892813795 19.609037181245764, 5.488617726436562 18.51932973859877, 8.186311332209996 13.273302561765233 M1.4184405194687306 22.82257448107603 C3.259880875765517 20.69272143683649, 6.476597996694614 16.67688192974677, 9.520906969838805 14.039326541986012 M5.592759752193805 23.825611792430422 C7.307942866480669 22.4480183445804, 8.212307620526063 20.989759635203818, 9.10149448152077 20.059241229320676 M6.0050420330505485 23.603591028293735 C6.997184120310262 22.826767431007063, 7.422964857937555 21.837638021920853, 9.053178748082413 19.810472426355616 M0.11254258591050537 22.73433086528881 C0.11254258591050537 22.73433086528881, 0.11254258591050537 22.73433086528881, 0.11254258591050537 22.73433086528881 M0.11254258591050537 22.73433086528881 C0.11254258591050537 22.73433086528881, 0.11254258591050537 22.73433086528881, 0.11254258591050537 22.73433086528881 M5.929861573379171 22.098431423529757 C4.256652082614477 21.011830189662597, 1.574092713930904 19.304944365128065, 0.4650933569281107 17.312558349892747 M5.5869803829748745 22.08048941776134 C4.357565699133809 20.956594082376572, 3.1516408444050854 20.236826496550613, -0.00457543637764396 17.13023668780948 M10.121445232337415 20.182753168460913 C7.062717129171834 17.198955974588607, 3.4216690828510186 14.707999372327537, -0.3524713898245271 12.114539642600125 M9.251906651524902 20.28591876760591 C5.579113179342314 17.57830213233177, 3.6714818775702374 14.709627436250415, -0.2918783027846392 12.603444145029192 M10.473748190486619 15.868717778092714 C7.538174725392173 13.909576462265296, 5.004208184830361 11.334146893947477, 0.13938320819944816 6.027800551682085 M10.138568288478815 14.899667959418611 C6.538828202620438 12.459409775321086, 2.6625674854352317 8.949545898549559, -0.8127368688052079 6.439563459922865 M9.432038860763287 9.117622396811898 C7.605199956174584 6.99650064732268, 3.572165479234237 5.115933526413237, -0.09012071183810799 2.5139482081093125 M8.661270981835997 9.381338697575087 C5.689245326046724 6.923768970958996, 2.1051725573445337 4.108438762764942, -0.1915162765506921 1.648643966966146 M8.546483688967841 3.55379644813161 C6.324612367895975 2.6493646259805126, 5.35246386323143 0.4417941854987504, 2.4521757926715937 -1.48178662365098 M9.031431922203469 4.489962965102453 C6.431945841591261 1.3809518556959768, 4.00655561273762 -1.008657331300717, 1.977829662050905 -2.6438578148910823" stroke="#ced4da" stroke-width="0.5" fill="none"&gt;&lt;/path&gt;&lt;path d="M0.6829217684119491 -0.16973786348026032 C0.8791864254884287 0.37920312296655917, 3.1910578855279725 -0.3957563616960291, 6.912090617661279 0.34360756759894406 M-0.2667292824749523 -0.17323220549182303 C2.72063262621087 0.3456653111718335, 4.749708376006726 -0.24394909796231215, 7.229682582308265 0.25241052498899064 M6.411005449407867 -1.5265737567096949 C5.937954922552307 5.561845427003775, 6.9635645946218565 9.745134224364707, 6.6320605398352654 23.660195352169332 M6.938223285527101 -0.19440644513815641 C7.192152617843996 6.40195309500238, 8.36637523618544 10.782680196483248, 6.733016384692064 22.89978243469691 M7.544573473186498 22.153125447951556 C3.9002544672952384 22.429798976618194, 1.1287212224425012 23.16429728723289, 0.4340183562157014 23.24560414699097 M7.5258059476205545 22.92420604742868 C5.076450937305314 22.22806534097259, 3.1177178310772287 22.601830003019696, -0.35215642511539313 22.44407947640819 M-1.5875023510307074 24.310512484642324 C-1.8448505228911976 16.617934432282617, -0.9709475463782885 10.968346110757873, 1.2057764027267694 0.032736023887991905 M-0.3091855840757489 21.939170167441127 C-0.7192435305716547 15.113115754060097, 0.8845006484387365 8.430376655925649, 0.4167503220960498 0.9547978984192014" stroke="transparent" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(254.65193321074992 11.704604740973252) rotate(0 4.375420648808699 9.594111612924156)"&gt;&lt;path d="M0.37308462522923946 1.1679444406181574 L10.596934376692616 0.7590200398117304 L7.407183049654805 19.46093582347178 L0.10979988239705563 19.33834641412043" stroke="none" stroke-width="0" fill="#f41d92"&gt;&lt;/path&gt;&lt;path d="M-0.4970000984258989 0.6147504312965698 C2.5666620831383358 0.7487948395769995, 4.128921428782176 -0.6417130746241304, 8.272290934589876 0.3662837488167566 M0.25285741212791574 0.006698909873221304 C2.8902842951674472 -0.14102344353873647, 6.429024648194269 0.36868418834627936, 8.794904652839426 -0.34729952833660566 M7.9589205791888045 -0.026688731649193276 C9.744641337618305 7.393941411420018, 8.780272605784631 14.342967080889228, 8.067429173524694 18.594951025302336 M9.556193932658989 -0.16320947611656744 C9.004016722076814 5.33446042948282, 7.69212199793707 8.256938093495318, 8.407539454827885 18.614547775853975 M8.569000174871066 18.629208647847438 C5.519885994902364 19.46477885996846, 2.1876181193779862 19.218828070649888, 0.552670143263983 19.31392880516876 M9.173045233581423 19.560118124066634 C6.287719263156525 19.300496098985526, 3.6924457566728823 18.812921311523343, 0.0585408116636153 19.365088039564757 M-0.41609017383506286 20.33151485154626 C-1.4926972045136928 11.202753158025413, 1.2303449870514662 5.720055084151639, 1.1867734767147577 -1.6167685946440988 M0.08323916493677708 19.212010396381977 C0.7880354359612445 14.716351143686706, -0.10193379675387909 8.544537708445223, 0.3371522058606785 -0.7768110637272067" stroke="#000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(242.91751404262448 11.828652624152255) rotate(0 3.68576547639492 9.938939199131074)"&gt;&lt;path d="M1.1679444406181574 1.8460930790752172 L8.13055099260157 -1.343658247962594 L7.644243550413307 19.987678280659203 L0.15012318827211857 18.474299481697564" stroke="none" stroke-width="0" fill="#fc54ee"&gt;&lt;/path&gt;&lt;path d="M0.5178532758647338 0.43401835621569473 C2.261361330927914 0.06381452758670983, 2.742281981323233 -0.3612548262620555, 7.680080942451037 0.5754139190228198 M0.005643025601875018 -0.3521564251153877 C1.8956947127412969 -0.2006670287477837, 4.413176392372298 -0.005482314156328258, 7.078972884833602 0.3085010392335906 M7.3438829858008035 1.1984138354448346 C7.38434852279161 4.473797221653989, 6.9559779386091 7.840851079598597, 6.7569356085145005 18.49173644978054 M7.202455469275614 0.41420561125309296 C8.220040110953592 3.499216224336667, 6.670334685829903 8.532473172713628, 6.777236697358505 20.453978844271916 M6.90062842757593 19.79492982039429 C5.518596404261556 19.611319662354457, 3.2536666107433927 20.32811857372328, 0.10589182655516516 19.96267537816339 M7.684807660727328 20.099250695704555 C4.68994052143129 20.28043248139598, 1.5067263148703065 19.998323192745673, 0.14898732640999424 19.979513993323827 M1.184383339816526 18.492225600191233 C-1.8903670984794712 13.742831615289422, -1.0586227186870658 5.9666931334308515, -1.674877821890868 -0.7203190822808749 M0.024642119165571907 20.680484423502843 C0.5436687365131456 13.932691305689548, -0.6069250239741115 8.575474430044567, -0.8047308852647257 0.18540327031420079" stroke="#000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(230.1756529961466 11.828652624152113) rotate(0 4.720248235015617 9.594111612924184)"&gt;&lt;path d="M1.8460930790752172 0.7590200398117304 L8.09683822206864 0.27271259762346745 L9.55029635242829 19.338346414120487 L-1.4035789165645838 19.030341736824802" stroke="none" stroke-width="0" fill="#e08fff"&gt;&lt;/path&gt;&lt;path d="M0.5558341660672721 0.7800624045249582 C2.5828066630286814 -0.7863515569485454, 4.59313141865066 0.3288307274932898, 10.177411651712172 -0.15796415723026758 M-0.45099606981130963 -0.2464259697001864 C1.686833317646383 0.3020064300352456, 3.8260649471210657 -0.37611562892857087, 9.835584409095432 -0.4010621892081429 M10.597331808830319 0.03140730669447467 C9.553771005153783 5.1840214667691535, 8.677892274095669 10.53897299011605, 8.102446171237634 17.721628365530922 M9.840331380522395 0.9160437605219266 C8.689507183390967 4.206838784782955, 9.258265154250038 8.999792608424523, 9.996609337665673 18.98816384591761 M9.334266738165393 19.45051885073929 C5.926037012331108 19.124877580776985, 3.6513167109184614 19.263528621112027, 0.10859692437756219 19.906719693816658 M9.724001267874732 19.304636972035688 C6.985717077934134 18.858877137926424, 3.7452056170936077 19.459267581889787, 0.13016162891457073 19.569811755306763 M-1.3375781192640064 17.48833856758677 C1.427073089684667 11.285789962731954, 1.2283824940982677 3.560582750119568, -0.6953278950459845 -0.13275511075310864 M0.7747599248859359 19.777490216395275 C-0.13185714081654573 12.265742616062306, 0.3205043728733239 5.609265686521168, 0.17897077677576567 0.5602694660492452" stroke="#000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(59.352226529371364 104.59731144489695) rotate(0 4.375420648808699 9.594111612924156)"&gt;&lt;path d="M-0.369867792353034 -1.8882046733051538 L7.223056184536063 0.2848064508289099 L8.643029259925925 18.993351820708746 L1.3624025080353022 19.71968859791898" stroke="none" stroke-width="0" fill="#f41d92"&gt;&lt;/path&gt;&lt;path d="M0.257932677524917 -0.028721444771974358 C2.015914188556263 0.8165451309521037, 4.702480022283992 0.0017425785039629493, 9.059581898991084 0.5348390666981043 M-0.37868762312197013 0.002130872364445424 C2.709257334890388 0.061260596374975884, 5.994891460276675 -0.10025076017536844, 8.335863278300463 0.14684392818677044 M8.170504025536683 1.3454491302861096 C10.477795174083466 7.091496019151587, 10.09845522104235 11.66586883463413, 7.196738823848957 19.38523572593781 M8.857216882438347 0.08731965499660588 C8.122543677918351 5.120618531274985, 9.037327240162123 12.762092335700187, 9.457736861131576 19.326607092623682 M8.690144125729535 19.056705469109595 C5.029676284001398 19.989292894577815, 3.3228012814648547 18.538004371077598, -0.4158204075901513 19.801096916444155 M8.921443670668673 18.918627596176115 C6.6693829215346465 18.91386832889657, 4.890851915223433 19.393695824080837, -0.21989156389074815 19.573036665448676 M-1.5059652661234475 17.403730984649304 C0.35625614458301097 13.536888628688969, -0.6449348133499031 7.107155746099529, -0.5938359338392722 -1.6865075833037624 M0.2089025903478724 19.71385672471351 C0.20810779298953008 15.111003901532284, 0.7633006527000255 10.329384148624959, -0.8558645648956917 0.7250154940643584" stroke="#000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(71.23383216618095 103.78360838930405) rotate(0 3.340937890188002 10.628594371544882)"&gt;&lt;path d="M0.2848064508289099 -0.10781203769147396 L6.487004375236438 1.3624025080353022 L7.213341152446674 19.622163966407506 L-0.28099522925913334 21.33647717546436" stroke="none" stroke-width="0" fill="#f071ff"&gt;&lt;/path&gt;&lt;path d="M0.5895505386496986 0.08510253820951419 C2.168904403863962 0.2570942471899623, 4.8114113501468125 0.3913692868669061, 7.261195876415207 -0.6261601212656447 M-0.01583928731945461 -0.17164892237791307 C1.2870896349911798 -0.25568930357922115, 2.964633695267779 0.15418294899674684, 6.7033197308718275 0.09837600365191917 M7.516608679818319 1.3917889799922705 C5.8767811729200465 4.114394308167641, 7.036275274921237 11.788436632802972, 6.861973310040639 22.745043184717623 M5.6991797640569075 0.2805962609127164 C7.363333273234086 7.349649115798138, 6.8454610887190865 13.75427802811764, 5.900110329652534 20.65711687050524 M7.333644228278222 21.828013108689525 C4.267268308812738 20.89131964411412, 3.2334150103557633 20.583967770045163, -0.6137339157226358 21.808177926859653 M6.417287822165951 21.06922459632513 C4.518573195474599 21.047359219281873, 2.4455150340090306 21.153199165262752, 0.3316425848200567 21.538405967216207 M-0.26194762252271175 20.783501949270693 C-1.1375339718747903 15.929418975252847, -1.5601664873052408 10.32645561488246, 0.6143809054046869 0.7806847896426916 M0.2371787829324603 22.114219675881294 C-0.37630518069159147 13.52510443479062, 0.6392260313998545 7.392231755040276, -0.9441023366525769 -0.763892556540668" stroke="#000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(81.02333370024394 103.78360838930362) rotate(0 3.68576547639492 10.973421957751771)"&gt;&lt;path d="M-0.10781203769147396 -0.19487140513956547 L8.733933460825142 0.5314653720706701 L5.736506176107582 21.66584868624441 L0.0792884323745966 21.278763069655817" stroke="none" stroke-width="0" fill="#f938c5"&gt;&lt;/path&gt;&lt;path d="M0.09388621027868205 -0.03597341293252998 C2.447341735313624 0.3854975949589043, 4.782740603270547 0.5740733801730895, 6.680743086174846 -0.6379975228306478 M-0.18936528991423496 -0.15352799063399614 C1.8654802390263747 0.12719316318872426, 4.532705851378314 0.02715206180755199, 7.480060620171816 -0.1114739523781908 M8.76331993278211 -0.9541467931121588 C5.9477951404094345 8.328279915527409, 7.887592839860881 16.42596274294139, 8.8593853944177 22.168595733936947 M7.652127213702556 0.662110517732799 C7.960654726148933 6.9280380728749, 7.752436002613395 12.785834028590731, 6.771459080205318 21.87748238805316 M8.00127170492763 21.44334724400279 C4.920580888931103 22.535020193183502, 2.4808349653286825 21.341668728076986, 0.6078583254631535 22.23426786270946 M7.164166498341353 22.05914402593206 C5.429043244450673 22.212623575335762, 3.788551707032669 22.254336898321682, 0.3102424439247914 21.657570921271827 M-0.47368679381906986 20.611619475182216 C-0.6814919521964778 15.645975950654844, -1.5756442954696406 13.475202802714385, 0.7806847896426916 0.4354808423668146 M0.8570309327915311 21.687000695500593 C-0.9531618220826636 14.916074923002547, -0.2460229558965217 5.429827609671982, -0.763892556540668 0.14240322541445494" stroke="#000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(92.33933204781965 101.96273863842077) rotate(0 3.6857654763949768 11.318249543958633)"&gt;&lt;path d="M-0.19487140513956547 1.3624025080353022 L7.902996324860624 -1.6350247766822577 L7.09053572353082 22.71578752029186 L-0.6680808458477259 21.70598917234188" stroke="none" stroke-width="0" fill="#daaeff"&gt;&lt;/path&gt;&lt;path d="M-0.035973412932530535 0.26007681113229186 C2.4264682950930347 0.3988221338800127, 4.590974809829297 -0.7001764876428732, 6.733533429959296 0.0035900071905613506 M-0.1535279906339985 -0.3495690540011441 C1.6602642378383106 0.14048032330613575, 3.0967890320397413 0.14640272536482518, 7.260057000411761 0.258440288935869 M6.417384159677795 -1.619850317016244 C7.671355414263387 9.133668958105988, 7.6461056350389915 19.11529759296943, 7.5932827712233575 22.818526686760244 M8.033641470522753 0.7368014799430966 C7.2933213112366815 5.796349339336301, 6.367317527295413 9.935461293272962, 7.302169425339571 22.486207530493495 M6.868034281289193 22.286220423146244 C6.209872132979358 22.01011997384532, 3.33858950044144 23.30733148859421, 0.2874239472059208 22.1822953236674 M7.483831063218471 22.45126690116519 C5.946775009099274 22.526557994444584, 4.2395733636118385 22.905268640952364, -0.28927299423172015 22.29372530328431 M-1.3352244403213263 22.01754033717757 C-1.7282090701601893 13.68745588956595, 0.6440287909962742 8.545319668883039, 0.4354808423668146 1.0957418885082006 M-0.25984322000294924 21.744426325554606 C0.701999019053257 15.030528310256788, -0.9977913096878166 7.0305327684086745, 0.14240322541445494 -0.05390601884573698" stroke="#000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(104.35117536322502 103.17893471994631) rotate(0 3.68576547639492 10.973421957751771)"&gt;&lt;path d="M0.5314653720706701 -1.6350247766822577 L7.0905357235307065 0.0792884323745966 L6.703450106942114 21.016333999928158 L-0.6441347394138575 20.91883766919009" stroke="none" stroke-width="0" fill="#f938c5"&gt;&lt;/path&gt;&lt;path d="M0.45053756557095337 0.40821075490787484 C2.9444588469007726 -0.7167955477282232, 3.97269346940062 -0.02241767392266744, 6.696637562535469 -0.03494820933020126 M0.12369834225570414 0.10260726532328662 C1.856385946883014 0.17844347818220369, 3.5539834596387734 0.32835409973609275, 7.700886769685429 0.15383148513877587 M7.576878261678871 0.3199473824352026 C7.446925928749922 9.599176391639336, 7.48858021751869 16.40467149886593, 6.025754702203926 19.98145188286535 M7.5157692860076395 0.5285827564075589 C6.735077863941321 6.59980153621907, 7.447481787214407 14.249455260183414, 6.647762549967524 22.92227127950273 M7.887803300410619 21.257490726217824 C3.8532517973293583 22.519674374489597, 1.9765548240462727 21.457612284776584, -0.46254431736362145 21.363050265998996 M7.6956899775725125 21.87837571292056 C5.940672923618456 22.22003992207792, 3.4887972046781663 21.567023693520177, 0.10501861495221537 21.898570040317647 M-1.7578569557517767 20.433732821490686 C0.7609007173241755 16.663272036934295, 0.5820006542863032 10.91407246758786, 0.5567001793533564 0.4743575658649206 M0.7556879920884967 21.761910019327026 C-0.8870749507335112 12.717985773038397, 0.1994306113335207 4.963006724808849, -0.09743570256978273 0.6812012540176511" stroke="#000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(115.66717371080051 101.35806496906346) rotate(0 3.6857654763949768 11.318249543958633)"&gt;&lt;path d="M-1.6350247766822577 -0.28099522925913334 L7.45081938516455 -0.6680808458477259 L6.441021037214568 21.992364348503408 L-1.0280062463134527 23.746983291240987" stroke="none" stroke-width="0" fill="#daaeff"&gt;&lt;/path&gt;&lt;path d="M0.40821075490788106 0.6391133507851483 C1.4846980062894506 -0.6830512793877537, 4.354561752999472 -0.7199471468114775, 7.3365827434597515 -0.37873057982847574 M0.10260726532328818 0.023657240874532248 C2.012932971950967 -0.13621362082933372, 4.067246898073847 0.30461614844445306, 7.525362437928732 0.25649038864412443 M7.691478335225156 0.18009752966463566 C8.676046693299309 5.932774394207455, 7.370219850514428 10.076268900962189, 5.406138920151761 23.197691609742698 M7.900113709197512 -0.7817654507234693 C6.938245140492512 8.220374612000692, 7.388025455653263 15.855703876079833, 8.346958316789141 23.490786692718622 M6.682177763504226 21.959419970431515 C6.271426663916891 22.975550269885264, 3.5014019598676382 22.225582005315715, -0.583793649504554 22.221770179020286 M7.303062750206969 23.002371435685866 C5.245713890020728 22.412057898642082, 2.156638154768955 22.80634950782602, -0.04827387518589771 22.54920416635314 M-1.5131110940128565 23.250879993321952 C0.850740085625579 17.792270545183825, 1.1657971844910882 12.633780497070116, 0.4743575658649206 1.7140618655830622 M-0.184933896176517 21.69239675126469 C-0.5619585138248864 16.576146668216364, 0.14802802387004466 10.133552095116771, 0.6812012540176511 0.26573268603533506" stroke="#000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(127.92194183757294 102.7916163822116) rotate(0 3.340937890188002 10.628594371544882)"&gt;&lt;path d="M-0.6680808458477259 -0.9305099155753851 L6.037741040962146 -1.0280062463134527 L7.7923599836997255 21.440167382468907 L0.5619014706462622 20.803078308810917" stroke="none" stroke-width="0" fill="#f071ff"&gt;&lt;/path&gt;&lt;path d="M-0.5783086611239967 0.0032541384213965463 C1.7628300121611689 0.03093743748765526, 4.4058675348468395 -0.21571286386868482, 6.048146637689529 0.22425109858033998 M-0.10104483143446913 0.23426150122321343 C2.4891205120052375 0.11976433786628224, 4.6136502200766385 -0.179062459425873, 6.411284815348373 0.034302630245775234 M6.903627598809408 0.18202759884297848 C5.355241365691317 4.751823685102868, 7.262210138102663 14.758652470947718, 8.155478740262197 21.545665409525363 M6.612514252925621 -0.15029155742377043 C5.791926020165134 8.47620232885593, 7.369982027788806 14.31852129600721, 6.206698144937263 21.95754847965899 M6.942409328133602 20.845478726387178 C4.882902457442042 20.783111745046646, 3.546541353481942 21.51587505540474, -0.33580499636546657 21.844852377837416 M6.4196661760953315 20.946483699001206 C4.896631583921409 21.26967465104851, 2.8418726130802345 21.09038709168632, -0.10339513714048365 20.96354419763968 M0.4354808423668146 22.352930631597964 C0.4914001250216673 16.96261409176247, 1.648761811251373 11.199637868699439, -1.784145524725318 1.5113759841769934 M0.14240322541445494 21.203282724244026 C0.10325391306037626 15.954350847782404, 0.46642230166549403 8.471598056098614, -0.14049761462956667 0.0396442161872983" stroke="#000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(137.7114433716357 102.79161638221117) rotate(0 3.68576547639492 10.973421957751771)"&gt;&lt;path d="M-0.9305099155753851 -0.6441347394138575 L6.343524706476387 1.1104842033237219 L7.5545095921689835 22.508745386149805 L-0.4541104342788458 21.582070841338556" stroke="none" stroke-width="0" fill="#f938c5"&gt;&lt;/path&gt;&lt;path d="M0.0035900071905613506 -0.6748933902543711 C2.8156244272918896 -0.31493082754540513, 5.394089291976189 -0.6353383557192126, 7.6189276373012484 0.20521453064657325 M0.258440288935865 0.32935581689558924 C2.4126052527247914 0.2787851791197587, 4.3417094696987455 -0.27622462829365374, 7.40937405387853 0.05896255082212959 M7.553558551632818 -1.3457762505859137 C5.569407466148625 7.342874315132373, 8.859020534252416 15.036966546500073, 7.660007619225439 23.00400942831866 M7.221239395366069 -0.7237684028223157 C8.215261479532064 5.722612346896295, 6.556805481826604 9.261471848751135, 8.071890689359066 21.01168784860156 M6.917327188539982 21.48429959813992 C5.253357134934082 21.560857401327162, 4.5273708369444465 21.60512193671999, 0.648318049565346 21.809907510337574 M7.02875716815689 22.051862530455757 C4.380053096229801 21.801694395093072, 1.239056782772804 21.774922476917755, -0.32395242399752855 21.667995283890406 M1.0957418885082006 22.5035440948569 C0.3886897297374413 16.402666514656424, -0.6053542761333778 5.64621985791786, 1.5113759841769934 -0.369867792353034 M-0.05390601884573698 21.84940821293376 C0.8488515610837811 17.700530089165444, -0.6498620812749988 12.782253276127106, 0.0396442161872983 -0.33404042292386293" stroke="#000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(149.02744171921142 100.97074663132831) rotate(0 3.6857654763949768 11.318249543958633)"&gt;&lt;path d="M-0.6441347394138575 -1.0280062463134527 L8.482015156113675 0.18297863937914371 L7.933432423436216 22.18238865363842 L-0.3647730741649866 22.675436069848594" stroke="none" stroke-width="0" fill="#daaeff"&gt;&lt;/path&gt;&lt;path d="M-0.6748933902543814 -0.03494820933020182 C1.1419408393935055 -0.30741498198705314, 2.3422047304416744 0.2470376837923559, 7.57674548343653 0.047314481749064496 M0.32935581689559423 0.1538314851387782 C2.6850090092450656 -0.22752612326046256, 4.558517822432586 -0.01384495669848343, 7.430493503612084 0.03318986286109671 M6.02575470220404 -1.9653920326381922 C7.957821898793945 9.799161547383914, 8.870232336854706 17.23835769027223, 8.428696465605071 21.072968186470327 M6.647762549967638 0.975427363999188 C8.21011753606264 4.749568453024475, 6.880652295822542 11.565553910030719, 6.436374885887972 21.71799366235232 M6.908986635426325 22.052705438412712 C4.137484432592825 22.815678932349314, 1.3624313566855046 23.239396761057634, -0.13693640516596906 23.368243783454467 M7.47654956774217 22.588225212731366 C4.452291857539247 22.321878123721916, 1.5935757656773504 22.24399190699314, -0.278848631613141 22.74972228444211 M0.5567001793533564 23.110856653782186 C1.664773057127853 14.442264395101303, -1.8334343331805272 8.79877856647413, -0.369867792353034 -1.8882046733051538 M-0.09743570256978273 23.317700341934916 C0.2334975812132272 16.75722228914092, -0.17273271945167454 12.552614483234128, -0.33404042292386293 -0.46525495778769255" stroke="#000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(161.72894020703052 101.49728754044006) rotate(0 3.68576547639492 10.973421957751771)"&gt;&lt;path d="M1.1104842033237219 0.18297863937914371 L7.933432423436102 -0.4541104342788458 L7.006757878624853 21.98578089743487 L-0.47294519282877445 20.40944087773196" stroke="none" stroke-width="0" fill="#f938c5"&gt;&lt;/path&gt;&lt;path d="M-0.3787305798284699 -0.3070559812679923 C1.515911185025236 0.2508915054444284, 4.6353131167016 0.05080930268208385, 7.588590287553791 -0.2229479047563816 M0.25649038864412055 -0.17583806547328604 C2.42982044493939 0.007076804060934597, 5.515642278239493 0.0024235658333410376, 7.64572507953248 0.040866259835481755 M7.932723474615273 1.324221035465598 C8.69745620609203 7.0722764060926995, 8.281018759020954 12.004068577867262, 6.171387207620796 21.808120860602777 M8.225818557591197 -0.6830286337062716 C6.737508995673953 6.954570375960547, 6.2775305642107115 11.589915853217697, 8.196133507342097 22.336754707504134 M6.956802043892867 22.171444136360574 C5.243811057698601 22.536782600118435, 3.7200835044496854 22.620209246090276, 0.6204848878495828 21.36829792704011 M7.2842360312257135 21.70077770823479 C5.731552396479343 21.613236716468833, 4.040858888034598 22.050412336991204, 0.14387105228058317 22.02709792822489 M1.7140618655830622 21.427157475497644 C-1.8452345844453226 15.671762915091044, -0.4309568520730387 4.485725273019405, -1.527785113081336 0.2848064508289099 M0.26573268603533506 21.129331527162414 C-0.11911355306860219 15.520741655211136, -0.3126563613628984 8.55009600475644, -0.32206736970692873 -0.5140031231567264" stroke="#000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(173.04493855460623 99.6764177895572) rotate(0 3.6857654763949768 11.318249543958633)"&gt;&lt;path d="M0.18297863937914371 0.5619014706462622 L6.917420518511108 -0.3647730741649866 L7.410467934721282 22.16355389508849 L-1.5374030377715826 22.654462815853414" stroke="none" stroke-width="0" fill="#daaeff"&gt;&lt;/path&gt;&lt;path d="M-0.307055981267997 -0.6991381080022882 C1.7839625800940166 0.24308758862942392, 3.1204462729142737 0.2549323927468028, 7.148583048033569 0.516880577871738 M-0.17583806547328876 -0.2985194187692965 C2.908014020716865 0.007664473093305646, 5.773531702117446 0.22289604901381965, 7.412397212625436 0.033545551978326205 M8.695751988255552 1.4736029598861933 C7.437559644454481 6.3249320957438595, 5.585552076571943 9.33538942068844, 7.232807897889188 22.335915973069724 M6.688502319083682 -0.47517763543874025 C8.292693295714152 4.21743538043813, 6.67382813357998 11.12978544923248, 7.761441744790545 22.020339748481867 M7.596131173646989 22.266034714413113 C6.270934033881952 22.4580897918616, 4.605445711380439 23.21551108487716, -0.5785459884634403 21.950951518651355 M7.125464745521196 22.522432248177818 C4.6260820130510005 22.3401914719913, 2.641761117831958 22.762911155885025, 0.08025401272135041 22.83843146910243 M-0.5196864400058985 20.852353563191947 C1.4576870111417537 14.84559441408027, -1.9418936463403935 6.2666402118680065, 0.2848064508289099 -0.10781203769147396 M-0.8175123883411288 22.4960014732877 C-0.11455589946528824 15.732069257853368, -0.6194550734402791 9.17365290393027, -0.5140031231567264 0.5552421016618609" stroke="#000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(210.6412950645422 100.69136735811665) rotate(0 4.375420648808699 9.594111612924156)"&gt;&lt;path d="M0.038936981931328773 -0.47294519282877445 L7.213438259845816 0.017963727936148643 L7.877952503448569 19.973239902259344 L1.5399870369583368 19.90248424172544" stroke="none" stroke-width="0" fill="#f41d92"&gt;&lt;/path&gt;&lt;path d="M0.05616764326389667 0.25767398968417565 C1.5306319621200837 0.589234442021449, 4.372557940968321 0.3408694697451067, 9.359807521801656 -0.41747935805775416 M0.039400122504704205 0.3254994523160138 C1.9650126110442203 0.02582332189513896, 3.538582613785503 -0.44396988796685516, 8.873613965015 0.2897012031081508 M7.250772299743614 -1.151431304270461 C8.645425623772098 6.881875299502077, 7.389735103255443 16.212187989667417, 10.39006742361777 17.8776126365281 M7.869616940622751 0.7911328944676733 C9.259312653008 4.253535366292679, 8.283222437486616 8.929558517840503, 8.211068017609948 19.48054236565661 M9.619505044948728 19.924808992891272 C5.878572487846078 18.358144793735804, 4.629240731305262 19.05735422153004, -0.20725789787439475 18.604006367150713 M8.885250042604472 19.359014443288988 C6.491679991699526 19.36173483483863, 4.163767371793327 19.225793945031658, 0.37498708400038455 19.074530886822927 M-1.8115646383734638 17.722449136301684 C0.12824338892046394 15.040049315602323, -0.33196489992050693 12.40585172230394, 0.5098938098050272 -1.5686610197385973 M-0.44637079934814383 18.879228196663394 C-0.50074735399388 14.2340516868688, 0.08016885375106861 8.484016480023914, -0.21783930955323527 -0.17498367934641712" stroke="#000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(198.90687589641675 100.81541524129565) rotate(0 3.68576547639492 9.938939199131074)"&gt;&lt;path d="M-0.47294519282877445 -1.5374030377715826 L7.3894946807259885 -0.8728887941688299 L8.156547629200873 21.417865435220484 L0.7142610158771276 17.971375803090815" stroke="none" stroke-width="0" fill="#fc54ee"&gt;&lt;/path&gt;&lt;path d="M0.2170593347639519 -0.2229479047563816 C2.97573654822009 0.6539801856162721, 5.225374910974272 0.5082493291133348, 7.019854821843268 -0.5970388375385838 M0.2741941267426403 0.040866259835481755 C2.3206910399926577 -0.25464875474030724, 4.212092270954689 0.09678322855553992, 7.615569361571591 0.27156774577309495 M6.178715381444581 -0.13787600081764295 C7.177854260067202 4.811380694305147, 9.41554464313404 14.198299961109976, 6.013814940455399 18.933326072779646 M8.191098418104145 0.3875299654778923 C6.5732447458145735 6.731887607011164, 6.398520390912251 14.151886586978382, 7.674356506057661 19.378386107273613 M7.992015840639422 19.299332409798716 C3.826206048121642 20.161090097720297, 1.5554285316134495 19.776463024687615, -0.49213241453750733 19.649744718783257 M7.515402005070423 19.958132410983495 C6.009908896133109 20.00311635265529, 4.331840614092632 20.216404539995672, -0.09577211695621601 19.549081299267293 M-1.518456334825299 20.16094579809303 C-0.07039279375586244 12.218685972674162, 1.3908445042097917 5.475076758497851, -1.625041184451782 -0.2792794498852422 M-0.3201008005541234 19.36701381934033 C0.6182518979913452 13.926641266503403, 0.3456353767026228 7.56787397779233, -0.1812728702802966 0.01934961480065711" stroke="#000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(186.16501484993887 100.81541524129551) rotate(0 4.720248235015617 9.594111612924184)"&gt;&lt;path d="M-1.5374030377715826 0.017963727936148643 L8.567607675862405 0.7850166764110327 L10.980483506989572 19.902484241725496 L-1.9065025951713324 18.401451710464052" stroke="none" stroke-width="0" fill="#e08fff"&gt;&lt;/path&gt;&lt;path d="M-0.28552263041870807 0.6619533041476315 C3.9385530188021294 0.3662165178157768, 6.846880900030825 -0.4781791033277768, 8.675886910042694 0.09692902723486574 M0.05233618147858898 0.04296077260813502 C2.665552149155429 -0.5340873366792497, 6.09879249599879 0.24230217725081157, 9.788285058557065 0.06808407377929138 M9.307404022830859 -0.2883827952807716 C7.901706602526823 8.642395160184485, 10.929715696244234 12.230836130845299, 8.5287150159583 20.532089122217034 M9.814581235784969 -0.5911501473888852 C8.662246148864654 3.202256121959725, 8.724478318624852 8.216456402772998, 8.958333867612039 20.032014802935784 M8.69957014186155 18.31026321331612 C7.052706156565347 19.144041066224396, 3.9033472776704894 18.63742776573469, -0.29216389507265106 18.358471106568643 M9.543275353909488 19.446831911611543 C7.453961678062816 19.33702775900452, 5.609190456978142 19.102421035792542, -0.4210804882047847 19.54492671693619 M0.2732464877333365 19.08478715356549 C-0.005805337270289007 15.0937157186618, 0.6910502736423759 6.81633884697292, -0.2695899592211344 0.07607020698156708 M-0.4931403332957248 19.72092869540222 C0.17705003431885458 14.342267749488169, -0.12856514968360688 8.782234051296903, 0.018678287525978998 -0.22687444833976067" stroke="#000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(128.78551472372624 43.63110606137064) rotate(0 0.6689726410712069 25.499127641102167)"&gt;&lt;path d="M-0.11396733485162258 -0.5172206226736307 C0.49421996988353767 8.064764385073092, 2.668021065369131 41.90783711745692, 2.9708841971306583 50.58666851497415 M-1.6329389149881899 1.8256346050836147 C-1.0846361757256324 10.589759684818441, 1.2893051975033 43.02417936594513, 2.2555832671186407 51.51547590487796" stroke="#000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(128.78551472372624 43.63110606137064) rotate(0 0.6689726410712069 25.499127641102167)"&gt;&lt;path d="M-9.627772883528586 28.812116302754166 C-8.191227618343932 32.4175108136179, -3.892590440391108 39.77484918645406, 2.9008493324926574 51.15371757404107 M-8.310003533975895 28.856388560700598 C-5.331996795898999 36.29735609692032, 0.12924019606037973 46.58343227924906, 2.276089867967971 51.38960550290692" stroke="#000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(128.78551472372624 43.63110606137064) rotate(0 0.6689726410712069 25.499127641102167)"&gt;&lt;path d="M8.019102309500205 27.212187460793825 C5.146916510163693 31.19037296747343, 5.148891973888661 38.93726206391053, 2.9008493324926574 51.15371757404107 M9.336871659052896 27.256459718740256 C5.650157380467125 35.36265801452498, 4.4376883717032625 46.25379626005339, 2.276089867967971 51.38960550290692" stroke="#000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g transform="translate(10 12.76662868256274) rotate(0 3.6857654763949768 11.318249543958633)"&gt;&lt;path d="M0 0 C0 0, 0 0, 0 0 M0 0 C0 0, 0 0, 0 0 M-0.7552016228111336 6.4426035710173775 C1.621243728538996 4.299708212135282, 3.1074143241110512 2.366726119987497, 4.6126878235065405 -0.006717076540250166 M-0.485753330183203 6.581137013857357 C1.6515754652648869 4.646728176401884, 3.1990555128822553 2.313813745920484, 5.211171267139262 0.5875905185786301 M-1.2102604017500174 12.500325718380093 C3.1818136753930677 8.98340670422732, 7.280621028042143 4.961200982196949, 10.171657053073002 2.1164157463480917 M0.314295311840217 11.929176432590515 C3.5107046363204786 9.217293830248776, 5.5524839798492165 5.273127431200929, 8.701614792243642 2.1064432523456604 M-0.27357727815865074 17.511522884574408 C2.648758499697518 15.267172215390914, 5.445603818982638 10.325406455700012, 10.236679371915834 6.788906896001877 M0.5671327813016017 19.026803090423964 C2.453525090925607 16.14856697525979, 4.016786112156146 13.240677970246768, 9.615174326676952 8.118868169914217 M1.244936765320471 23.522857990429205 C4.5931753897498275 19.797064317171383, 8.025440202348898 14.587438966480832, 9.567000565751485 14.723294842963734 M1.1935768501924933 23.428394847816254 C3.697228728853348 19.39297638067927, 7.539100467117044 16.33404521544999, 9.07403526299543 13.99516550537541 M6.3197550713218416 23.616661421880266 C7.212091287681375 22.125320225137845, 8.331885681693405 20.97085948034352, 9.521527787404178 19.486949527288587 M5.687275202725422 23.680924404105312 C6.901522153293149 22.781146819126423, 7.511698712191703 21.559421149433785, 9.210891520571007 19.763713223279254 M0.11254258591050537 22.73433086528881 C0.11254258591050537 22.73433086528881, 0.11254258591050537 22.73433086528881, 0.11254258591050537 22.73433086528881 M0.11254258591050537 22.73433086528881 C0.11254258591050537 22.73433086528881, 0.11254258591050537 22.73433086528881, 0.11254258591050537 22.73433086528881 M5.2340910712887165 22.16052501521982 C3.3012735679612963 21.093576691015166, 1.9948683828300124 19.890107289090835, -1.0519921476037495 16.478169092461716 M6.151125781114828 21.943003031462204 C4.556681157354208 21.528870094411054, 3.047172754751848 20.038069597303174, -0.42629353311073714 17.17598928466464 M10.235068684523998 20.900413667079995 C5.951159826143114 18.3130208611814, 3.8782560626094917 17.122240424273592, 1.0249070977624481 12.351513953475063 M9.039400917558073 20.021634454880843 C6.349555132820052 17.433826776694623, 2.054701411776085 13.472411219182206, -0.17420711109228687 11.909538326566821 M10.142008377397248 15.856200141078325 C6.416396580109765 13.306236332723314, 4.189216435004601 9.992989587090843, 0.12006462161154219 6.900307366877168 M9.326860079560245 14.932715251055079 C6.998328656360223 12.539625192417406, 3.030417247934273 10.133785220410727, -0.2502952783768556 6.85757502691944 M9.901504903281904 9.912960812911445 C5.756876564643497 5.371202885859453, 3.7366096251139393 2.9818122735653803, -1.1537258057941016 2.0324578583397206 M8.554620808171904 9.270805758141035 C5.667376011511837 6.564894041267278, 3.1793301560033473 4.550190141688032, 0.3253454363818945 1.8803889358572896 M9.578435295241492 4.889782883544971 C7.498976932491038 3.0040753157202316, 5.091224478759875 0.005368196122827129, 2.1711066927684866 -2.968308046513987 M9.164992728716301 4.603654146046896 C7.915394289161332 2.9513231775263526, 5.99789631016448 1.9416086000214126, 2.180452701534929 -1.8271188815662684" stroke="#ced4da" stroke-width="0.5" fill="none"&gt;&lt;/path&gt;&lt;path d="M0.057201613398925155 -0.19641453560150834 C1.699812103655209 -0.4514639959478138, 3.411916645775749 -0.25025606413398693, 7.436843496510396 0.6351625165069053 M0.3515048248741449 -0.3005456320647199 C2.7416175364991604 0.23454588431571824, 4.8398018722926555 -0.1687970767873844, 7.19130043266978 -0.026606238194261833 M7.298265290373138 -0.6067905221134424 C8.815628573386467 5.154582130712671, 6.723947748629845 11.259481623748746, 7.186980140322021 22.18423044833785 M8.223170978875032 -0.48430084716528654 C7.506573572113407 7.155510875369227, 7.134918733789814 14.407509983462823, 6.839316083044878 22.09252162336802 M7.825286958430421 22.12557423888637 C4.7484641760252515 22.869828597752953, 3.9278039991741847 22.44021278538961, 0.08954056512396147 22.183367470634717 M7.649182903439767 22.993383637660163 C5.37599036369493 22.489570033500236, 3.3973659797048343 22.93699053093902, 0.15289052736329456 22.438355508205664 M1.8339905831962824 23.195940555664357 C0.9457828427045663 16.86841038289031, 1.8647488380163033 9.652839667513723, 0.03198964335024357 0.17207415960729122 M0.1757476134225726 22.149546608919856 C0.5013853302599612 18.311524545820145, -0.7816903302071866 12.221981372185764, 0.5752083761617541 -0.11647429596632719" stroke="transparent" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(267.9310344827585 10.697663165321401) rotate(0 3.6857654763949768 11.318249543958633)"&gt;&lt;path d="M0 0 C0 0, 0 0, 0 0 M0 0 C0 0, 0 0, 0 0 M-0.7972335740597793 6.318284847636812 C0.49068469613281374 5.3648339337173985, 3.2000245763489477 2.7698239832317504, 5.306576158436483 0.2770983267423921 M-0.5867226827563593 6.289518955026858 C1.3281313765770475 4.649887215737377, 1.9379477138079473 3.6692545056039827, 4.900843532341069 0.7575040750053936 M0.037014898946474695 11.326365638754844 C4.701987058877135 8.459820346404223, 8.273390662117684 2.522310757923557, 8.688209556852533 1.822325688066074 M0.3880379628482865 11.360683243114083 C3.0643856694391785 9.34873852964501, 6.442111163959109 5.557584174075994, 9.711286449589831 1.7670150102359443 M0.29085988061965895 19.457614272367827 C2.0269877429963734 16.161587532958286, 6.34807921242555 10.830144019280512, 9.47945936182894 9.021799991539897 M0.07105135065035417 18.280064124055826 C2.9307024961103068 14.867385362106889, 4.752673628626287 12.832374226729666, 9.724265333455133 7.250856359469922 M0.7832014226912172 22.29613393371592 C3.496449920176392 19.60331272600894, 5.496694346870157 17.2428686376453, 10.029319625235193 12.926615180981972 M0.6612416233027936 22.874657655122157 C2.260034854258274 21.431048029384865, 5.440014436673795 19.307922346044947, 9.073947022137261 13.635691040776365 M5.914031323524531 23.92563149791225 C7.551943756234243 22.749352745895976, 8.289229393781728 20.50665394277791, 9.227315955529033 19.587613082143278 M5.817067213602703 23.871081461357093 C6.614235159747115 22.436888721428723, 7.534574137560055 21.59226030851202, 8.971242394730492 20.160759775784598 M0.11254258591050537 22.73433086528881 C0.11254258591050537 22.73433086528881, 0.11254258591050537 22.73433086528881, 0.11254258591050537 22.73433086528881 M0.11254258591050537 22.73433086528881 C0.11254258591050537 22.73433086528881, 0.11254258591050537 22.73433086528881, 0.11254258591050537 22.73433086528881 M5.360962947707307 21.59287161700405 C3.5110655605176966 20.51090267080492, 1.8661468722127266 19.37254289004061, -1.0507447848112577 17.489618448838844 M5.8472162029404755 22.32375497728564 C3.687039546571605 20.58939550305942, 1.3282183817453599 18.882939125452378, -0.21906741392028267 16.987988727092993 M8.10906020448178 20.345073549118826 C6.559621191883568 18.425987346349658, 4.41291781624305 15.996725982437372, -0.43369632532634095 12.90567803454191 M9.651833972228765 19.577616463829763 C5.399905923023596 17.434355210786602, 3.0652837871700904 14.467938945006898, 0.31047415210120133 11.558793939431911 M9.832623835997472 15.955002232445867 C7.341841132438334 13.390410505706186, 4.568598166506716 9.867589909630823, 0.24965204671681596 6.614904623545081 M10.087241978682393 15.127367776297497 C6.141580058795535 11.569772010599072, 1.874114652538208 8.796423327554828, -0.16625890344656324 6.077429287062332 M8.047968661780333 9.865047290638143 C6.590443559389407 7.479719150788045, 3.6614616319392113 3.8990644890152044, 0.09348193788041481 1.9974527709342773 M9.583866068157231 9.56875050989544 C5.892769389664476 6.604074022135377, 2.881959730850925 3.8507324537128147, -0.48754784437755916 1.508833396264189 M8.61973440551998 3.4391351364008678 C7.4305235156137766 2.9108461156711467, 5.064664852023305 0.24333181629985212, 2.341408791385444 -2.3157057073505642 M9.129756143762338 4.698461833262403 C7.065833912001313 1.6483850815105323, 4.223482234137086 -0.571609781163823, 2.3428401145076774 -2.2243072740340653" stroke="#ced4da" stroke-width="0.5" fill="none"&gt;&lt;/path&gt;&lt;path d="M-0.04264309311222214 -0.2933056011221431 C1.462131578716766 -0.3827730617278492, 3.4610864092484577 -0.1941846224421251, 6.6355325930281746 0.45375600564046814 M0.24177138367184803 -0.06134323327573826 C1.7453047193680715 -0.2572587432224599, 3.246241509885488 0.2928656479164419, 7.068083924463665 0.27765195064981374 M6.4460469120379 1.9598688576370478 C8.143589025426405 7.907609268842981, 7.1656888183837095 16.669507166010828, 8.441362404191068 24.470489671113548 M8.23055876039075 0.1313006980344653 C7.477968731724079 7.510176619898833, 6.557853111826237 14.033228071243181, 8.044278062806598 22.812246701339838 M6.659301486929901 21.954010179098844 C5.943046829017729 22.58155311611282, 3.4227229853291132 23.27257990178649, 0.13260342918638413 22.099149941072707 M7.137803651588086 22.514066017569284 C4.387248407812705 22.663080054972276, 2.0190728432087806 22.383261153269995, -0.08668012467937397 22.581169493434746 M-1.0456213746219873 21.44712470996624 C-0.21843305159612503 17.056528479543445, 0.3298241205115715 11.858141492895061, -0.06167749501764774 -1.2343619968742132 M0.1608767556026578 22.318368783811685 C-0.5484638136969665 15.26019907275996, 0.1905525701893708 9.402296199781967, -0.9093360556289554 -0.5607412653043866" stroke="transparent" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;
&lt;figcaption&gt;Clearing bloat in Tables&lt;/figcaption&gt;&lt;/p&gt;
&lt;/figure&gt;
&lt;p&gt;There are several ways to rebuild a table and reduce bloat:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Re-create the table&lt;/strong&gt;: Using this method as described above often requires a lot of development, especially if the table is actively being used as it's being rebuilt.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Vacuum the table&lt;/strong&gt;: PostgreSQL provides a way to reclaim space occupied by bloat and dead tuples in a table using the &lt;a href="https://www.postgresql.org/docs/current/sql-vacuum.html" rel="noopener"&gt;&lt;code&gt;VACUUM FULL&lt;/code&gt; command&lt;/a&gt;. Vacuum full requires a lock on the table, and is not an ideal solution for tables that need to be available while being vacuumed:&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;-- Will lock the table&lt;/span&gt;
&lt;span class="k"&gt;VACUUM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FULL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;table_name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The two options above require either a significant effort, or some down time.&lt;/p&gt;
&lt;h4 id="using-pg_repack"&gt;&lt;a class="toclink" href="#using-pg_repack"&gt;Using pg_repack&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Both built-in options for rebuilding tables are not ideal unless you can afford downtime. One popular solution for rebuilding tables and indexes without downtime is the &lt;a href="https://reorg.github.io/pg_repack/" rel="noopener"&gt;pg_repack extension&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Being a popular extension, &lt;code&gt;pg_repack&lt;/code&gt; is likely available from your package manager or already installed by your cloud provider. To use &lt;code&gt;pg_repack&lt;/code&gt;, you first need to create the extension:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;EXTENSION&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg_repack&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;To "repack" a table along with its indexes, issue the following command from the console:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;pg_repack&lt;span class="w"&gt; &lt;/span&gt;-k&lt;span class="w"&gt; &lt;/span&gt;--table&lt;span class="w"&gt; &lt;/span&gt;table_name&lt;span class="w"&gt; &lt;/span&gt;db_name
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;To rebuild a table with no downtime, the extension creates a new table, loads the data from the original table into it while keeping it up to date with new data, and then also rebuilds the indexes. When the process is finished, the two tables are switched and the original table is dropped. See &lt;a href="https://reorg.github.io/pg_repack/#details" rel="noopener"&gt;here&lt;/a&gt; for full details.&lt;/p&gt;
&lt;div class="admonition info"&gt;
&lt;p class="admonition-title"&gt;pg_repack on RDS&lt;/p&gt;
&lt;p&gt;pg_repack is a &lt;a href="https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/CHAP_PostgreSQL.html#PostgreSQL.Concepts.General.FeatureSupport.Extensions" rel="noopener"&gt;supported extensions for PostgreSQL on Amazon RDS&lt;/a&gt;. You can find &lt;a href="https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Appendix.PostgreSQL.CommonDBATasks.html#Appendix.PostgreSQL.CommonDBATasks.pg_repack" rel="noopener"&gt;more details on how to use &lt;code&gt;pg_repack&lt;/code&gt; on AWS RDS&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;There are two caveats to be aware of when using &lt;code&gt;pg_repack&lt;/code&gt; to rebuild tables:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Requires amount of storage roughly the amount of the table to rebuild&lt;/strong&gt;: the extension creates another table to copy the data to, so it requires additional storage roughly the size of the table and its indexes.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;May require some manual cleanup&lt;/strong&gt;: if the "repack" process failed or stopped manually, it may leave intermediate objects laying around, so you may need to do some manual cleanup.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Despite these caveats, &lt;code&gt;pg_repack&lt;/code&gt; is a great option for rebuilding tables and indexes with no downtime. However, because it requires some additional storage to operate, it's not a good option when you are &lt;em&gt;already&lt;/em&gt; out of storage. It's a good idea to monitor the free storage space and plan rebuilds in advance.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="the-find"&gt;&lt;a class="toclink" href="#the-find"&gt;The "Find"&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;At this point we already used all the conventional techniques we could think of and cleared up &lt;em&gt;a lot&lt;/em&gt; of space. We dropped unused indexes and cleared bloat from tables and indexes, but... there was still more space to shave off!&lt;/p&gt;
&lt;h3 id="the-aha-moment"&gt;&lt;a class="toclink" href="#the-aha-moment"&gt;The "Aha Moment"&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;While we were looking at the sizes of the indexes after we finished rebuilding them, an interesting thing caught our eye.&lt;/p&gt;
&lt;p&gt;One of our largest tables stores transaction data. In our system, after a payment is made, the user can choose to cancel and get a refund. This is not happening very often, and only a fraction of the transactions end up being cancelled.&lt;/p&gt;
&lt;p&gt;In our transactions table, there are foreign keys to both the purchasing user and the cancelling user, and each field has a B-Tree index defined on it. The purchasing user has a NOT NULL constraint on it so all the rows hold a value. The cancelling user on the other hand, is nullable, and only a fraction of the rows hold any data. Most of the values in the cancelling user field are NULL.&lt;/p&gt;
&lt;figure&gt;
&lt;p&gt;&lt;svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 309.6594202898557 217.01811594202906" width="auto" height="20vh"&gt;
  &lt;g transform="translate(10.659420289855689 10) rotate(0 144.5 19)"&gt;&lt;path d="M0 0 L289 0 L289 38 L0 38" stroke="none" stroke-width="0" fill="var(--light-color)"&gt;&lt;/path&gt;&lt;path d="M0 0 C59.032957619894304 0, 118.06591523978861 0, 289 0 M0 0 C68.55619995994493 0, 137.11239991988987 0, 289 0 M289 0 C289 11.035891681723298, 289 22.071783363446595, 289 38 M289 0 C289 7.6242059560492645, 289 15.248411912098529, 289 38 M289 38 C228.8924437279813 38, 168.78488745596258 38, 0 38 M289 38 C201.03417980866504 38, 113.06835961733012 38, 0 38 M0 38 C0 24.59847422260791, 0 11.196948445215817, 0 0 M0 38 C0 28.538693573884665, 0 19.077387147769333, 0 0" stroke="#000000" stroke-width="2" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(10 47.981884057971) rotate(0 144.49275362318838 79.51811594202903)"&gt;&lt;path d="M-0.7780494858853485 1.3681983066036707 C90.47544329404681 -1.1169060822291295, 178.1603350345365 -1.8922651697643396, 290.2772367165998 -1.0494040810178824 M0.16908271668100122 0.3440149955665091 C79.6610272954566 0.8815468357837666, 158.92673607316198 1.2364868645741653, 289.584298538843 0.19168855099288123 M289.5521144329475 -1.304497042670846 C288.58678669248303 49.94471266770779, 291.2888890555926 102.01565966751514, 287.09366322149504 157.83330681852573 M288.7845519849004 -0.31142672430723906 C286.90449401060096 60.513345319849975, 287.6244882277583 121.8210628062069, 289.80552706801313 158.21304209369742 M287.6530878453379 159.25950799031165 C206.964409212794 156.05033231346448, 128.32039527825032 157.79604598420383, -0.47952031432682846 160.01558805720134 M289.5844737818952 158.66724233004297 C230.07805262921983 160.39482576930365, 171.22288994423585 159.6377570341142, 0.3015134409762986 158.38369116401205 M1.8084924910217524 160.7772659950661 C2.4549477136940228 110.32020957105988, 1.6431607640594708 65.35566680366985, 0.9607170913368464 -1.2252840790897608 M-0.2655061976984143 158.78656278390014 C-0.641924654866326 124.72435196602278, -1.5818253809462668 90.48645621925138, -0.1441378192976117 0.323324684984982" stroke="#000000" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(103.82608695652243 18.3333333333336) rotate(0 50.5 10.5)"&gt;&lt;text x="50.5" y="15" font-family="inherit" font-size="16px" fill="currentColor" text-anchor="middle" style="white-space: pre;" direction="ltr"&gt;Transactions&lt;/text&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(157.24275362318895 49.41666666666663) rotate(0 0.33333333333337123 78.50000000000003)"&gt;&lt;path d="M0 0 C0.11111111111112375 26.166666666666675, 0.5555555555556188 130.83333333333337, 0.6666666666667425 157.00000000000006 M0 0 C0.11111111111112375 26.166666666666675, 0.5555555555556188 130.83333333333337, 0.6666666666667425 157.00000000000006" stroke="#000000" stroke-width="2" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g&gt;&lt;g transform="translate(297.15942028985523 78.5) rotate(0 -140 0.8333333333333428)"&gt;&lt;path d="M0 0 C-46.666666666666664 0.27777777777778095, -233.33333333333334 1.3888888888889046, -280 1.6666666666666856 M0 0 C-46.666666666666664 0.27777777777778095, -233.33333333333334 1.3888888888889046, -280 1.6666666666666856" stroke="#000000" stroke-width="2" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/g&gt;&lt;g transform="translate(15.826086956522431 50.166666666666686) rotate(0 61 10.5)"&gt;&lt;text x="67" y="20" font-family="inherit" font-size="16px" fill="currentColor" text-anchor="middle" style="white-space: pre;" direction="ltr"&gt;Purchasing user&lt;/text&gt;&lt;/g&gt;&lt;g transform="translate(165.82608695652198 54.166666666666686) rotate(0 58 10.5)"&gt;&lt;text x="58" y="15" font-family="inherit" font-size="16px" fill="currentColor" text-anchor="middle" style="white-space: pre;" direction="ltr"&gt;Cancelling user&lt;/text&gt;&lt;/g&gt;&lt;g transform="translate(163.94739488946334 90.51508378937461) rotate(0 58.68576547639498 1.7349162106253004)"&gt;&lt;path d="M0 0 C0 0, 0 0, 0 0 M0 0 C0 0, 0 0, 0 0 M1.5393925315839105 4.436834412194569 C1.9237084554699764 3.132450287200631, 3.778133759205122 2.1872322643159405, 5.080774208302146 -0.20883660783644364 M0.8868998954211229 4.964794198270668 C1.9596842954022202 3.9039372495152618, 2.7741885449311208 2.9279456469240532, 4.923914575255012 0.6198165739734881 M6.885581066747498 4.662871751990929 C7.4039111006188385 3.642816521709601, 9.350239195970248 1.1055912744158745, 10.782153592769934 -0.16531250503959638 M6.822797518791003 4.638228343949481 C7.392992553833969 3.619222613762915, 8.654782288089557 1.9002519271853764, 10.512583290086074 0.2066066423512758 M11.914234246931809 4.919886868116016 C12.32949991612133 3.9266963078914663, 14.2049832459887 2.2784635893868415, 16.00268585468032 0.6868566104141047 M11.863533272746789 4.9331619830285565 C13.225729981317622 3.116236615655646, 14.381562697457163 1.3367225215603455, 15.856544847417105 0.5142950078402629 M17.03631656566473 4.114750883327416 C17.749728137831507 2.8522892297948705, 19.81229661078348 2.5761368115383636, 20.75782767506119 0.4969246737822417 M17.301147700621062 4.178466360335788 C18.866523611677422 2.7752782567982677, 20.22743592815966 0.857787462185694, 21.278406196830684 0.14274900901834398 M21.813361088012133 5.3702435537737925 C23.439859250434058 3.5702344166074114, 24.44307254223033 2.335981449700328, 26.032354306478414 0.11130425371348279 M22.036355386034373 4.928003385340156 C23.58869322089371 3.7972214031158695, 24.295230381536484 2.351130637421441, 26.05007332819736 0.42620164910449987 M27.674691682501887 4.4186266272127295 C28.640927427765888 3.826075385910765, 30.004581904342828 1.655098752274374, 32.29758180495987 0.4671399104654078 M27.742642184819285 4.257323990369555 C29.237954559331786 3.4323347580553722, 30.239292189814627 1.9995653974566763, 32.05684850359971 -0.3700500458233769 M33.137700789199876 5.070570484792897 C34.56648173777899 3.058781135669507, 36.22377825188175 1.310547544617977, 36.53164479807143 0.6612348376866898 M33.23341799309637 4.835719772392968 C34.058023009457614 3.766674485238033, 35.136804039297836 2.330611550034633, 36.664583025408284 0.12240661495369878 M38.925071651633964 4.008727499271872 C39.605544314777816 3.4756035643728964, 41.571368954478736 2.025687631517144, 42.73671871684097 0.1436242206349021 M38.42611397919818 4.622921351238283 C39.24833729063001 3.3694178569572246, 40.233942797180994 1.8515816488200425, 42.38377446820565 -0.42820165498638685 M43.18505608487004 5.056658676798326 C44.97843790353661 3.4556300770812474, 46.14328695355458 1.2728321635587418, 46.95929756383437 0.3865018779587196 M43.50933535174528 4.941150011374486 C45.00430136931589 3.2835743045501222, 45.793649091604664 2.1865219280414, 47.33671944686237 0.5182175225753531 M49.008696707435966 3.9724070151298427 C50.76520236195961 2.9146710815812273, 51.83567854728847 2.2409241081513613, 52.6074161450795 -0.12220638008427542 M49.30695859157023 4.371816060129472 C50.325924824727934 2.944835467612754, 51.34361826016059 1.405420976644909, 53.121610848461 -0.2296794512831859 M53.650104709397624 5.1174241454150895 C55.18688053562107 3.635023920882298, 56.077291808381716 2.5757029348823703, 57.87092783814223 0.5900838901545845 M54.0452842197936 4.427570469472221 C55.432606428617106 3.348254086625624, 56.67315326910066 1.9258155226560236, 58.27969060037212 0.3773794891569757 M59.64368208428289 3.8501436904116417 C61.07358324920668 2.3715731338661437, 63.19319619696277 0.6659878659635972, 63.83872682405044 -0.6258108760671248 M59.717134029747946 4.049003234230622 C60.97791504644552 3.4678071385071476, 61.67456703216618 2.237060697404451, 63.75934690217827 -0.174019567526274 M65.22439939715763 4.394907906268174 C65.62215017232198 3.65800919842936, 67.48992096088756 2.294208349838591, 69.11902585537001 0.4280843898992672 M64.64900385505383 4.676695013873661 C65.79926639797489 3.198196471337359, 67.10038684101566 2.1883605545184337, 68.78326699375003 0.21207513188604854 M70.64501801654973 4.635541040742273 C70.90341520557372 3.2095495686981783, 71.76927098680481 2.479747614322322, 74.3565784881288 -0.5095232516886425 M70.46516080154383 4.147915490729237 C71.74779030508007 2.6467744407845037, 72.8198430306947 1.1854008350229113, 74.51253438716995 0.037679363471852356 M75.79636788252506 4.38427597498259 C75.9170211256814 3.74046041070576, 77.13607731018217 2.9520791252776917, 79.25295684989271 -0.09026810718221157 M75.25479601533848 4.768675011766315 C76.84793134197857 2.7335117981247943, 78.37326813449343 1.430895243362516, 79.3137816348086 0.18292456795009288 M80.52456973861149 4.575327581746724 C82.05289594724225 2.8950351209436205, 82.92986976811221 1.850625102428568, 84.6837738716896 -0.7625524913238393 M81.13612191477037 4.210422946812805 C82.5612721304312 2.682923326669072, 84.26254159680353 0.7212182124998098, 84.82328756124855 -0.09724379110131737 M85.85131289987491 4.73381936718333 C87.10743197806742 3.4737529174772117, 88.01761692829487 2.572620855870631, 89.71896105608532 0.4973799145351301 M86.22352495846005 4.782099330337676 C87.03055860406319 3.929687226049853, 87.6026427072787 2.8982541724729005, 90.01413003136034 -0.20101458546340845 M91.2611222985768 5.382658545011359 C91.89683656176052 3.3213078022435534, 94.71856462771281 1.951256639797427, 96.21818772688553 0.0272625685473602 M90.91180525637319 5.099714334066661 C92.30631290378709 3.2125195379028098, 93.96024414461517 1.12751538330858, 95.42519561219075 -0.3951058636149697 M96.67141218358219 5.044954098495407 C98.50345053448078 2.5253629646571896, 99.89886609072077 0.9490942784100664, 100.66015370832288 -0.2934930293236917 M96.84261744223103 4.293994336775256 C97.87325012228705 3.3235045451929346, 98.60811025483213 2.7282028706106978, 100.40377019121775 0.01775278883305731 M101.80836616336356 4.6898665640488835 C102.48567703510314 3.9830100101377073, 103.00832824367748 2.438094167138435, 106.6985956160253 0.09509013256779442 M101.59817390503349 4.999364713624549 C103.459938840738 2.8058829421764564, 104.77148014763277 1.673137061442095, 106.43840418470968 -0.2054909035211936 M107.33571846802188 4.272221184543804 C108.27536606662882 3.933949229222387, 109.08634893788913 2.4869710685921387, 111.51652504098035 0.10571657686630687 M107.42606272446392 4.47982389826654 C108.27597002689917 3.5115009450030756, 109.30048178177178 2.5366333061012742, 111.47872262219332 -0.054840691973022604 M112.14823795832582 4.339892085275835 C114.4046094462894 3.4164997686057372, 114.93563530844334 1.3513171412356706, 116.64651565402599 0.8219533421613264 M112.6298198521685 4.979489764671063 C113.52883657026821 3.5234356522366697, 114.94135116190736 1.3578223217392895, 116.56446226714033 0.6229149838492263 M-0.20861296487881575 3.2884879375449243 C-0.20861296487881575 3.2884879375449243, -0.20861296487881575 3.2884879375449243, -0.20861296487881575 3.2884879375449243 M-0.20861296487881575 3.2884879375449243 C-0.20861296487881575 3.2884879375449243, -0.20861296487881575 3.2884879375449243, -0.20861296487881575 3.2884879375449243 M5.988668010136191 3.2676014407784413 C4.7865542212842325 1.9092823058157844, 3.288144786789191 1.1306316193886807, 0.28973050091729513 -0.4342907335172237 M6.079426773715843 3.6838781941462977 C4.984077860232479 2.584834521021346, 4.074900602522962 1.3388099060441387, 1.2011976034132485 -0.8057861513486893 M12.318044095140438 2.739857139020713 C9.958344335882046 2.0081206555914424, 8.529024581909855 0.919165579217171, 6.77503414831728 -0.3127585350424943 M12.09528733410735 3.2482453108313694 C10.686559119667749 1.9541384333996044, 9.364684052488684 0.908479703961911, 7.407353292011174 -0.8353732870780823 M18.156861529668454 3.725407794692096 C16.600196023826015 1.8079381686068314, 15.883278585425394 1.7147139785837888, 12.653160798708836 -0.5019610260269096 M17.900340085971763 3.4787991116442796 C16.83353187162474 1.7859136668840638, 14.577450899318947 0.4786571852771641, 13.043598056324685 -1.205098651361694 M24.03946638650703 3.9736208034506952 C23.26002587121453 2.25399710250924, 22.05722991934483 1.6966462920558756, 19.87166071704033 -0.6632346925307594 M24.57999911884288 3.5291433872948352 C23.704787169253166 2.9745996488270428, 22.064568604704256 1.5330920841020976, 19.631744647560673 -0.8518245451599888 M30.072800944586458 3.4253303137668842 C28.126238615773037 2.217213233704073, 26.442229927547356 0.4106217342479104, 24.877215409854866 -1.3734013838442123 M30.35270576791762 3.3110573323038843 C28.257274514619464 1.5832834785922787, 26.705396051441454 -0.07815645045956021, 25.315406201191685 -1.3657127642287024 M36.942575257906974 3.530377161941987 C35.49684729404519 1.968621884838873, 34.710274741932885 2.1427141911098504, 31.940984149902675 -1.563381982415934 M36.36641831717668 3.558691170179781 C35.46897310296466 1.9881937046475007, 33.92954601586112 1.1792697593525228, 31.413097641823427 -1.0730134756844687 M42.00865651199291 3.0978496023421926 C40.65019044778788 1.693115067690257, 39.993303387437734 0.4382998224048471, 37.22708665153449 -0.4803571590623198 M42.09686184085602 3.25727915372381 C41.33112468447788 2.0606797690729826, 40.28856919692957 1.2983491997402403, 38.05826299239387 -0.7672075357688903 M48.4370457473489 3.303798025438158 C47.24807870896469 1.1528568432371005, 44.84026923050446 0.29681579673744696, 43.03993824377576 -0.5618916227649764 M48.6576968676223 3.154024742554469 C46.59181085250396 1.8032168816884084, 44.88119028777237 0.05959306472420678, 43.093944997963625 -1.1018602202348275 M54.66893563455247 3.6897117826403636 C52.69439060313656 1.4967970822850853, 50.7644606429255 0.5686225330128047, 50.36259074618318 -0.5737793601611627 M55.17725577422694 3.7111740089734604 C54.09998681947207 2.5696384731240234, 52.78346619688113 1.6647008998505306, 49.73051960014016 -1.0800700026592238 M60.92656437827033 3.8460562455017153 C58.8114592304109 1.9629532892125412, 57.817065951836646 -0.3233942372140323, 55.293303272097084 -1.9901845120153836 M60.95609307096098 3.325581116137657 C59.24395022991165 2.2150055104968587, 58.377589869390974 1.0407529222195646, 55.322248141762515 -1.2923808920923137 M67.3767943762527 3.12745740657355 C66.18989472917826 2.157739169281465, 64.3744687664649 0.6665526914433428, 61.6937624380177 -1.3966178045263165 M67.11174711993013 3.6313474594304274 C66.11136967283844 2.7236864538716508, 64.99902724205685 1.30188561489862, 61.62753812556137 -1.210404383775743 M72.23689691193863 2.8962139605001003 C71.6260139558229 2.3703436976559042, 69.4994303017901 1.1838567218759046, 68.60350419839185 -0.22293423004260027 M72.89433945810885 3.1053601424597588 C71.19281157334838 1.8477696125550043, 69.97941259847057 1.0164837827947104, 68.1004279761323 -1.0152589208427598 M78.59714510708866 3.6660106019178653 C77.36244994269165 1.7185303992522196, 75.1562061831845 0.27442845123210136, 73.3346795369567 -1.0989099079490872 M78.91172403844824 3.605606259924012 C77.28313496532839 1.9106094130627889, 76.27212267078046 0.6238955897293295, 73.57081004606314 -0.9431048308712665 M85.00863923852442 3.5283976905714836 C84.30741979840266 2.3519337947935677, 82.05292364844678 1.2391841095113856, 80.01553430605722 -0.7291821019797948 M85.42626246966532 3.3193880927950254 C83.6027023986341 2.220513846974194, 82.06330713304568 0.4693661859870144, 80.08105832568413 -1.1192306918631676 M90.87039310049789 3.0083254702358175 C89.80391605692935 1.9990284371923779, 88.04334696159377 0.6919392446058854, 85.62184106643768 -0.736295318866975 M91.19233545556257 3.4877467971729716 C88.91214838590952 1.6826067135851825, 87.24307715235483 -0.5821521119774251, 86.18368741242672 -1.351328082227043 M98.30229643450932 2.8433300897121905 C96.47036759000258 1.5573324399020663, 93.98827481889623 0.7675130184679588, 92.57768877834829 -0.9822251285593042 M97.28612393637069 3.680672440316202 C95.72394037224687 2.14847033900861, 94.02209378151151 0.5060814481356448, 92.3160598871466 -1.3704323253295394 M104.57134071014063 3.308727642080407 C102.6068121682459 2.9101604103581002, 100.78416245577088 1.0291362528804546, 98.47614313012093 -1.463042156726976 M103.8960961272771 4.077468236886557 C102.09868886708948 2.2005583230664536, 100.72142959599354 0.8834722801235716, 98.54188810565614 -0.9873280971338192 M109.10262286572372 3.188916251670278 C108.06717746231293 2.0352571609878978, 106.62739268421242 1.2877641212123985, 103.79025027883756 -1.2376099977901873 M109.33869875909414 3.5083651359208745 C107.85416890575675 1.3628753234822375, 105.48863859072928 0.1694358837547872, 104.61692543939273 -1.0009348392280482 M115.59034904900882 3.648678161033125 C115.16284503379971 3.1368210935920553, 113.83812662615615 2.104019833839385, 110.48404059099843 -0.8562831279475972 M115.72404831892756 3.4461364078025083 C114.21465780903996 1.5333897907544762, 112.01265884416503 0.23626961845643102, 111.10465751608712 -0.9566921718538538" stroke="#ced4da" stroke-width="0.5" fill="none"&gt;&lt;/path&gt;&lt;path d="M1.8840626571327448 1.5885224547237158 C23.185989588825077 -1.037407684731364, 46.69996219007307 1.5737156983138325, 118.37116087686377 1.1360649671405554 M0.7441802425310016 0.32448721397668123 C28.618409254206945 -0.5781302627167648, 58.276248609815994 0.7788670782008225, 116.6742162238072 -0.07928272616118193 M117.45986536600778 0.2603812047707857 C117.14356146944424 1.3035390284538402, 117.23517688520677 2.391206429838955, 117.296524554507 3.2591292380369614 M117.34395924389491 0.11375671914366522 C117.5219226165778 1.0372537750854383, 117.22717611693898 1.9902157962151619, 117.28882946737619 3.4286306624873117 M116.70615429889517 5.073372266225476 C78.71845773555732 5.64181185072903, 41.02007734958764 6.618203941049617, 1.3209982607513666 5.376877150468488 M117.33637945160376 2.671718662574847 C88.53689239222314 4.37715907351875, 60.00874761036936 3.331154629158017, -0.8725300328806043 3.5726152416020227 M0.2106256626084752 3.278231446444655 C-0.29379012048615827 2.681823241631754, 0.17704635460715612 1.4899836582652406, -0.08980876472267635 0.04974518218981927 M-0.009024683072564349 3.3033246924475934 C0.0076270465839475485 2.2287565173201003, -0.11870761614710032 0.9921284691140803, 0.06461778745923352 0.1328549824955503" stroke="transparent" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(17.78399964104733 88.07255505374252) rotate(0 66.45875398214207 3.3441116129241664)"&gt;&lt;path d="M0.6559000704437494 0.9523003902286291 L133.40964469044002 -0.06809172965586185 L134.06162574617656 6.397672954799191 L-1.1521338131278753 8.036929730178372" stroke="none" stroke-width="0" fill="#f41d92"&gt;&lt;/path&gt;&lt;path d="M1.4141949620097876 0.6050111744552851 C45.54198800061405 2.3952677612043276, 91.29931862161577 2.4841651682592287, 133.28839079393538 -1.1149299051612616 M-0.2999035669490695 -0.645080198533833 C47.30730687647925 0.3461694973924405, 96.5760821468729 -0.8148558777353518, 133.69056671122553 0.3187736077234149 M132.47131344992718 0.49867399661324896 C133.45620521775257 1.1260058934356882, 133.5099314254555 3.874435210500552, 132.42391372481526 6.52710958566867 M132.92331153035045 -0.09261557688349709 C132.61679828274876 1.976226030071012, 133.09093178218777 3.730407469108883, 132.95282545185486 6.670565482890404 M132.45620975746306 7.362481765301482 C103.19684351390127 6.3407837355781815, 74.60888496992445 6.471455892388942, -0.9236967954784632 6.920208684952513 M133.6119505987549 6.128631763943986 C105.16410918772317 6.878552972829952, 77.93990535756302 6.990738123214855, -0.26032599341124296 6.49219527173932 M0.5632868783599667 6.806426175983673 C-0.6167651730584072 5.447040537466737, 0.6367623645620537 3.086424866339067, 0.615730591819506 -0.5326179506165992 M0.21356087638979293 6.926829437508684 C0.008557033575723058 5.3384565330347735, -0.2598104480066321 3.855866301920013, -0.11969403288501654 0.2053834177420627" stroke="transparent" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(18.200666307713618 110.82255505374252) rotate(0 66.45875398214207 3.3441116129241664)"&gt;&lt;path d="M-0.5297946836799383 -1.7191759143024683 L134.57694866983684 -1.137702265754342 L134.89143773643764 6.252254847289578 L-0.6296014096587896 7.198577586413876" stroke="none" stroke-width="0" fill="#f41d92"&gt;&lt;/path&gt;&lt;path d="M-1.314145127311349 0.9005595538765192 C48.01583600254428 -0.4682059484947716, 97.81916574995363 -0.8782995540131127, 132.8970200802246 -0.970651438459754 M0.6788537846878171 0.9510406656190753 C50.75362877650677 2.147700681190269, 101.34296542146294 0.5228376824662853, 132.35934462408068 0.897405038587749 M133.28888040842756 -0.5942833163285821 C133.44232701830066 2.1942006547640527, 133.37599604210035 5.086517745317043, 133.23596784367973 6.852799239956767 M132.72486476107795 0.22551125417782203 C132.8573965667957 2.1086732600627216, 132.93964740822386 4.293429308682917, 132.97417884546635 6.765029400074435 M131.49949518694075 5.793448082955138 C84.26117665850788 6.1386112280017375, 35.18260811481622 7.800199598801756, -1.27183554507792 5.9146267695739425 M133.27810686091425 7.157562457808808 C81.04913483137051 8.425556216257974, 30.54328158387021 7.261098567265434, 0.7754772948101163 5.752721004971818 M-0.30450345725276157 6.041199302091185 C0.23295076599769463 4.225749784874816, -0.14777726966736746 1.7470532928993467, 0.34140385329490863 0.12337390824347516 M-0.30547190097492133 6.779837293241168 C0.050252284409389336 4.476978219014427, -0.32283778266043356 2.2305994402109324, 0.2126458968158299 0.213900487486005" stroke="transparent" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(19.033999641046876 97.65588838707589) rotate(0 66.45875398214207 3.3441116129241664)"&gt;&lt;path d="M-1.7191759143024683 1.6594407055526972 L131.7798056985298 1.9739297721534967 L132.4815395857254 6.058621816189543 L0.5103543605655432 6.003562085182921" stroke="none" stroke-width="0" fill="#f41d92"&gt;&lt;/path&gt;&lt;path d="M0.9005595538765192 -1.089774826541543 C31.916650143021116 0.48130856891013085, 63.943858667346376 0.8528765930686086, 131.9468565258244 1.6844141092151403 M0.9510406656190753 -0.3160299016162753 C43.14496195874078 -1.6986674681114344, 83.74444183798309 -1.4604788735317378, 133.8149130028719 0.6386176692321897 M132.32322464795556 0.4770978116844562 C132.6723650631977 2.894446226559329, 133.13109343321662 5.285818661545984, 133.08208397839257 6.665452591459706 M133.14301921846197 -0.09837197309824988 C133.03276856562547 2.3729515921823605, 133.23382828087978 4.834816328887101, 132.99431413851025 6.916925206313231 M132.02273282139095 6.997300628425137 C88.85916933178966 5.941747169956559, 46.987119285181535 4.473994057640428, -0.7735964562743902 8.413682404757992 M133.38684719624462 6.162289189958528 C82.7810063535888 7.478174106629518, 30.84189353064731 7.942999587805894, -0.9355022208765149 7.060519295597032 M-0.6470239237571478 6.861219611115741 C-0.14328414408556756 5.073568414999042, -0.3665137462480076 4.215780758239967, 0.12337390824347516 0.18796452543407316 M0.0916140673928354 6.7376734144873005 C0.04322117496016088 4.366137337367191, -0.00872611065476063 2.90397710256359, 0.213900487486005 -0.08858462770797837" stroke="transparent" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(17.90899964104733 123.78088838707583) rotate(0 66.45875398214207 3.3441116129241664)"&gt;&lt;path d="M-0.03133909963071346 -0.7696782741695642 L131.77753552524837 0.3903953041881323 L133.68923642961772 6.7929733431353725 L0.3929115626960993 4.922266129256741" stroke="none" stroke-width="0" fill="#f41d92"&gt;&lt;/path&gt;&lt;path d="M-1.7191759143024683 1.6594407055526972 C47.1895295892747 2.3260247175908146, 96.21849533149933 -0.2775064642214716, 133.42786232484968 -0.684661140665412 M0.6952143358066678 0.6912037236616015 C30.477293124273622 0.4632572892361635, 62.89041916158091 1.1375819507294649, 133.36896555880548 0.3095451397821307 M132.25551173821384 -0.2269001812091314 C132.8344357277318 1.9146765077589212, 132.54829597782543 4.2236252532663885, 133.4614650021835 6.074014985511295 M132.88199768312325 0.07483086759095647 C132.77124227428723 2.678364500243415, 132.86180766171498 4.72271613195414, 133.00701136184765 6.514523115479583 M134.39513502372893 5.224009685070769 C106.7646979215627 7.611390648801965, 80.32764117523143 8.003735779245538, 0.5038911905139685 6.019880525620238 M133.56774660209658 6.357509069928483 C103.57646757613686 5.510940227991234, 73.51268355292373 5.25486213374723, 0.3255282985046506 6.264720343837098 M-0.35300457029031695 7.05974555834218 C-0.07350080291591098 5.362248815860351, -0.24700207050477846 3.8762523605523493, 0.032181718497548806 0.43830055319750216 M0.30981097107670386 6.706475072549202 C0.22497843745014043 5.154347191111585, -0.016617679420900887 3.635910905049422, 0.1326351846661491 -0.2030950849271993" stroke="transparent" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(18.82566630771339 146.53088838707583) rotate(0 66.45875398214207 3.3441116129241664)"&gt;&lt;path d="M-0.7696782741695642 -1.1399724390357733 L133.30790326847227 0.771728465333581 L133.02225808157118 7.081134788544432 L-1.7659570965915918 6.173213653118864" stroke="none" stroke-width="0" fill="#f41d92"&gt;&lt;/path&gt;&lt;path d="M1.6594407055526972 -1.137702265754342 C50.35102750628491 0.7065745128477641, 96.12459405860403 1.6528972519720622, 132.23284682361873 -1.2779210601001978 M0.6912037236616015 -0.9050551308318973 C49.74513605133823 -0.7983389187948031, 100.08081715068234 -0.4726039756433291, 133.22705310406627 0.05344242323189974 M132.69060778307502 -0.06429249675248605 C132.79493017095234 1.4682373230970247, 133.0616684630748 4.181296177411104, 132.3032997239471 7.13581410235507 M132.9923388318751 -0.14855479762380314 C133.14988118723517 1.9713686338274163, 132.74017497468463 4.148219485610455, 132.7438078539154 6.366831539375414 M131.45329442350658 7.636396352054135 C90.15381864936644 9.282335306860082, 46.51749938462572 8.17412694857227, -0.668342700228095 5.117740515471951 M132.5867938083643 6.785202818775133 C97.46757468306734 7.023719769837276, 62.459633138031634 7.87803251755418, -0.42350288201123476 5.780605661535219 M0.3715223324938475 6.640977246640446 C0.011399294083353412 4.198460774216835, -0.07516508085283488 2.182374534676395, 0.43830055319750216 -0.3193492519892899 M0.018251846700870045 6.88411647932113 C0.13233788127196794 4.926905234469019, 0.30101931097501583 3.389628154552342, -0.2030950849271993 -0.005240072350682801" stroke="transparent" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(19.65899964104642 133.36422172040932) rotate(0 66.45875398214207 3.3441116129241664)"&gt;&lt;path d="M-1.1399724390357733 0.3903953041881323 L133.68923642961772 0.10475011728703976 L133.31041952698024 4.922266129256741 L-0.5150095727294683 6.66113800168182" stroke="none" stroke-width="0" fill="#f41d92"&gt;&lt;/path&gt;&lt;path d="M-1.137702265754342 1.9739297721534967 C35.918334556353315 -1.7324450256415793, 73.21896023038968 -1.7875047566482016, 131.63958690418394 1.4725079033523798 M-0.9050551308318973 0.08377961348742247 C36.2061982831677 -0.16062648946661406, 72.612408858117 -0.6091856246652072, 132.97095038751604 -0.28078817296773195 M132.85321546753167 -0.12753394562609988 C132.50087609501827 2.195232837193865, 133.39526537957647 3.4978487410579993, 133.3650988407909 6.711114302117311 M132.76895316666034 0.22430665598217714 C132.8524164978559 1.90173308087452, 132.99990930561546 4.00056516451696, 132.59611627781123 6.792720169365494 M133.86568109048994 7.953198306114928 C98.0724316969303 7.372445361719928, 60.50704752248687 5.046782450781665, -1.5704827103763819 5.917310647518889 M133.01448755721094 5.990153395423249 C97.30560280010678 5.294498286645465, 63.076794832858596 5.825143329303317, -0.9076175643131137 7.080776267537431 M-0.047245979207886646 6.761680896232166 C-0.18544678976772241 4.0480657905366835, 0.0674821755265805 1.8724223175272476, -0.3193492519892899 -0.2908518397734303 M0.1958932534727974 6.841464906876794 C-0.10766505761358469 4.794641371260408, 0.07067299045080833 2.054118210282265, -0.005240072350682801 -0.12869450274329353" stroke="transparent" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(19.242332974381043 159.6142217204092) rotate(0 66.45875398214207 3.3441116129241664)"&gt;&lt;path d="M-1.9010888244956732 0.5413527693599463 L132.55703773824962 -0.25828091241419315 L133.43958481876643 7.860065940619961 L-1.9803152587264776 6.890369240046994" stroke="none" stroke-width="0" fill="#f41d92"&gt;&lt;/path&gt;&lt;path d="M-0.02708522416651249 0.5691442582756281 C43.64898490032468 -1.043913175145103, 84.57586946268208 -0.9355093136224284, 133.28318963302763 -0.18016808293759823 M0.9604518162086606 -0.030379791744053364 C33.47402622627954 0.4512371903518153, 67.31948406683138 -0.13590798915830127, 132.2781248913193 0.3396849138662219 M133.50471484923773 0.3735124489456425 C132.56233612850247 2.2108252021626784, 132.92638679575396 5.291671418116801, 133.1531879051075 6.526939541865397 M133.24716882362935 -0.23017980781824202 C133.1005729190086 1.7189137078666354, 132.74667541755784 3.5659769550955533, 133.11877206852492 6.594741165062349 M133.47506979955824 4.75557374194409 C91.2709945855587 6.197360604637216, 46.04319767661288 6.4606983956032495, -1.8492508921772242 5.498406939060942 M133.62944900254251 6.794080995807008 C103.68214299960529 7.109692471905201, 75.19324630605527 7.90812354056403, -0.4665891481563449 5.963452570924119 M-0.5291221325140583 6.420619509358146 C0.14775073595237542 4.209812747834006, 0.12906885985196825 1.584567120446917, 0.532795040543945 -0.34419058041599665 M-0.19063576544796426 6.835525731886468 C0.24688670486100045 4.283717546552303, 0.3502984820478245 1.5167009989287776, 0.22143943972508795 -0.015164581085301054" stroke="transparent" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(19.658999641047103 182.3642217204092) rotate(0 66.45875398214207 3.3441116129241664)"&gt;&lt;path d="M0.5413527693599463 -0.36047022603452206 L132.65922705186995 0.5220768544822931 L134.08935067905577 4.707907967121855 L0.20214601419866085 8.478474609406248" stroke="none" stroke-width="0" fill="#f41d92"&gt;&lt;/path&gt;&lt;path d="M0.5691442582756281 1.1624912228435278 C26.75273944667553 -0.3791173897858868, 55.0000648749436 0.38368217123782533, 132.73733988134654 -0.8935314808040857 M-0.030379791744053364 -0.462927277199924 C42.18481371596396 -1.8310315594298099, 83.74507996994883 -1.9159916922671054, 133.25719287815036 0.9304772363975644 M133.29102041322977 -0.29274918682613393 C132.47418222957418 1.453541958624628, 132.92623753141407 3.0001608972219342, 132.7562242803012 6.455348573002247 M132.6873281564659 0.13574190747563902 C132.89788254950784 2.2615418462677304, 132.97135348182834 5.1606589847264726, 132.82402590349815 6.759877505494167 M130.9848584803799 7.764985684157864 C106.3732725627737 5.631013260650136, 79.69174989284818 5.209520982074239, -1.1898162867873907 5.066243711711422 M133.02336573424282 6.548636901044802 C83.58795017071571 8.101813133598185, 35.02830803705923 7.167928363920069, -0.7247706549242139 7.283939379119829 M-0.26760371649018694 6.823562931505138 C-0.11170842302893397 4.922524390605937, -0.3320588214291784 3.456304936794691, -0.34419058041599665 0.46063039185601107 M0.1473025060381355 6.877380793323804 C0.12445759975875215 5.551616976611422, -0.17546158825426267 4.051311477775011, -0.015164581085301054 -0.3178726607598167" stroke="transparent" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(20.492332974380133 169.1975550537427) rotate(0 66.45875398214207 3.3441116129241664)"&gt;&lt;path d="M-0.36047022603452206 -0.25828091241419315 L133.43958481876643 1.1718427147716284 L130.93719270555766 6.890369240046994 L1.7902513835579157 7.912758949996487" stroke="none" stroke-width="0" fill="#f41d92"&gt;&lt;/path&gt;&lt;path d="M1.1624912228435278 -1.3861821200698614 C36.05544504775744 -1.6560244409580738, 73.27080754881881 -0.5584142653484852, 132.02397648348006 0.34189010597765446 M-0.462927277199924 0.03740228246897459 C35.18248037032929 -0.5093628349302068, 70.83442354794883 0.38006497597715705, 133.8479852006817 -0.9333218531683087 M132.624758777458 -0.417965711951558 C133.04753013320484 1.7869563300588167, 133.21190859360286 3.34444979635804, 132.68463331143806 7.161453698336681 M133.0532498717598 -0.05467860704177929 C132.76528454217805 2.375791215688023, 133.18470424039396 4.620515720032614, 132.98916224392997 6.362903601145308 M133.99427042259367 7.08884828522946 C100.52937376732001 6.067771310350436, 69.14750590538132 4.213992173215884, -1.6219795141369104 8.115096323044554 M132.7779216394806 6.716738634595231 C89.58001282342096 7.655873213069695, 44.8413372913579 6.104156080739754, 0.5957161532714963 6.5026577942460335 M0.13533970565680475 6.614707976820851 C0.1695700428078034 4.005958870651584, 0.5857072537953508 1.567235229894292, 0.46063039185601107 -0.5772913738552948 M0.18915756747547097 6.750814906162814 C0.33069649775188786 5.063087944838003, 0.2595665928146808 3.897443098139437, -0.3178726607598167 0.09051720413526271" stroke="transparent" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(165.97365481346037 99.26508378937467) rotate(0 58.68576547639498 1.7349162106253004)"&gt;&lt;path d="M0 0 C0 0, 0 0, 0 0 M0 0 C0 0, 0 0, 0 0 M1.5563645701062732 5.294105925164898 C2.5009033194576897 3.1945714626228, 3.564872493871019 2.8035990248601816, 4.57152999020601 0.06351167353216947 M0.7861843042214269 5.122491397741963 C2.313091094111671 3.4240555501965857, 3.6127121955890837 2.053847861867834, 5.079918162016666 0.4689531457458847 M6.900466239367915 4.385039312076583 C7.147230083144773 3.2547477890967147, 8.378352559046226 2.0430507759308, 10.894611342390013 -0.5172805941581962 M6.567777822547895 4.782470992916092 C7.566642930913744 3.7339659906655385, 8.236274636832917 2.315981062028338, 10.683232471206171 0.21322849058431992 M11.84624069879188 4.80332460556602 C12.430193664608687 3.360137761186192, 13.467806161986804 2.0827256468343496, 15.870274908564468 0.27807665215106514 M11.906528616662907 4.961408157967533 C12.494916435106193 3.7134642640124342, 13.84645410975747 2.384828361892253, 15.489294266145158 0.5614864297141304 M17.815650977062308 4.566123455734542 C19.308255924047074 3.229486313910824, 20.308962568170518 0.7737350788711064, 21.381616529913508 -0.19126274993871423 M17.276380306070916 4.431564030302307 C18.970777427452337 2.786567502252413, 20.38567719062707 0.9429778636009201, 21.28366826008808 -0.25219549320866436 M22.872244299401316 4.721740521074565 C22.797533879416168 4.197815324993202, 24.69822736573689 2.755877433016659, 26.234566244159254 0.2837480455648841 M22.54881773806435 4.5981982244675175 C23.08165185777833 3.9119443745519162, 23.79114167296591 2.6312720538919825, 26.258835394077362 0.5798837426130414 M28.00087431949303 4.380696826687421 C28.86263288441952 3.2975122811469086, 30.068303739441212 1.8982218147428511, 31.837610517485835 -0.4068777487368102 M27.675714219647652 4.577517190369185 C28.625758797353864 3.1047462473587997, 29.60396460752825 1.964087228288125, 31.997040068867452 -0.1830030066647346 M33.490478887559576 4.822156406065533 C34.38992224632274 4.352793470150357, 34.8580999147883 2.5793616242334854, 36.78457001261537 0.8142660881486258 M32.77523428561325 5.049975855054418 C34.06012413854988 3.5901935317970044, 34.61468394511081 2.6083147984266213, 36.65619291300078 0.12329059225511757 M38.013802282285084 4.5249996593102235 C40.244860450696166 3.3479304681058624, 40.833029974374575 0.9320906909783255, 42.53432182073199 -0.5569478561774843 M38.8146809304656 4.538225980929228 C39.306248437592494 3.397769344669816, 40.38184142689621 2.3710032171212205, 42.55271801473178 0.04573270802860918 M43.410172978891666 4.474249994913918 C44.950474181355865 3.198394260105821, 45.72193720851398 2.283383263779157, 47.993577229557815 0.10024601427728397 M43.49639837649712 4.460417807540727 C44.62788615848755 3.4603370480678306, 46.318145615052416 1.8843100040337009, 47.54745569010291 -0.032585013547147657 M48.96522588106346 4.392147187977948 C50.04138923026813 3.2496114816610424, 51.821371451530496 2.023948403245727, 52.796603955276964 0.16257966211426333 M49.14061930571225 4.208148409794935 C49.97250288485278 3.338637546595381, 51.24667747384562 1.9686592770975366, 53.22850971486858 -0.11449909131613295 M53.79368008902259 4.444574021994732 C55.5989186197301 3.739564788057161, 56.889121534623605 1.1396662204920354, 57.88800391061158 0.46917805832587123 M54.37136762790344 4.999763339738957 C55.72599435997549 2.9319359374592207, 57.47488903989303 1.3896624147988845, 58.09715009257124 -0.03547256698124174 M59.51906113145317 3.872129647065594 C61.10269538571836 2.776896161471627, 61.50974032166194 1.4887468166575677, 64.00243543674577 -0.24736745948656702 M59.599305712575116 4.3583049847788065 C61.02722437507495 2.7307762890315948, 61.98753550576292 1.4777905502735278, 63.95066028646533 -0.48564664688646103 M64.25647873357734 5.0965562216829605 C66.11439594508293 3.02140097341658, 67.04760825058229 1.611499740818761, 68.64739107140169 0.5454274546693698 M64.70763715701973 4.764814209461594 C66.22180667595117 3.1652357140522613, 67.3942784263958 1.511599664166543, 68.4682399875933 -0.05653906023468247 M70.0923490490792 3.951812270948166 C71.85198498862152 3.3336476925731184, 73.38598713975459 1.1016153152968222, 74.18291978076341 0.011013513395027985 M70.3349540182417 4.536278713693345 C72.02052278827549 2.545663069318533, 72.95376857452116 1.3197355035889, 74.59385234670954 -0.4630957008698715 M75.86717242818091 4.590499328353614 C77.12390741497808 2.8398505269803036, 78.54328146129676 1.9059641695826497, 78.8044047275531 0.6502344376828005 M75.56113958103347 4.678934674521149 C76.4945857347116 3.7122660974354185, 77.5136476183966 2.1880977285945407, 79.52212674235655 0.009611014505051696 M81.05525564956656 3.7149352940006524 C82.536841176963 2.336897143940687, 83.80741260505158 1.7343320889139306, 84.62228550295488 -0.06759968486596224 M80.99021558989949 3.9662151043510407 C82.6543885540998 2.3213813187190104, 83.86585751463838 0.667815549479045, 85.28120601278872 -0.515669495453875 M85.74748968845832 4.2835810763816875 C86.84261838066308 2.9479644745611715, 88.1775384823243 1.6556142068192494, 89.84483589469085 -0.1982857582588532 M85.83624501713219 4.592051492730114 C87.43102595436058 3.1608329732679725, 88.38746260864566 1.8990471399839992, 90.1186492240485 0.24493566505044245 M91.54111583955672 5.350875186817425 C91.58052103751953 3.1420314863292877, 92.9250456386334 2.185368809705369, 95.65573237169579 -0.05518779542393293 M90.92874107678507 5.008701436444821 C93.11663163755198 3.265686885460024, 94.56222832395663 0.6261506756484904, 95.45319061846517 -0.048157990510605764 M97.27228403003454 4.5298542001421875 C97.33969560284686 3.186539970130988, 97.80311674698753 2.668038491448401, 100.80760058843994 -0.08514289878057896 M96.78576108250334 4.780653921901001 C97.6023590306505 3.5380106756878154, 98.49624993967784 2.533874008755417, 100.4160837998567 -0.19204460078599508 M101.04543321403662 5.553469603693541 C103.42893239334248 3.8158356675575, 104.50485492711263 1.3214443263229314, 106.04341709389297 -0.027351717476038706 M101.65928367540228 5.029090185229164 C102.71274919841372 3.960707750422711, 103.73875900879145 3.0346178645042294, 106.09342741187606 -0.2792923549769953 M107.4695339685346 4.080246434010104 C107.71870705437958 3.378355740580109, 109.54904568729425 2.3898752829694643, 111.28473982442536 0.40152825049261054 M107.51093289433447 4.447793719846294 C108.52734503206287 3.1222711251574653, 109.86503572065315 2.033888924759041, 111.57114433696859 0.23375322480822408 M112.02416367545612 4.35018368215043 C114.27697041617691 3.129949033081702, 115.26360011311692 1.3614599519271071, 116.67677206583174 0.9292076174683855 M112.42069704370462 4.812402405130899 C113.34690058795799 3.8325563178848574, 114.53020564078255 2.565061404869489, 116.20994128892052 0.14077533553838606 M-0.20861296487881575 3.2884879375449243 C-0.20861296487881575 3.2884879375449243, -0.20861296487881575 3.2884879375449243, -0.20861296487881575 3.2884879375449243 M-0.20861296487881575 3.2884879375449243 C-0.20861296487881575 3.2884879375449243, -0.20861296487881575 3.2884879375449243, -0.20861296487881575 3.2884879375449243 M6.353717987177973 4.195043224586613 C3.513899728720805 2.2150873477227706, 1.574454549864193 0.5529044419488525, 1.549538231569687 -1.4553244119073092 M6.51701653933533 3.379454412382535 C4.722898586242825 1.954393382828246, 3.1874571834925813 0.7630959164700497, 0.9865563403567021 -1.0541951539023608 M11.479445817348104 3.562389825239332 C9.925170355389906 1.7349504910373441, 7.958236722881505 0.756987163574414, 7.871825448867384 -1.0998896810115275 M11.55591784466852 3.1436206089812533 C10.599502738157325 2.301156356021367, 9.622551057219152 0.9129985454299542, 7.029080727115724 -0.6303533553966282 M18.619481079635804 3.433704268575188 C16.802810422139686 2.7181047899535846, 16.21718973736226 1.6083914861038526, 13.387963105338553 -1.2598177029407074 M17.984249568760056 3.6734427854106837 C16.812358190484627 2.4637724871024225, 15.62036594029017 1.0210774528706763, 13.050965254028254 -1.18309830232807 M24.313803214239652 3.8866322848773516 C22.694407794209493 1.9593061741533047, 21.99270504854148 1.141089197016397, 19.62660410667214 -1.1908737755362473 M24.78830866598035 3.3392932600635055 C22.75756697200444 2.0090159883382745, 21.657382975127668 1.055943189757858, 19.633712543616102 -1.0595140534900658 M29.982523911473514 3.862664918887007 C28.123236497344024 1.366316247143404, 25.7537153500438 0.28909283092649196, 24.619578363018547 -1.571289219682113 M29.967976395487028 3.0395207351600257 C28.49890528363874 1.5901232395876455, 26.63896038587766 0.4013450466190116, 25.115455611486425 -1.5806284267669626 M36.587140578853244 3.342984559639853 C34.52908936435557 1.4811116978766845, 32.607313366873164 0.8453695986911696, 31.069496563143645 -0.5406824275925952 M36.454557598877976 3.5299581734664107 C35.276793938737455 2.653614842282024, 33.73612364961614 1.3212289434697226, 31.43116140861212 -1.3169268187675822 M41.951873202786544 2.9107309018229883 C40.59369515983288 1.3044055313916205, 38.88372055530594 0.08936863686743812, 37.51011072157388 -0.6254722875943719 M42.17756080019916 2.876745606210623 C41.026884350095536 1.649276815559669, 39.189540255303065 0.5776435207326975, 37.975312744383416 -1.058629248517597 M48.40457164769047 3.746533413381843 C46.59578897230201 1.7078828878445198, 45.866833680885456 0.9649090810018621, 42.94575274337003 -1.190181707700701 M48.71399100290598 3.128229021012379 C46.69909824422348 2.0453380444896103, 44.43750631844519 -0.1717419354280964, 43.59932770372288 -1.4732705151364072 M55.09666227976137 3.2826026612208743 C52.852790636337105 1.9128928791429622, 50.701425819215956 0.18439243195181487, 50.07421851658472 -0.3896743075484408 M55.10246281794549 3.9803813469377864 C53.36867392891694 1.7198402487919247, 51.174629630635685 -0.2540156928497732, 49.87941953486041 -1.1313211215687915 M60.17061302231383 2.9181528695408288 C59.76511401718823 1.788124747686856, 57.5313595770064 0.9611175828604219, 55.1512954989138 -1.047403842423063 M60.5071240147831 3.1402703534826673 C58.981449423333046 2.1075285020658807, 57.46175278640539 0.6182143961051868, 55.726773440667145 -1.6516774668152892 M67.32708143911373 3.0342687711274188 C65.66866550725548 1.4667261253142978, 63.22284909318633 0.48394094059327286, 61.75937953537312 -0.793711360788869 M66.8670431068917 3.3239340815382015 C65.44128614121396 1.6668289401582825, 63.32989356942262 0.24902268876549993, 61.788807543990956 -1.274307369359734 M72.61454430923328 3.1137287700936582 C70.98067644268967 1.575368494372889, 68.88775823665443 -0.15296105629811985, 68.27934406099182 -0.9569266669433345 M72.81102006661236 2.995013215897271 C71.83722942187278 2.538412372631693, 70.5662630074305 1.692450897622408, 68.27038194655714 -0.9671525438297321 M78.52284674409596 4.009730581107201 C77.36845883117056 1.7201135270910757, 74.61063590887537 0.05899353387658324, 73.47142234432265 -1.3868555459873046 M79.28517774001514 3.4892345959848043 C77.54762799958532 2.2373161651356286, 76.46167572536018 1.0894452140853192, 73.62752504378311 -0.9534498900388603 M85.29786517539172 3.9535962118502828 C84.08471790319551 2.894877047104037, 83.41608721585456 1.5915057276983169, 79.91520422242624 -1.162649052970055 M85.56047773918247 3.5246629553116975 C84.57409358845291 2.788439147637685, 83.18582092854274 2.08106843412792, 80.5926796171539 -0.6478701539877232 M91.3142560462706 3.1165808645977306 C88.82996439000713 2.1769071178486015, 86.48877340354325 -0.1606644290307777, 86.45705104499581 -1.5154203397062487 M91.10324122903461 3.620338066058852 C89.77705751980939 2.171931816545441, 88.70697124513566 0.5937375059534686, 85.92749342095564 -1.1914903828249195 M97.38996207674846 3.351546615885245 C96.16785343949465 1.9094643308461183, 93.61627136326784 -0.286146744817524, 92.63726881329717 -0.5943581109101908 M97.74623789491945 3.5415655472424348 C96.65677610975808 2.776264327754183, 95.44197398868339 1.628075563443002, 92.32777324991116 -0.8546983169092816 M103.43162927742289 4.0981589240270075 C102.34990725474225 2.457319327577726, 99.884387587561 -0.31843773100707806, 98.11299181917786 -0.33997268951769477 M104.03716141589159 3.8400149465320963 C102.84886651070687 2.5529881346876495, 101.15557566095895 1.2594284034268182, 98.86518739245005 -0.8074552679461575 M109.80186069014137 3.4979387160871998 C108.14146504888164 2.6438793174630657, 108.2733433848893 1.334856837650165, 104.72543638573546 -1.8690439297145915 M109.52619570538056 3.173459327222488 C107.78080685152757 1.7607428236444014, 106.61358248382133 0.5757221844168452, 104.22483187137689 -1.1495565320159045 M116.58647012866977 3.00931447485152 C115.08037492283525 1.80424049841849, 113.04906460030851 0.6531201794373869, 110.86715460798581 -1.5595691299651548 M115.90358115185929 3.675514855204777 C114.84523494651779 2.7827119361894113, 113.69814914447178 1.5555982297440254, 110.99279668453958 -0.7908918026023344" stroke="#ced4da" stroke-width="0.5" fill="none"&gt;&lt;/path&gt;&lt;path d="M-1.6936921272426844 -0.21267413161695004 C24.051720587243658 -1.207226524681973, 52.94247898959621 -0.46735293659679833, 116.66252466376977 -0.44257729314267635 M0.5681388126686215 0.6286263270303607 C37.69559455642062 1.5574767827047875, 74.17641810565358 0.29424517741342104, 117.54680509112882 0.657931755296886 M117.5078514860092 0.13825536658070625 C117.21773296013258 0.8091293467644585, 117.40581788552923 1.3952605086317045, 117.21469720158798 3.4282048351086987 M117.43763449486546 0.02618494519093037 C117.26941211593444 1.2819701346386, 117.46046356301522 2.768440430514467, 117.33475497272377 3.422765358196188 M118.64358940061288 3.0031672818287802 C78.89149186682405 2.725733525654066, 36.260203888088924 3.9408535431826, -1.878772834315896 5.226347158633132 M117.99084160469579 4.21431056779619 C78.6223588779912 3.5445168761791384, 37.608769835673826 4.194205269056717, 0.25321943033486605 2.624954115569551 M0.3348770017319666 3.756265386961949 C-0.14793677586804385 1.8180595906010613, -0.10555174773600279 0.6265026468284213, 0.23962946667152746 0.12062622029609393 M-0.08474007716841493 3.5670088370266515 C-0.07250837336885896 2.9170132122322023, 0.03659643649813502 1.9401977892442988, -0.17085012360099192 -0.008617515051748098" stroke="transparent" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(160.7006663077134 107.65588838707583) rotate(0 66.45875398214207 3.3441116129241664)"&gt;&lt;path d="M-0.9886523392051458 0.7629342284053564 L132.51564731924327 1.784803232178092 L131.1543284329632 8.249066833259121 L-0.5182266738265753 7.368450943233029" stroke="none" stroke-width="0" fill="#f41d92"&gt;&lt;/path&gt;&lt;path d="M0.9030838813632727 0.7620372865349054 C38.582499120441135 -0.3969555008647584, 75.63089285988521 1.8054730188610413, 133.41419492019804 -0.4239510800689459 M0.9418623195961118 0.6360292239114642 C36.03596081701502 0.9878805006070936, 71.61379821366488 -0.28421646112958543, 132.60027941326143 -0.939386417157948 M132.7770764660154 0.41420878860918764 C133.43960575535294 3.0651252698101166, 133.15645938233095 4.615757924103919, 133.5668112942292 6.857582033368506 M132.6675047067349 0.3227435606211416 C133.25237271844333 1.2654631937306808, 132.68167640397635 2.7477060020941746, 132.77176598536957 6.919170310608771 M132.17849329961928 5.711345188172118 C89.54171765522013 6.292091978959988, 45.811884378374685 7.549845053129147, -1.114913085475564 4.718674234898344 M132.21202527741434 6.33344533372815 C94.21884057167999 6.683815724795013, 54.42967064408285 6.70647469872728, -0.2864329172298312 6.284875624665574 M0.04751241015146568 6.09920718126725 C0.6191206897195044 4.051700195285685, 0.2478109798746857 2.987161920755868, -0.20971263778346594 0.25593619835174775 M-0.3240067153966796 6.970077959054409 C0.2341349065768699 5.053788944680436, -0.23031853392962234 3.1980828040634353, 0.27076079336146164 -0.23157160169768087" stroke="transparent" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(165.9605248514622 120.39008378937473) rotate(0 58.68576547639498 1.7349162106253004)"&gt;&lt;path d="M0 0 C0 0, 0 0, 0 0 M0 0 C0 0, 0 0, 0 0 M0.5092985491406972 5.170207895837505 C2.831891926953226 3.829138366920982, 3.2791244248714366 1.7502041719398083, 5.130399638185746 0.06028930338446442 M0.8755307607491368 5.1652944468294875 C2.1581225642337865 3.9065296934859015, 2.7088058147422087 2.915326788522704, 4.98804971375922 0.48708731055606536 M6.397690379535151 4.891509324741378 C7.30942560087489 2.827532367633543, 8.9673210638573 1.6845596445227224, 10.211540682752561 -0.6210919535637137 M6.455364940696699 4.300251153820023 C7.383057618614786 3.744844127319895, 8.494162187655567 2.7117670997526924, 10.900688668944818 0.041349532835906866 M11.275743281341928 5.122486878973003 C13.119488413869533 3.3036838187348816, 14.273295778759513 2.9014060494073792, 15.38637722255983 0.052875522237735006 M11.797581005807805 4.95128457131287 C12.934202363612288 3.2948477270526806, 14.026704806431821 2.459212715864648, 15.869737219254901 0.12327599003212358 M16.893545283245167 4.002177208633712 C18.436160697042457 3.8732134297069765, 19.4874472479388 1.9152276166677795, 21.366130098791682 -0.13677943160985673 M17.287060075586908 4.55471833671405 C18.67030116661469 2.9057723359303336, 19.700008162847894 1.6678273692202519, 21.141697577943916 -0.11899327419926495 M22.54276861046699 5.400759972215922 C23.178589975262362 3.2056828053346482, 24.254163556274047 1.4178405895169426, 26.68485434917024 0.5408650308859289 M22.256224497326436 4.829874564570667 C23.532001796053542 3.103747153709851, 25.14748519880095 1.463384877669281, 26.319988186054292 0.48630526093884713 M27.736391916691517 4.350631194820966 C28.79137639577091 2.7494689094554228, 30.304287371851043 0.8192778092629859, 32.179161439879636 -0.2023028776735778 M28.06788507803083 4.457022204788741 C28.78380167250358 2.902100786308512, 30.08635276147767 1.8305799321288918, 32.14779094434047 -0.0874930483975227 M33.391478052140876 4.352829909941265 C34.26932981170784 4.166820039317687, 35.16194236890409 2.9177715814032026, 36.77099703364198 0.8329980637040456 M32.80114385249253 4.73067868441538 C34.49373619989534 3.1375061360043017, 35.354787060396255 1.9124014877783198, 36.6939539813449 0.08782063108873572 M38.50889629549919 4.008111517678143 C39.44919914838182 2.9275805529514876, 40.33742568132049 2.4868825391317744, 41.94736322164141 -0.43548455737987696 M38.534241315215965 4.637558265856596 C39.48520796112954 3.139782772929509, 40.503959562724205 2.5298983622843734, 42.33583888532199 -0.12672303194636642 M43.756100121988595 4.723374426524026 C44.98249992694415 3.493064155046104, 45.45847975699909 2.9282087985572254, 47.54309629160005 0.13488985506707896 M43.630459188493575 4.709873688659501 C44.469052703282266 3.4361790025537524, 45.449382608678725 2.53273531547676, 47.74522347231421 0.416030108294283 M49.346849435293194 4.542733411580243 C50.47726410322301 2.9346568822227743, 51.68774695349536 2.6720900875774074, 52.62058895970202 -0.2572717363537542 M49.42096765432981 4.5032845277301154 C50.15527195029708 3.2779153177969276, 51.43557788913361 1.881351224460259, 53.07114901807597 -0.07269396703329606 M53.823988335635136 4.653946256339214 C56.08550584389658 3.08582756619483, 57.43049214924518 1.281499589577535, 58.45256813866944 0.5490306393500411 M54.29072812251709 4.650198835115544 C54.96667738158475 3.789058454441019, 55.50718795004841 2.959061640020806, 57.92582302614973 0.23570420036800144 M60.33916735169853 4.419487110902264 C61.50861719138427 2.3677153838559164, 62.279348991323914 0.9879901184573024, 63.40181716968645 -0.6917366802202096 M59.76134046255986 4.337721498221799 C60.88038898898223 2.9896856198083652, 61.71430605786238 1.8112772644317021, 63.56746645978991 -0.23485409584581163 M64.6356212087055 4.282973133761818 C65.84335202954755 3.197261418849712, 66.5296440353523 3.229101075669192, 68.30395359047516 0.4747856509279147 M64.57694577175056 4.560955881595993 C65.86832301332511 3.9268648550870524, 66.59953541837615 2.579273288644567, 68.48621651003897 0.08817591512237324 M70.92789106034019 4.400237850019211 C71.85232856778512 2.965311968686901, 72.36379148203669 2.7255173897396934, 74.94715136721972 -0.1660766546042396 M70.18707360709362 4.400952185144151 C71.4438935035328 3.6170460256437647, 72.07033494863775 2.341680142928155, 74.18697382765131 -0.49608916057140956 M75.08460225682471 5.145757793166847 C76.51798716396009 3.4566730810592903, 78.1636953307809 1.9699599219232962, 79.45491866264221 0.008395124175720525 M75.43254475338149 4.8780090859133605 C76.82421617462471 2.8870111073356286, 78.3823360868451 1.846919765511096, 79.57530529116941 0.19134076036991987 M80.50818657684317 4.543725863306072 C81.86876477732567 3.1445483675490467, 83.02535775463443 2.409375919781316, 85.1658200679268 0.08938607486914518 M80.8283184551186 4.029622096309659 C82.71544555658052 2.677811653553928, 83.72669914527555 0.7501518075555438, 84.94029123884768 -0.012722403397600612 M86.11765999561773 4.774477968929293 C87.67554750093062 3.734138729296535, 87.9374009721142 2.2234656345856525, 90.01625437079558 0.34297429346844077 M86.24939518459739 4.363785205880139 C87.61747356183459 2.639080938720554, 88.87954983839829 1.265162781380567, 89.94042823672855 0.13965674392312327 M90.72126763589766 5.6582363604471855 C92.47944058883776 3.841592778936123, 93.05601378752633 2.5392446243601454, 95.84507148493127 0.3331471941237969 M90.93747308571909 5.111504690572323 C92.10669637404102 3.794517606454439, 93.51314492814657 2.370768744532949, 95.80575426192068 -0.548524749810838 M96.53464794070959 4.49818639542417 C97.84560555409014 3.8300265012356665, 98.81629086407413 2.3265874918286396, 100.4518767305603 -0.431486606818909 M96.96780468258575 4.607273499308837 C97.83368171629192 3.5742589501138884, 98.9336713426783 2.1177290578575265, 100.82736171390394 -0.19585562640550191 M101.73792937072913 5.235449307596739 C103.62766870867017 3.6674414781795157, 104.71500103416855 1.379101686186477, 105.86316697373 0.11909789609512811 M101.94834252759802 5.0889445257824475 C102.79425163642944 3.1686743568025975, 104.6548132296482 1.6802996572928324, 106.3383784454683 -0.6479498247264457 M107.18854460772528 4.042810807026461 C108.50156989958424 2.247959023990582, 110.351621760441 0.9766173089252826, 111.77882675756133 0.4718776576886555 M107.17651836453406 4.3240257240119435 C108.26870518983776 3.4358404150506945, 109.58171530851612 1.9352913860473133, 111.48031958988301 0.23238665719652968 M111.99098010077961 4.545850472158975 C114.08769829194688 2.8379872032383644, 115.61680789869335 1.3254527405335759, 116.49493431635054 0.23920241040661805 M112.2671403630253 4.877869333612955 C113.30479342279077 3.6735436849614262, 114.32034080557493 2.506913970344653, 116.07980837533104 0.494903457868098 M-0.20861296487881575 3.2884879375449243 C-0.20861296487881575 3.2884879375449243, -0.20861296487881575 3.2884879375449243, -0.20861296487881575 3.2884879375449243 M-0.20861296487881575 3.2884879375449243 C-0.20861296487881575 3.2884879375449243, -0.20861296487881575 3.2884879375449243, -0.20861296487881575 3.2884879375449243 M6.4027388020736025 3.0787990515366865 C4.594051670622626 1.2337941060369446, 1.8356418626524715 0.035461359011204174, 0.5056955821098481 -1.3838564877892674 M6.389873609304507 3.6630788945373816 C4.09085743375749 1.9214117797323973, 2.5127533177003922 0.29699482008569966, 0.7175516854723637 -1.177891075800249 M11.705109288570029 2.9638091787244396 C10.577598554813907 1.2869960530295956, 8.294568430855914 0.23382764634872877, 7.89639333903462 -0.7832634183467326 M11.88253236760248 2.953257154162528 C10.024338192985137 1.4294149341007065, 8.230583988071782 -0.056848805973118643, 7.009245970264527 -1.0739013194926974 M18.437682192382994 3.0196176882986987 C17.799580305075846 2.1669094953274293, 15.424377860949104 1.9621871335516583, 12.375117159980276 -1.5464460922992278 M18.298078013240954 3.0862863227413113 C16.85354460212473 1.8677604452624155, 15.265353346643934 0.4369647845968923, 12.813117520530636 -0.931041965925177 M25.160417718100128 3.415348133800081 C22.89471292152181 2.0094587557731374, 20.233585954938103 0.241895846951094, 20.041048505552144 -1.545295438820549 M24.880593989123355 3.9441266960757737 C23.268827824981283 2.4371114672272953, 21.66813231859477 1.1242197920633834, 19.316680087218092 -1.1332168418430322 M30.91385063457227 3.134915618883955 C28.17614500366939 1.9856622765372927, 27.812490493987287 1.310612188237684, 24.762924434261834 -0.9130842350171959 M30.280491058229643 3.4348059839164344 C29.26910819039192 1.9603195160263733, 27.779377240810543 1.565782057843676, 24.925854643125525 -1.3089721758132882 M36.692290849315285 4.05169003258602 C35.458045632478424 1.4128156067716673, 32.579618298952525 -0.03248842633060889, 31.72616813487521 -1.1472433731034595 M36.8001678121577 3.38056339902946 C34.96910080001915 2.108533750502394, 32.988050558266934 0.6852164818891477, 31.12170188922583 -1.2373871144447672 M42.68121404217253 3.3223889027690334 C40.966864401308634 1.4821870739470002, 39.178802723311996 1.1321188972526781, 37.41345363615518 -0.7596425337186395 M42.552702849988584 3.3586309917239294 C40.8021702319875 1.82230341045103, 39.51792161518852 0.6690048134130935, 37.75630043626269 -1.001754544156635 M48.10595299725407 3.5256581978514476 C47.43785666632186 2.2843670630098174, 45.689958641994025 1.1267388449079896, 43.96359680045315 -1.8397022479381278 M49.023765950072416 3.5747036923762097 C46.864106339822115 1.3245875861204839, 45.045048366524576 0.06446299488435647, 43.46765596509831 -1.3997656618878112 M54.93607616106325 3.4300669842646974 C53.78875873713033 2.3342819044948415, 51.81161216778193 0.22234106372697243, 49.955534558531134 -0.5830135123366489 M54.96049444846154 3.3252179872957575 C53.51379931624571 1.9459620547621337, 51.56382390093146 0.38020451065388705, 50.01258162802295 -0.6639220727635545 M60.068420763479 3.5956266530338707 C59.367094517353166 1.6610178798536466, 56.92855951814884 0.3231202058381486, 55.511757353848495 -1.793204289887052 M61.06065353958626 3.563962126897279 C58.75023258759267 1.663319530739352, 56.724061479369894 -0.23285131205398021, 55.81185432655429 -1.6122348083909117 M66.88645686826717 3.8456094369446796 C64.93463748160497 1.8050849010936343, 64.4321650581127 0.5004942056785358, 62.1474735314211 -0.5084281290536394 M67.40940548691947 3.6983804020719537 C65.62937664397539 2.3660058188339606, 64.58645923147908 1.680348557455836, 61.933985933540214 -1.0682654372537157 M72.23476954803333 3.4062308872506404 C71.74329910066396 2.3877688626889255, 70.50506655896166 1.4402197822267606, 68.57679403123718 -0.7494129531066648 M73.02155487763225 2.920875957367608 C71.19273150554056 1.75144520803659, 70.23983102556532 0.9602546465699636, 68.44999482154188 -0.874064155386739 M78.79612903360096 3.4191272771979 C77.55305901479706 2.4868215872372588, 77.06621641232005 1.7104345491689332, 73.74148960697218 -1.770020190228637 M78.84458571330464 3.0715132529443387 C78.1456237471479 2.430027417205129, 76.82346368244559 1.0683339219889292, 73.97680957346908 -0.8399174183820611 M85.63911163726002 3.8615570558901995 C84.75548114205105 2.1135615380505572, 82.30198025190445 0.6777065540336904, 79.75523761056407 -0.5109702523871683 M85.51528595050908 3.9718319015778722 C84.2648697688124 2.619116817304935, 82.25367398109448 1.3087544052639646, 80.00316028331449 -0.9356339164617836 M90.75789050862717 2.9957132784618663 C90.48462695307049 1.8306237425255738, 88.78548764228789 1.0644774135267812, 86.2467868867469 -1.1766031724241444 M91.38115963933582 3.519921715694167 C89.8882006026147 1.881644355885855, 88.10661650909476 0.71990098576239, 85.84172393452833 -1.5703697153673388 M97.1032208677183 3.8666455214485986 C95.14506568541348 1.6501760027048642, 93.35399070601416 0.6226725545711913, 91.73925593678655 -0.3739201550538678 M97.29607021513276 3.59749456430939 C95.94964254290284 1.862978414114294, 94.54825453166669 0.935044124867541, 92.68440107183346 -1.17310630545756 M103.56456122513102 3.2562067431645794 C102.36130434675846 2.4635861892721547, 99.95045400255191 0.19831590330654691, 98.72259155253053 -0.9512440232121536 M103.80380510695451 3.8976262450931927 C102.92900445215223 3.1891784646712438, 101.7368008240956 1.7648553228049664, 98.97395373365947 -0.5473987484377953 M109.13732712271278 3.9981053706739926 C108.47809348706706 2.306545653019478, 106.58475484952585 0.8639404527302139, 104.5208225570764 -1.7831671310848305 M109.82564197823059 3.4444960713402253 C108.07146533523547 2.202392645755058, 106.6428117515164 0.797277071924211, 104.34157169281092 -1.0016360903884016 M116.39014487422948 4.192957386762531 C113.51070661310561 2.6143489139081604, 111.84293422939767 0.2558443499857662, 111.05931354544003 -0.8554992771361754 M115.89791496743392 3.8631264589416547 C114.2330404660206 1.6910775691546807, 112.07620390567769 0.09931748903811721, 111.09443869713571 -0.7293017747931912" stroke="#ced4da" stroke-width="0.5" fill="none"&gt;&lt;/path&gt;&lt;path d="M-1.1372508201748133 -0.23434065841138363 C31.991630108883935 -1.7706760088648623, 65.4367551633089 -1.8775126258578128, 117.99884267982202 0.964375564828515 M-0.11952855717390776 0.23701665829867125 C30.562328027096314 1.175764466152922, 63.73866569809749 0.8781242915256181, 116.6588966324191 0.428721378557384 M117.50389364884009 -0.06971945474937447 C117.6571516126058 1.0159928101970888, 117.61829649425238 2.553871384900442, 117.4895447621707 3.343555829495461 M117.31033785380102 0.016258915912562538 C117.3403125141565 1.4049634695314255, 117.35083853833815 2.521413796449462, 117.35713774063296 3.355539125397641 M115.5327333443995 2.8704778058155966 C83.94026392008487 1.6856916946810063, 51.32700248832397 2.7943358821314153, 1.2882946934551 2.7429801923855734 M116.87949869774388 2.5808490924238754 C93.1018492956443 4.714837299741258, 68.97645685918192 3.975624935782899, -0.6042630029842257 3.090415369689424 M-0.11861939677549724 3.510392218965438 C0.16790248531686403 2.9816574310483093, 0.10973882644943883 1.7087973026245038, 0.09712929404272419 0.09058514252828276 M0.1551346205447676 3.3807691946805556 C-0.033857076349634564 2.6184976331600676, 0.04568035199197799 1.6065795482449354, 0.1511032440194743 -0.030040646842850433" stroke="transparent" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(167.98678477545945 129.14008378937473) rotate(0 58.68576547639498 1.7349162106253004)"&gt;&lt;path d="M0 0 C0 0, 0 0, 0 0 M0 0 C0 0, 0 0, 0 0 M1.3325042731267267 5.33371042547353 C2.699792038494034 3.1325813706239245, 3.3291424355913812 2.3693734187784097, 4.687197335953361 -0.15804175605650594 M1.3275908241187089 5.156613359447349 C2.226733512014481 3.513258627994754, 3.399135433830692 2.1602836853639777, 5.113995343124962 0.20856242429811894 M7.091482343812776 4.123416969981804 C7.30658959848112 3.4999383861675577, 8.439708095914234 1.457165848285649, 10.04349272078736 -0.4642639758914945 M6.5002241728914205 4.3002484252311435 C8.30151420680029 3.0240389895357955, 9.62356533052007 1.7563610593154908, 10.70593420718698 -0.15127397979723792 M11.949367930613297 5.257931857225056 C12.427352032688278 4.1221275049204635, 14.327362606598342 2.072704101284397, 15.344368229157705 0.23675500833662055 M11.778165622953164 4.951338794962074 C12.30658245770941 3.8811446099439193, 13.653170464675167 3.041659128769993, 15.414768696952095 0.13773591126547957 M16.86673490205618 4.638266834960869 C19.006863361734343 3.414377548426943, 19.313836639581368 1.8554174884763257, 21.19238991709229 0.05090975688542165 M17.41927603013652 4.551060400968036 C18.4390364558902 2.9187041208515034, 19.873422574255343 1.5309697915641336, 21.21017607450288 0.006514123564166141 M22.89222569820729 4.44384228433791 C23.670678760011885 2.4988050250084224, 24.8551643998125 1.7909808520468289, 26.496942412156972 0.6437534634035766 M22.321340290562038 4.634329840218009 C23.458350347222122 3.4257473167268215, 24.676100021744237 2.0613823501512463, 26.44238264220989 0.3890983478178025 M27.879773562594508 3.9260605781137605 C28.730439909353823 3.05234713711679, 29.255382765594014 2.5450820590819525, 31.79145114537964 -0.1659286868513341 M27.98616457256228 4.126502732539889 C29.024780142220315 2.7978572310909873, 30.547940130442363 1.7147337388859023, 31.906260974655694 -0.38065970239571884 M32.50888031028371 5.277232722437093 C34.277441944643805 4.152860220423028, 34.98574244699253 2.308620200982974, 37.45366011932616 0.4628071247693619 M32.88672908475782 5.015425362402658 C33.94005372039191 3.281016926128194, 35.35704435429691 2.0246396867067022, 36.70848268671085 0.002957351142587328 M38.20183855980276 4.424151715065482 C39.423779121687765 2.943294153634138, 41.28515410123109 0.7703204744162271, 42.22285414002441 -0.6133632914767617 M38.83128530798121 4.448133037134203 C39.74283393652125 2.9891264733525915, 41.53677530620919 0.9817643376727434, 42.53161566545792 -0.38576909048939284 M43.544009501217545 5.297019187147599 C44.39635985696542 3.6372911194260493, 45.91803508087573 2.238387191373943, 47.42013658504027 -0.05310549194103015 M43.53050876335302 4.602901728703511 C44.19987719173076 3.6684095734013127, 45.235359659780315 3.1670627400965223, 47.701276838267475 0.06805803842732772 M49.40104512805593 4.62301876144103 C49.94791452269091 3.659420960643076, 51.842784354725474 1.4439676363679865, 53.06565163540161 -0.23685304136586116 M49.3615962442058 4.185946431938477 C50.87180676109624 2.6654001388435864, 52.20644957289185 0.9792625878860053, 53.25022940472206 0.11664982647064409 M54.1391660053838 4.995242694094442 C55.15239571411431 3.8939457564828728, 55.93466030960586 3.234228373079385, 58.4988620436743 -0.39788087110258696 M54.13541858416013 4.703472806308083 C55.537255458215725 2.889076641141112, 56.96808467422777 1.3997434144488403, 58.18553560469226 0.42158302685953614 M59.942383501729026 4.466265232865408 C60.50655119420899 2.673965594343884, 61.74482082088573 1.8247295172000533, 63.295771365886225 -0.42873192089363243 M59.860617889048555 4.3583755341402135 C61.204876081282244 2.4462727844013905, 62.71403227488977 0.6761394324439924, 63.75265395026062 -0.44950158432810805 M64.43277755715748 4.817069258902604 C65.40316014538845 3.4330754010527316, 67.48830787347462 1.7897418018574243, 69.08920172960326 -0.4122904970903266 M64.71076030499165 4.922289962403858 C66.30543711755352 3.381205867268815, 67.1897857339746 1.4420067575473627, 68.70259199379771 -0.05084756637330523 M70.58771891519704 4.750619644963616 C71.37363983052494 2.9703455431481034, 73.35685051388226 2.0961071751599487, 74.48601606585326 -0.828208914435072 M70.58843325032198 4.4322904609269145 C71.65190362957345 3.2235235177608175, 72.22835010160632 1.9469712868902052, 74.15600355988609 -0.2618844556981458 M75.96014689091358 4.560789897197099 C76.87921441200385 3.605583999488882, 77.99932370813067 1.7858582144678057, 79.28739587720213 0.31601914572077305 M75.69239818366009 4.662942064178348 C76.07732198395802 3.920847490343411, 77.40838368277302 2.841153876672555, 79.47034151339633 -0.11517612408367484 M81.39579160283498 4.125935513360647 C82.03558904540473 3.2556199540727566, 83.34207705689884 2.032684709007777, 85.40606346967772 0.07863199363480933 M80.88168783583856 4.422998829486069 C82.10242650070181 2.9668805527944397, 82.74842610428958 2.0079843576980587, 85.30395499141098 -0.3997224204519796 M86.2534517410271 5.177820624602916 C87.5597906673864 3.0633546421942226, 88.39960729184287 1.8870387551591874, 90.28655972084591 0.6525265729556926 M85.84275897797795 4.627455478819046 C87.45371734780876 2.52901187335013, 89.41977471405637 0.9323853105237693, 90.0832421713006 -0.08890752908409041 M91.7641181651139 5.296132157081442 C92.90653440514463 2.8446158794118652, 94.56962150890584 1.2927851658088008, 96.31440926328347 -0.4629539667824589 M91.21738649523903 4.711695627625735 C92.65383365042436 3.3885132122585255, 93.97812140291181 2.1850559058800054, 95.43273731934883 -0.34935963551610033 M96.64174484187305 4.572702456745072 C98.53057965012616 2.9819686076636875, 99.58403475584892 1.5111682715778871, 100.17668349490964 -0.2841646997605508 M96.75083194575771 4.7162940735134615 C97.90644647097919 3.5828594372621, 98.63367844014132 2.236725688518533, 100.41231447532306 0.2588908358129615 M102.00591578661452 5.207730511201974 C104.1951240524274 2.5246857248692507, 105.65992750875675 0.3575143165050997, 106.76494463960586 -0.3711926106477662 M101.85941100480022 4.675407553509307 C102.98761121814346 3.4378154031945387, 104.54747553162483 1.5316756667905054, 105.99789691878428 -0.03172438886226581 M106.85095392782641 4.203101409248546 C108.37641155772742 2.7436378147091807, 110.42223350592256 1.470965011393794, 111.74463243376829 -0.12232615680297415 M107.1321688448119 4.328359215449081 C108.31592229662087 3.6301020717189965, 108.89028453250391 2.8717359269315947, 111.50514143327617 0.2075890877949675 M111.98090162552782 5.247771592817491 C113.35152585444035 3.7225951371248183, 114.91676688163285 1.8589468645162235, 116.13886521905513 0.02780686398520943 M112.3129204869818 4.930232837896085 C113.50273047243114 3.5092956122219094, 114.73010981795683 1.7319652533003203, 116.39456626651662 0.37306887708157177 M-0.20861296487881575 3.2884879375449243 C-0.20861296487881575 3.2884879375449243, -0.20861296487881575 3.2884879375449243, -0.20861296487881575 3.2884879375449243 M-0.20861296487881575 3.2884879375449243 C-0.20861296487881575 3.2884879375449243, -0.20861296487881575 3.2884879375449243, -0.20861296487881575 3.2884879375449243 M5.718025342127387 3.8753739877850486 C4.293672963699714 1.941726880150926, 3.478955873103708 0.8934154863263211, 0.5648159441755807 -0.784859212816769 M6.302305185128082 3.247675474338673 C4.406684346963436 1.9341114971701878, 2.5516047506343598 0.301457763336606, 0.770781356164599 -0.7284570909133942 M11.640712111097317 3.6476553935106115 C10.282779207859775 2.1166806796907167, 9.609753040000331 1.7319711693302353, 7.301736206632557 -0.277701598301619 M11.630160086535405 3.0908777654650823 C10.265344503107869 1.8110141705295728, 8.87289979465699 0.4041310663127683, 7.011098305486592 -1.0598278151714204 M17.832847813686016 4.05932360646921 C16.56047263106247 1.6252110881643784, 15.96247906295596 0.5235664902028234, 12.576230174462237 -1.220124033524697 M17.89951644812863 3.620976785818588 C16.196618217496656 1.9229860418472637, 14.191655446876839 -0.04961156344502207, 13.191634300836288 -0.9475398317100903 M24.36490545220184 3.8427609575740016 C23.41535553867607 2.088695701158106, 22.102758859550672 2.2900867808003613, 18.713708020955355 -1.1978274104167306 M24.893684014477532 3.7127405675421423 C22.815245326727606 1.9165904061240235, 20.8483091624232 0.4794226710641176, 19.12578661793287 -0.9518727086931996 M30.122149579067887 2.733265366877007 C28.45298815025178 2.404442996706189, 27.33270430383092 0.023874201043605103, 25.383595866540887 -1.8823766929777421 M30.422039944100366 3.4410758496788767 C28.340843181522235 1.6547847924460737, 27.27968567647927 0.3230670122689556, 24.987707925744793 -1.08916686114204 M37.1752511857844 4.230091733179551 C34.81837209466615 2.099467514034439, 33.69261654687685 1.5467037193875957, 31.285763921469066 -0.8990287011563975 M36.504124552227836 3.5763633244871462 C34.82612648177355 1.5939310984336694, 32.9014490403332 -0.13509177157041008, 31.195620180127758 -1.027566987423246 M42.48362669774959 3.1836862632041614 C40.52525859822373 1.6861230371458296, 40.0882736778771 0.565564841980387, 37.809691953868324 -0.7398082026681244 M42.519868786704485 3.0255622049761755 C40.98874781268643 2.0759229097269554, 39.78692463198837 0.9697241891679245, 37.567579943430324 -0.9286640551342011 M48.823223185846444 3.47493174035248 C47.169101486195494 1.624627952394874, 45.57128013554424 1.1229632985322764, 42.76730888143101 -0.6538413657974318 M48.87226868037121 3.5499979451406434 C46.8055451914638 2.371656489035398, 45.6707219346694 0.7224097435585588, 43.20724546748133 -1.0748961309604212 M54.86395916527413 4.070563248104002 C54.05596997575476 2.7321317456070005, 52.203387619454936 1.6065395512433254, 50.16032481004694 -0.5527825928786767 M54.75911016830519 3.90463056040607 C53.767962642594384 2.7815173360168455, 52.65572571263757 1.8150000560206174, 50.07941624962003 -0.7899029600461944 M61.067195475825486 3.6497663306552948 C59.42180519200481 1.8167500292718, 58.33943344179441 1.0261858107999904, 54.98781067427871 -0.8233738027773314 M61.0355309496889 3.2714251912213896 C58.735505860756945 1.490244640955061, 56.51781417635464 0.15667504131750387, 55.168780155774854 -1.2455325025859736 M67.45350545275073 3.047831043057696 C64.93791082998172 2.5008305736995746, 63.13045885672232 0.3779961457154427, 62.40891402812657 -1.5142116591920727 M67.30627641787801 3.255088701719971 C65.45709831448613 2.135529277266349, 64.29045319405184 0.9440271471171828, 61.849076719926494 -1.220915785071405 M73.05180354483888 3.2542906878903892 C71.42075181007513 1.768748137960133, 69.91655347857123 0.7266319134171768, 68.30425639708798 -0.8135509688388339 M72.56644861495585 2.8864703040026516 C71.17743154305337 2.147687216491256, 70.2155733275634 1.0437195246854878, 68.17960519480792 -1.051736230308168 M79.20102712780056 2.8550323859850204 C78.07275111792543 2.4989736562398353, 77.16466001767213 1.2834331387639328, 73.32132580174819 -1.6970621580005845 M78.853413103547 3.5784676422892385 C77.48717173825935 1.8642443958668222, 75.39735327955356 0.3484527022879838, 74.25142857359477 -1.3061623518568692 M85.77978409950732 4.26198219589108 C83.50528978197305 1.614159189353454, 81.44715492568304 -0.07747453887166911, 80.71670293260408 -1.0184100585132272 M85.890058945195 3.839058385530126 C84.49929433194346 2.1637487504334065, 83.16232629284643 1.0234578482405214, 80.29203926852946 -1.1581386042367103 M90.95161696386116 3.765433878448479 C89.29767212350363 1.829179580362958, 87.95366364278294 0.6728175603368073, 86.0887466543493 -1.3366612377417155 M91.47582540109346 3.530907518832632 C89.2634037333601 1.690741267212872, 87.56638752952173 -0.11346721086815315, 85.6949801114061 -1.0414556486838378 M97.95887639986232 3.0148376449585435 C96.3800776272248 2.2189464731332444, 95.97994064025819 0.9540284995700095, 93.02775686473402 -0.9456272031347284 M97.68972544272312 3.5289855782051105 C95.82416763666252 2.342221229397511, 94.73160556349382 1.0165938925584095, 92.22857071433032 -0.7602933398148997 M103.48476481459276 4.203673651001077 C102.3376613492646 2.0307405948192105, 99.81474367365783 0.22221955263861215, 98.58676018959017 -1.4669645178624586 M104.12618431652137 3.7644512470594007 C102.83495267284236 2.3805533561919923, 100.92555276046987 1.5221517869592631, 98.99060546436452 -1.0517058848430554 M110.26434008388435 3.4407540834058894 C107.65500233594562 1.0207193340894731, 105.31583271173358 0.12787660647309607, 103.79251372349967 -1.1788727138451036 M109.71073078455058 3.3662659431823414 C108.70116324444629 2.5003774740494245, 107.46828596022984 1.2733693137197764, 104.57404476419609 -0.9940072566064739 M116.59551929298733 3.0215252472243024 C114.63981720039678 1.5565616414006276, 111.95781636931865 0.5590968783301888, 110.85650877046277 -0.24479220478132224 M116.26568836516645 3.952808405707286 C114.03866849966818 2.280649382683252, 112.44315153056631 0.7675961508242987, 110.98270627280576 -0.6651566065967813" stroke="#ced4da" stroke-width="0.5" fill="none"&gt;&lt;/path&gt;&lt;path d="M-0.23434065841138363 0.142077824100852 C28.715953912368914 2.4050549429488974, 59.086424035517375 1.2947160762382346, 118.33590651761847 -0.6271101627498865 M0.23701665829867125 -0.9688872648403049 C40.54571875747384 0.8165276117050515, 79.95095844667587 -0.5723418228900565, 117.80025233134734 0.809664343483746 M117.30181149804058 0.30964840602822036 C117.04848178797722 1.0714655519263452, 117.26447067737034 1.7193583378838562, 117.24525436103481 3.774069461958904 M117.38778986870251 -0.04196367157969905 C117.44389687467893 1.1169644084395502, 117.21516043451348 2.2824108995181795, 117.257237656937 3.451698091471317 M116.77217633735495 4.023190758161206 C76.14580382012839 4.315442243970393, 36.8684398630825 3.6790146626601214, -0.7268522288650274 1.5858928775116397 M116.48254762396323 3.355566626384814 C80.35674389053537 2.8080842616731956, 42.32692843072813 2.2880636277372672, -0.3794170515611768 2.629336511686404 M0.04055979771483731 3.652375844574424 C0.26388576688719817 2.4068530792622296, -0.2596626308548632 1.192243502403786, 0.09058514252828276 -0.04740227727084212 M-0.0890632265700454 3.4211925880837044 C0.11493123267303818 2.6076078582227575, 0.09656059686224477 1.8346913488649563, -0.030040646842850433 -0.049094910607272294" stroke="transparent" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(167.4605248514622 137.39008378937473) rotate(0 58.68576547639498 1.7349162106253004)"&gt;&lt;path d="M0 0 C0 0, 0 0, 0 0 M0 0 C0 0, 0 0, 0 0 M0.6636327809792811 5.437912976123173 C2.2004859760752415 4.226952148857306, 3.7809761552800425 2.588543634480981, 4.753457107538826 0.26739244589928923 M1.2526921490345948 4.722668374176849 C2.893076157371256 3.315675006092145, 4.0593697646852505 1.1757016715799575, 4.942586639201736 0.13901534628469886 M6.888673206706834 3.923559729066506 C7.8167811755684395 3.665116062906882, 9.193282153746134 1.761861752315569, 10.254336575929951 -0.02053238776626576 M6.783235177220148 4.72443837724702 C7.780195310455909 3.2706019642173962, 8.591260857205842 2.4053002574624074, 10.690039552793783 -0.0021361937664686192 M11.864336236439485 4.693022393104191 C12.832123101116894 3.466765492856924, 14.293252541030547 1.4697871681264814, 15.768112818248053 0.8118149884906583 M11.492984622124421 4.77924779070964 C12.474223981093884 3.3169147608756475, 14.08396577532556 2.4076180127643365, 15.793423126268614 0.3656934490357509 M16.953734801205123 4.2103986534938125 C18.215125911337292 3.2035176473623537, 19.171040867078883 2.9002483596160022, 21.456140953021013 -0.4228349275723574 M17.07446930832999 4.385792078142604 C18.593328624419634 2.5674006463956744, 20.28613083027282 1.1920316374214188, 21.228957590458805 0.009070832019251907 M21.714300403569613 4.411944828884037 C23.279297800514495 3.4413857570461186, 25.32263367856215 1.9551547888790175, 25.692844670314702 0.04165699519335647 M22.557017100707416 4.989632367764892 C23.926326600236063 3.2925432065914566, 24.954926295529926 1.993984194845566, 26.350287216484904 0.25080317715301526 M28.415005349560833 4.099649229532447 C28.41402131952634 3.663007678639202, 29.384683365561937 2.0508462146115596, 31.382938463657 0.11841187954537913 M27.970852136339253 4.179893810654398 C29.151002846331227 3.1444238808792417, 29.981890749989084 1.645116514312564, 31.652577547679492 0.0666367292649333 M33.05664051674466 4.210158799087713 C34.178850818027534 4.019508889309605, 34.20989773831537 2.899275209830106, 36.38170539811377 0.13645948163238375 M33.21424759259607 4.661317222530107 C34.4486520009651 3.2183493860011745, 35.82136545288414 1.4339502470899435, 36.73966816766311 -0.042691602176008536 M38.289037586148964 4.00835247280741 C39.145836703035805 3.4663063561696146, 40.86230015071601 2.699425754378073, 42.212219342212904 -0.36568845078805623 M38.717622919060425 4.250957441969904 C40.17001742219369 3.121345222071163, 41.00214852858819 1.2375657455553983, 42.48816993226834 0.04524411515807569 M43.23134681033963 5.156267819340211 C44.37349218722228 3.939623670560652, 45.161392539645064 2.882708366472267, 48.08562273602027 -0.37111153656726614 M43.71069660560289 4.850234972192776 C45.161177248017175 3.089904194708075, 46.81635661836112 1.4158002326649208, 47.21461773761573 0.34661047823617325 M49.189259245344516 4.3066743989437 C49.701047107349076 3.2945671903931624, 50.85297011705536 2.072741990438038, 53.618348823063734 -0.5909074029476635 M49.286583414306016 4.2416343392766205 C50.208256533528306 2.8854043338700746, 51.62258134818292 1.0755015901270104, 53.039567752037854 0.06801310688617906 M54.27528451872952 4.3720004052665455 C54.751167317820595 3.398595663233414, 55.520224550197646 2.664658105859668, 57.65239941840918 0.004734956219419217 M54.26894714754037 4.460755733940415 C55.19112882291247 3.8536901626917057, 55.97529565756454 2.6122107708420255, 57.854750184155264 0.27854828557707306 M60.3259730615225 4.71790602356632 C61.5987501786983 2.300839209151037, 62.10364199285903 1.0048224632885137, 63.37256714058448 -0.21989146130844262 M60.076097879888515 4.193013369762047 C61.16541959394812 3.067354877079675, 62.727114079099 1.3043253590358614, 63.487166514800535 -0.3934986783632567 M65.12219387628933 5.2322100724917 C66.30239659451297 2.799977599565531, 67.37904640648433 0.761502337955084, 68.72671317965552 0.3029149756174221 M64.78814472622102 4.745687124960506 C66.15484341978211 3.376684759111635, 67.0732096483159 2.080054104864959, 68.86624226509421 -0.08860181296582692 M70.50635676905789 3.718463362300271 C71.86991218744134 3.6291806603476133, 72.56147411136503 2.4145581894636736, 73.94298952915015 -0.46216211024539006 M70.41109307362044 4.24462090061369 C71.95683242216171 2.7967594625068632, 73.30082420069581 1.3224896128209231, 74.58792422836888 -0.419296123402728 M75.80269011739406 4.764875336640687 C76.29453464410345 2.747531740019447, 77.81159742447727 2.315747127254188, 79.15011675413427 0.11546953725176368 M75.74399734945733 4.806274262440563 C76.79807116512586 2.9760640011653474, 78.32234134386346 1.465533444010473, 79.68183323824508 0.4018740497949949 M81.13446310232571 3.937887430770543 C81.99490079271949 3.10912057465615, 83.84135676676516 1.0190819947702123, 84.69460622313575 0.12588416586648343 M81.24305825331426 4.3344207990190355 C82.58066978530059 2.5098003610886312, 84.29079976208823 0.9364831235448969, 84.97536140055539 -0.3409466110447454 M85.64426347641871 5.0510997133384965 C86.75647735203347 4.109874768157185, 88.08740020747075 2.1795883581900215, 89.96103787622044 0.18756465674742606 M86.0887755250618 4.892199763568076 C87.19385989368864 3.4489991670746076, 88.35093970094168 1.6755024321737275, 89.86082486142034 0.0051272410470205865 M91.40128832791086 5.137060529822188 C92.68020500257708 3.1654529074144793, 93.43078495070915 2.0942183646073267, 96.15902809932874 0.3328807742139024 M91.21325127250601 5.300359081979545 C92.012548947557 3.8400492494909555, 93.07103963377553 2.638415967344628, 95.6576864452944 -0.23010111699908287 M96.2712791877383 4.225111718210144 C98.26520220059713 2.9963325739213125, 99.09012389732493 1.3548952590822787, 101.19477928028905 0.6174913497294242 M96.55131226084694 4.301583745530559 C97.69375421173844 3.4627999310720625, 98.9226360300207 2.6034795421865886, 100.58908139243255 -0.22525337202223594 M101.20668477295885 5.327470338715669 C103.36456700691004 2.937246299648309, 105.4049611249472 1.7780722332996386, 106.74990180115161 0.09595236441841837 M101.90929637761774 4.6922388278399225 C103.31821544142318 3.277841446186783, 104.5123895625816 1.8433431926146149, 106.09821368617882 -0.24104548689187916 M107.4049774857278 4.274264866880611 C108.71343890640962 2.788070035169462, 109.31707772496281 2.088468924340156, 111.28351870071508 0.25666563182274305 M107.53700306462524 4.6809838255154945 C108.0860312841616 3.12222539537236, 109.12650269543003 2.3610878484570605, 111.37902958909415 0.2627585777747114 M112.2427111712832 4.656354751072115 C113.81741926793998 3.599521478843223, 114.18645041046885 2.1053450067537076, 116.68359045237156 0.059544280967860186 M112.47804475730175 4.643885451655126 C113.14877852810976 3.6926663947829215, 114.2196652425751 2.4064126642711727, 116.38533321795839 0.4845819225117552 M-0.20861296487881575 3.2884879375449243 C-0.20861296487881575 3.2884879375449243, -0.20861296487881575 3.2884879375449243, -0.20861296487881575 3.2884879375449243 M-0.20861296487881575 3.2884879375449243 C-0.20861296487881575 3.2884879375449243, -0.20861296487881575 3.2884879375449243, -0.20861296487881575 3.2884879375449243 M6.038753206035201 3.1300926818758916 C4.723818621693496 2.1285291867907636, 3.13791404806339 0.7830587097280205, 0.25563286219106074 -1.3614096875796855 M6.510433165062298 3.393394878857277 C4.13005972867639 2.2360394515574655, 2.4373793680172717 0.3336049185055383, 0.8839488849727158 -0.8186739943018944 M12.182263897758466 2.8939493137891605 C11.164632495092897 1.887166621124267, 9.701507768673842 1.8858835342337947, 7.694971991401616 -1.1931350110919174 M11.902747525953245 3.159165903973879 C10.20691216110827 1.9279064921942974, 9.39630670044131 0.5169822903619603, 7.18241497387782 -0.6329279022180436 M18.298998231609943 3.406147212364253 C16.663144277764403 2.210227778792071, 15.85473329376894 1.0317988228814723, 12.871631023446955 -0.9257426921865394 M17.95483518962901 3.411947750548374 C16.51043599035948 2.0023286122392463, 14.106608229645348 0.15343760856561472, 12.660253699609004 -1.1205416739108462 M24.942657222817417 3.0984803421250495 C23.001491422954494 3.2358929608261473, 21.973631237132544 1.502402049608751, 19.784031145483286 -1.2302833226491294 M24.970303977825445 3.434991334594316 C22.701928544994644 2.0311390018294455, 21.22492413130367 0.5617980190221387, 19.418983300564843 -0.6548053808957868 M30.33339296116418 3.462562536919999 C28.673046880117205 2.606973976570341, 27.57646978371944 1.01041260236581, 25.027391681651093 -1.4145855081947603 M30.290241585838782 3.002524204697969 C28.24634011332845 1.9679953086904836, 26.46690308665172 0.21954108720015225, 25.014776020653983 -1.385157499576927 M36.37713034440046 3.3389572893004233 C35.34497515543884 2.4242901434088577, 34.06926468566778 0.9055546823716396, 31.05246174845061 -1.0282224750220972 M36.70015512577472 3.5681790062426892 C35.093988115850074 2.327015776613409, 34.09976614687362 0.7231080653761941, 31.649008112607138 -1.0386782751959014 M42.54837356039951 2.579493237376753 C41.700813259380475 2.4450540523519404, 40.162638775913145 0.8968843998136137, 37.96443458220428 -1.158395797892505 M42.48194969154591 3.232919805307482 C41.163669496073105 1.8943144784125137, 39.921438145895706 1.143648522031354, 37.940435365331076 -1.024593484069252 M49.171947017619246 3.1230152453869264 C47.07898134002786 1.4929833755779627, 45.235620449808906 0.505715224140042, 43.68473932102242 -1.5690918489526822 M48.89500356528894 3.38562780917768 C47.20569896906054 2.293098779975278, 45.96714488619246 0.7539871622378518, 43.67167303848 -0.8916164542250201 M55.0925247061839 3.757788503474142 C52.90777734311879 1.6673511434334716, 51.75262614980788 -0.3604394416166038, 49.96046866287687 -0.4088626391747965 M54.86109152653531 3.5467736862381507 C54.0346190894434 2.4025511021072354, 52.39318357643779 1.471350594739946, 49.593309715317794 -0.9384202632149681 M60.18001251591128 3.041108311947066 C58.755724954149606 2.2985288661187715, 57.30069067397136 0.1818664858943746, 55.12986288663623 -1.0210310928783826 M60.81374822214936 3.397384130118046 C58.865076481164905 1.7644968491946185, 57.10797838209198 -0.036615627871190104, 55.454080128964684 -1.3305266562643907 M66.54336268830161 2.9464483196070352 C65.52397321760216 2.13805774372614, 63.76885572567046 0.019868619414590394, 61.25607345255074 -1.6816352800121181 M66.87212334236179 3.5519804580757333 C65.28837370785398 1.977155273453115, 63.23428011052899 -0.10435818156351012, 61.609315610276354 -0.9294397067399345 M73.08496643074318 3.259956174941399 C71.56893728456264 1.50095402520942, 70.26520973868138 1.3593546608890896, 68.05576587875646 -0.4993613500129217 M73.04555899596303 3.02367190228928 C71.67287681224579 2.144145835278109, 70.71462234912832 1.7368549659512011, 68.22245334971038 -0.9284509337488344 M79.5299714681488 3.9272853360573072 C76.89786613779968 2.552731377637286, 75.30481919236661 0.5674010000250406, 73.43743683292284 -1.1014763260008067 M79.43475523295632 3.2443963592468084 C78.12302170314885 2.411650468483611, 77.15245725231453 1.5294228406179258, 73.8969472147686 -0.9758342494470302 M86.10065816168785 3.573837711728748 C83.8670605537796 2.4605049722698684, 81.77089279621147 0.08484396370151315, 80.85779117096259 -1.5480562554746307 M85.32651272125653 3.599282651880395 C83.93709452737835 2.651293425811132, 82.25896523907227 1.3754990242531546, 80.35951983711725 -0.903612303508389 M91.21697506916301 2.866534904387467 C90.39033630334995 2.700039490653049, 89.14235548366706 0.608007862275713, 86.54027657743069 -0.7357441755783627 M91.43327674910019 3.4649271514701327 C89.17151436063847 1.34694036322608, 87.04432828565803 -0.5007749393561702, 85.61119720457579 -1.4261512546175041 M97.56277677582457 4.089885719084785 C95.25614476593243 1.2987704096684802, 93.90450856573715 0.3826807647656454, 91.83232185632738 -0.5889180000106826 M97.64690208484299 3.502677347149374 C95.63580196991317 1.8707505467866168, 94.33340239070559 0.33113339958950927, 92.35032822612489 -1.303441504266021 M103.95339459500448 4.231803905150515 C102.99990325244588 3.100824167613683, 101.39221605206542 1.9855922357296154, 99.13470753973455 -0.3004256981917798 M103.70434327179738 4.075381375454959 C101.91804560380528 2.1266179395915157, 100.39896224975898 0.6194764690564218, 99.08442408570193 -0.6330353065421019 M110.24664030878459 3.054910243164456 C108.29821828541148 1.9383571245340734, 106.59507396268434 0.5993288313569899, 104.91256962848414 -1.560283208291859 M109.95334586890068 3.6390026072987243 C108.13350928557453 2.2655465992861474, 106.85884151434145 0.7884743144311759, 104.04584948286931 -1.4084321811589153 M115.95380990866516 3.4678346319279383 C114.03705975899312 1.9378389784554808, 112.44010205978047 -0.06739445709964764, 110.2064033328264 -0.4843723502595709 M116.36813769138362 3.784234234939025 C114.92119691659654 2.4273365895296304, 113.28339144448272 1.7972876661618962, 110.85751387348276 -0.7274643264830843" stroke="#ced4da" stroke-width="0.5" fill="none"&gt;&lt;/path&gt;&lt;path d="M-0.48375444673001766 0.6891018953174353 C39.10751305899041 -2.0577524826356917, 78.77392437879776 -1.4275613353082686, 117.16247959148245 0.8817383255809546 M0.2766791684553027 -0.4198594940826297 C26.72960303917632 0.48619844362871045, 52.17863115031482 -0.011690270446378048, 116.42956118092047 0.17714208830147982 M117.33188263682342 0.0957035415666283 C117.32522756774468 1.1666784346092876, 117.14478912220966 2.523287032747493, 117.07989295709652 3.6162418013201902 M117.46280266445187 0.13056837023972614 C117.4218588953406 0.8822536336664173, 117.40823366013377 2.0722114958595848, 117.34782981415454 3.3959186371642907 M116.81081415426092 5.10925062649693 C73.2879641513797 4.294939576213515, 30.23393472039787 2.5209902511288287, -0.5659629013389349 3.8746218895241213 M118.15323926314818 3.3116817231922937 C84.45962233541337 4.064319416637466, 52.18538845668979 3.6291881946278073, 0.2337651653215289 3.5481276567726923 M-0.2629101199759744 3.794495027187076 C-0.04156435476283983 2.3380613986824215, 0.31137420504295416 2.0230080490371862, 0.1357712119890644 -0.007303334008396556 M0.12877287445269817 3.607455699116977 C-0.09660054197418294 2.41367973642349, 0.07276723417762318 1.3097636034644708, -0.14982422125015224 0.006731646528611218" stroke="transparent" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(169.48678477545945 146.14008378937473) rotate(0 58.68576547639498 1.7349162106253004)"&gt;&lt;path d="M0 0 C0 0, 0 0, 0 0 M0 0 C0 0, 0 0, 0 0 M1.600209353412395 4.925640894971568 C2.4996527121755583 4.456277959056393, 2.967830380641111 2.682846113139521, 4.894300478468186 0.9177505770546608 M0.884964751466071 5.153460343960454 C2.1698546044026967 3.69367802070304, 2.7244144109636297 2.711799287332657, 4.7659233788535955 0.22677508116115272 M6.123532748137904 4.628484148216262 C8.354590916548995 3.4514149570118997, 8.942760440227394 1.035575179884363, 10.644052286584806 -0.4534633672714469 M6.924411396318418 4.6417104698352665 C7.415978903445314 3.501253833575854, 8.491571892749027 2.4744877060272583, 10.662448480584604 0.14921719693464647 M11.519903444744486 4.577734483819957 C13.060204647208685 3.30187874901186, 13.831667674366798 2.3868677526851956, 16.103307695410628 0.2037305031833222 M11.606128842349934 4.563902296446766 C12.737616624340363 3.5638215369738697, 14.427876080905238 1.9877944929397395, 15.65718615595572 0.07089947535889052 M17.07495634691628 4.495631676883987 C18.15111969612095 3.3530959705670806, 19.931101917383312 2.1274328921517647, 20.906334421129788 0.2660641510203008 M17.250349771565073 4.3116328987009735 C18.082233350705597 3.4421220355014186, 19.356407939698435 2.0721437660035744, 21.338240180721396 -0.011014602410095609 M21.903410554875407 4.548058510900767 C23.708649085582916 3.843049276963196, 24.998852000476425 1.243150709398071, 25.997734376464397 0.5726625472319067 M22.481098093756263 5.103247828644992 C23.835724825828304 3.035420426365256, 25.58461950574584 1.49314690370492, 26.206880558424057 0.06801192192479394 M27.628791597305987 3.9756141359716315 C29.212425851571183 2.8803806503776643, 29.619470787514757 1.592231305563605, 32.1121659025986 -0.14388297058052968 M27.70903617842794 4.461789473684844 C29.136954840927775 2.834260777937632, 30.09726597161574 1.5812750391795651, 32.06039075231815 -0.3821621579804237 M32.36620919943015 5.200040710589001 C34.22412641093575 3.1248854623226228, 35.15733871643511 1.7149842297248044, 36.7571215372545 0.648911943575414 M32.817367622872545 4.868298698367635 C34.33153714180399 3.268720202958304, 35.504008892248606 1.6150841530725868, 36.577970453446106 0.04694542867136209 M38.202079514932024 4.055296759854199 C39.96171545447436 3.4371321814791522, 41.4957176056074 1.205099804202856, 42.29265024661623 0.11449800230106177 M38.444684484094516 4.6397632025993785 C40.130253254128306 2.6491475582245667, 41.063499040373976 1.4232199924949338, 42.70358281256237 -0.3596112119638377 M43.97690289403373 4.693983817259651 C45.23363788083089 2.943335015886341, 46.653011927149585 2.009448658488687, 46.914135193405926 0.7537189265888379 M43.67087004688629 4.782419163427186 C44.604316200564426 3.815750586341456, 45.623378084249424 2.291582217500578, 47.63185720820937 0.11309550341108904 M49.16498611541939 3.8184197829066897 C50.64657164281581 2.4403816328467243, 51.917143070904395 1.837816577819968, 52.7320159688077 0.0358848040400751 M49.099946055752305 4.069699593257078 C50.7641190199526 2.4248658076250478, 51.97558798049119 0.7713000383850823, 53.39093647864154 -0.41218500654783763 M53.85722015431113 4.387065565287731 C54.952348846515896 3.0514489634672155, 56.287268948177115 1.7590986957252932, 57.95456636054368 -0.09480126935280919 M53.945975482985 4.695535981636159 C55.54075642021339 3.2643174621740165, 56.49719307449849 2.002531628890043, 58.22837968990133 0.3484201539564871 M60.24080241439308 4.645598439504101 C60.274578298361206 2.752303839085699, 61.427027956458815 1.9323072591223405, 63.76761658479799 0.01183016901151035 M59.71590976058881 4.352306653470441 C61.591244526960445 2.8582941811977594, 62.83032740102157 0.5958345727878738, 63.594009367743176 0.017855716080076467 M65.38201449588736 4.633338689048225 C65.44942606869968 3.2900244590370247, 65.91284721284035 2.7715229803544377, 68.91733105429276 0.018341590125454776 M64.89549154835616 4.884138410807038 C65.71208949650332 3.641495164593852, 66.60598040553066 2.6373584976614524, 68.52581426570951 -0.08856011187996143 M69.9059444274781 4.8143229640214384 C71.94894372402597 3.3249224473334036, 72.8711630386861 1.186872726275202, 74.18993061021212 0.030761831590371047 M70.43210196579152 4.364854891051972 C71.33507241408704 3.449098518360727, 72.2145093944108 2.6553071875734577, 74.23279659705477 -0.18518728626759184 M75.57926443438743 4.183730922916145 C75.8284375202324 3.4818402294861497, 77.65877615314707 2.493359771875505, 79.39447029027818 0.5050127393986514 M75.6206633601873 4.5512782087523345 C76.63707549791569 3.225755614063506, 77.97476618650597 2.137373413665082, 79.6808748028214 0.337237713714265 M80.78995317029944 3.6989585908336906 C83.04275991102023 2.478723941764963, 84.02938960796024 0.7102348606103679, 85.44256156067506 0.2779825261516463 M81.18648653854794 4.16117731381416 C82.11269008280131 3.181331226568118, 83.29599513562587 1.9138363135527499, 84.97573078376384 -0.5104497557783532 M86.5300734854363 4.386045001266483 C87.6134446301795 3.6969214517081457, 87.71010486125863 2.22273607931836, 90.1311500841249 0.0739524128417628 M86.37117353566589 4.526569633284542 C87.33788147132714 3.292293346481812, 87.97350677711567 2.0108767122282525, 89.94871266842449 -0.16254086144835142 M91.2429423344889 5.617612057821529 C92.17594571106946 3.3282348097590164, 94.00999803784863 1.4021832535391994, 96.31414284337357 -0.7233094372982467 M91.40624088664626 4.802023245617451 C92.61334169360515 3.1288412926863653, 94.08205321960232 1.7274729894301026, 95.75116095216059 -0.3221801792932979 M96.36867016465902 4.98495865847425 C97.93131117378991 3.002896295768975, 99.08384639685221 1.8067984562789157, 101.22566145145798 -0.2692241551701969 M96.44514219197944 4.566189442216171 C97.80307253708999 3.6102463171145533, 99.13552780442272 2.0605990411503643, 100.38291672970632 0.2003121704447025 M102.09793681773344 4.954923653042373 C102.67273042491709 4.143242270980425, 104.47352396520175 2.8666546283954575, 106.74179910792914 -0.42915217709937525 M101.4627053068577 5.194662169877868 C102.68543910674988 3.7611716153594172, 103.88395548821825 2.1513159335920458, 106.40480125661884 -0.352432776486738 M107.08240798768057 4.722024628914431 C108.03268869736583 2.899017961483962, 109.769056959808 2.034212281897078, 111.52942040790238 -0.22202673026224173 M107.48912694631545 4.252876893359707 C108.29302192799366 2.8823694334387504, 109.89475711485191 1.8875036342154852, 111.53551335385434 -0.10943268279408613 M112.09140590444096 5.399819009012765 C113.69012623719256 3.0504545325059396, 114.8504622991196 1.9039589515862847, 115.95920708961638 0.15024072570279118 M112.07893660502397 4.694266851532495 C113.62135276790966 3.216944483167724, 114.8247503613836 2.002361974475887, 116.38424473116028 0.14223569105863448 M-0.20861296487881575 3.2884879375449243 C-0.20861296487881575 3.2884879375449243, -0.20861296487881575 3.2884879375449243, -0.20861296487881575 3.2884879375449243 M-0.20861296487881575 3.2884879375449243 C-0.20861296487881575 3.2884879375449243, -0.20861296487881575 3.2884879375449243, -0.20861296487881575 3.2884879375449243 M5.769318972466593 3.2702040915623973 C4.184777922353994 1.396157826059134, 2.189807550405893 -0.02138521755241296, 0.5872627443851625 -0.8553662960911931 M6.032621169447978 3.2305545800146382 C4.690165310993749 1.7985076575885242, 2.5465972004025397 0.5482688136237224, 1.1299984376629535 -1.360716083834955 M11.570852246162039 3.4421151026631907 C10.02046709582907 1.6947003664883418, 9.395648274614883 1.0578656749089206, 6.891864613887372 -0.7893550011218486 M11.836068836346756 2.91213990920365 C10.10901790033319 1.983947643612705, 8.170510535380371 0.08359337511181311, 7.4520717227612465 -1.032002550352454 M18.21937733775157 3.025979774833199 C15.97550569432731 1.6562699927552875, 13.824140877206172 -0.07223045443585918, 13.196933574574926 -0.6462971939361146 M18.225177875935692 3.723758460550111 C16.491388986907143 1.4632173624042502, 14.297344688625898 -0.5106385792374468, 13.00213459285062 -1.387944007956465 M24.04803766052681 3.317589012143663 C23.642538655401196 2.1875608902896904, 21.40878421521937 1.360553725463256, 19.028720137126776 -0.6479676998202287 M24.384548652996074 3.5397064960855014 C22.85887406154602 2.506964644668715, 21.33917742461837 1.017650538708021, 19.604198078880117 -1.252241324212455 M30.449796497103932 2.7776458847397443 C28.791380565245685 1.2101032389266233, 26.345564151176525 0.22731805420559836, 24.88209459336332 -1.0503342471765436 M29.989758164881902 3.067311195150527 C28.564001199204174 1.410206053770608, 26.452608627412815 -0.007600197622174565, 24.911522601981154 -1.5309302557474085 M36.4625184424988 3.507840594157141 C34.5563392648646 1.713086939149577, 32.11460135782348 -0.3032975366332665, 31.40478481955043 -1.24125741571935 M36.69174015944107 3.3693391142613556 C35.55565107391157 2.8366381304515156, 34.07285692372891 1.8496830762740162, 31.394329019376624 -1.2531876054201472 M41.74073103235731 3.6670221124743865 C40.75125567842124 1.7044932090319929, 38.38740745931107 0.2806760719910003, 37.410938689694454 -0.9586231393209041 M42.39415760028804 3.2208826966551882 C40.904829251348175 2.147809755927324, 39.97401301629805 1.1639203693127738, 37.54474100351771 -0.5871325770793794 M48.42058023338192 3.696973325462608 C47.20743296118572 2.638254160716362, 46.53880227384477 1.334882841310642, 43.03791928041646 -1.419271939357729 M48.68319279717267 3.268040068924023 C47.696808646443124 2.5318162612500106, 46.308535986532945 1.8244455477402453, 43.71539467514412 -0.904493040375398 M55.19168068448358 3.5160170072005648 C52.70738902822011 2.5763432604514342, 50.36619804175621 0.23877171357205373, 50.33447568320879 -1.1159841971034183 M54.98066586724759 4.019774208661686 C53.65448215802237 2.571367959148274, 52.58439588334864 0.9931736485563009, 49.80491805916862 -0.7920542402220889 M60.51267713473868 3.0949237294975704 C59.29056849748488 1.6528414444584443, 56.73898642125805 -0.542769631205199, 55.75998387128738 -0.8509809972978644 M60.868952952909666 3.2849426608547603 C59.77949116774831 2.5196414413665087, 58.564689046673614 1.3714526770553275, 55.450488307901374 -1.1113212032969557 M66.55434433541309 3.8415360376393357 C65.47262231273247 2.200696441190053, 63.007102645551214 -0.575060617394751, 61.23570687716809 -0.5965955759053698 M67.1598764738818 3.5833920601444254 C65.97158156869708 2.2963652482999777, 64.27829071894915 1.0028055170391457, 61.987902450440274 -1.064078154333832 M72.90552883252963 3.227652808503845 C71.48233256859272 2.4956018953974444, 71.5953711423136 1.3735826269863889, 68.55430800018173 -1.372618030754827 M72.66924455987751 2.9495276180483785 C71.17319697086066 1.7386277578385914, 70.17271894139819 0.7228957813578295, 68.12521841644582 -0.7559145470130972 M79.70918518665997 2.7526915884638465 C78.20308998082545 1.5476176120308165, 76.1717796582987 0.3964972930497131, 73.98986966597602 -1.8161920163528285 M79.02629620984948 3.4188919688171024 C77.96795000450798 2.5260890498017368, 76.82086420246199 1.298975343356351, 74.11551174252979 -1.047514688990009 M85.49206475534587 3.761520178496477 C84.05461121671922 1.9207630736317183, 81.42422105136544 1.1088957250737872, 79.67961692951663 -0.9968650933429033 M85.51750969549752 3.514226238635939 C84.66352026955705 2.292920472446747, 83.52845068743653 1.6176655507167197, 80.32406088148286 -1.042859730923492 M90.82243858978676 3.796138933111601 C90.7756099835855 3.0097791757950727, 88.82075325706427 1.9811235184151597, 86.52960565119507 -0.6320244950082967 M91.42083083686943 3.2882978604609745 C89.97012087253111 2.219387551238216, 88.71340311853322 0.8182630498519998, 85.83919857215594 -0.9721367934093692 M98.1821165974985 3.2424177413449162 C95.94435540603256 2.9065816063447434, 95.610040228169 0.8443813930405555, 92.8127590197772 -0.9066011135633121 M97.5949082255631 3.2070960470583763 C96.17131722752133 2.5767519469334967, 94.92409057719877 1.4855227181782171, 92.09823551552186 -1.2706750230564747 M104.4603619765787 4.054279874010604 C102.59275934310172 2.1196036052910467, 100.75281098013083 1.0159369838302492, 99.23757851461053 -0.415597459443766 M104.30393944688313 3.582348939847793 C102.0782023302114 2.2962376561768787, 100.36874547619048 0.9100141827075392, 98.90496890626021 -0.6140021378201579 M109.32114495637481 3.868754070672848 C108.43352454895503 2.9113453217515817, 107.37968253777 1.6594092754330125, 104.01539764629263 -1.3490355743354856 M109.90523732050907 3.2912631958738783 C108.7348073136849 2.556444434534085, 107.41753450708173 1.1693861064687556, 104.16724867342558 -1.048104840030774 M115.87039653815273 3.4210266266173432 C114.559126491048 2.364249533864955, 112.67933300867229 0.5771727986471796, 111.22763569733937 -1.3769049442027494 M116.18679614116382 3.746263574777798 C114.45999141945708 2.2125451402883427, 113.54687817371676 1.407346669528906, 110.98454372111586 -0.8696892294221357" stroke="#ced4da" stroke-width="0.5" fill="none"&gt;&lt;/path&gt;&lt;path d="M0.6891018953174353 -0.3624111320823431 C29.123832458821493 0.11797099674185574, 60.82561281175426 0.07484363878210842, 118.25326927837091 -1.609285881742835 M-0.4198594940826297 0.9623611373826861 C24.554797365023767 0.31940477088411257, 48.47724341632987 -1.266712347712918, 117.54867304109143 0.8257444007322192 M117.46723449435657 -0.029230690336674026 C117.17989395992824 0.6605536434174992, 117.20903336235858 1.4488075628849448, 117.51794033285954 3.442342157173142 M117.50209932302968 0.06218988222819888 C117.24438110393012 0.9708949766210452, 117.42087950382557 1.820959520563945, 117.29761716870364 3.6062395629544333 M119.01094915803628 3.826017866812606 C73.09245945795222 4.882679737431558, 25.61179591448436 2.57480857454017, 0.40478946827352047 3.062255452357192 M117.21338025473165 3.377486448705156 C71.65823756449531 4.840318226265352, 25.14225369536493 4.621100598024767, 0.07829523552209139 2.859146308124025 M0.3246626059364753 3.413369760381883 C-0.3040527352174651 2.8743416548712752, 0.2099457940590694 1.8216703026068612, -0.007303334008396556 -0.004261920248263329 M0.13762327786637604 3.341924582688068 C-0.035764013952607664 2.783415598348192, -0.10299234078470978 1.8642546790170842, 0.006731646528611218 0.16699495353629376" stroke="transparent" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(168.4605248514622 154.89008378937473) rotate(0 58.68576547639498 1.7349162106253004)"&gt;&lt;path d="M0 0 C0 0, 0 0, 0 0 M0 0 C0 0, 0 0, 0 0 M0.5412350933206136 4.7791320045811725 C2.3898014437187407 2.966051119073953, 3.464688773789443 1.825541752612348, 4.706651329765565 0.5056935528141671 M1.2181881390650624 4.779603134874035 C2.43581630537634 3.009125923208155, 3.836426814817765 1.4577501424243429, 4.701629657535351 0.5045533281599396 M6.809685784493876 4.721702087354674 C7.879022374932515 2.603081614200287, 8.52316913774228 1.644407387826863, 10.702317787845564 0.48054799075124266 M6.834508086377126 4.71903629373835 C7.721436709732617 3.3736898937778896, 9.309608019076641 2.0287713165991708, 10.645874619343264 -0.08763878381925208 M11.234539490438836 4.574296195846554 C13.115829181364996 3.526776647272496, 14.458151176432029 0.8230546889325857, 15.86994512202539 -0.04104937567142941 M11.660551837798046 4.8163155722846644 C12.487374282778353 3.752565205601494, 13.392996188337959 2.668961024794281, 15.639671876470972 0.03911446704761884 M16.766470177339635 4.359740042658141 C18.839988659547917 3.418708468019949, 18.756432138859566 2.5842118717236175, 21.465090110989177 -0.4380306463525173 M17.519970044082278 4.551807576668998 C18.543397651750066 3.365000376196681, 19.62901589142837 1.7996341832477132, 21.346418173422926 -0.051128906833427856 M21.89148600791839 4.938252348781855 C23.374262206386273 3.271043435307007, 24.197033057551764 1.9383919133091316, 26.359893729959044 0.7668150872003973 M22.437079583772245 4.885752390188458 C23.862900221822866 3.316775258471505, 25.065433097278024 1.7814858678869565, 26.539551482491554 0.026187584084713666 M27.510651573359226 4.838518201819029 C29.37905051491272 2.7665784201635066, 30.615204435846298 1.7553708095530443, 32.12280338261912 -0.2620848999113555 M28.21971079512239 4.23621193149906 C29.19113183712907 3.094281642754589, 30.633783126979917 1.4775519136207895, 31.985398235681302 0.0540672647080605 M32.72286566138427 4.9641156977620815 C34.27174061885099 3.11248366643923, 35.772019718843154 0.9687285867960934, 37.001350669191524 -0.16666032419535315 M33.04511024417515 5.032371596397916 C34.19869828497451 3.453783931389634, 35.27943103165267 1.6641753343290475, 36.9290760927846 0.06726193708372322 M38.104583488177646 4.224589299212338 C39.496477880137626 3.440279041807342, 39.98259161161114 2.044661422199152, 42.20187361836039 -0.5641245355502719 M38.44278795668497 4.2924886160410365 C39.56571985398436 3.1537271460887446, 40.65411393726129 1.7491488162795363, 42.777341173305885 -0.36977976138228713 M43.892594466461475 4.241876477162224 C44.764739915661096 3.6190170682361127, 46.79416326710375 1.860311329015238, 47.07229445534784 -0.34054787548368703 M43.68042018619427 4.977193698725534 C44.75420053906599 3.722724686005822, 45.66487112322653 2.2614082215966897, 47.784490824537606 0.4903892838571416 M49.66374443741989 3.781381069213071 C50.71705896196027 3.255879155202149, 52.2699208075994 1.5014557404792517, 52.98184183383799 -0.6197843188942663 M49.1466966459575 4.167439388107427 C50.31341348173052 3.3287606718648393, 50.975084084209925 2.600417922186099, 53.13521085348418 -0.14383697729197065 M53.717713438775014 4.6583999213572955 C55.75700717228737 3.504420220233135, 55.83503755251824 2.724595792616348, 58.50382860405297 0.542955167710408 M54.337223277017095 4.417801704983822 C55.65841071059085 3.1989803747236047, 56.74681797904691 1.8720154776262194, 58.23047829493807 0.2501838822174811 M59.956737936710965 3.7691962461962247 C60.79266405300983 3.051815774699893, 62.77658578550891 0.6130392793738813, 63.29537404467691 -0.40782401556833886 M60.037369480127495 4.334428505510732 C61.47020517955698 2.8483928819463125, 63.07484578603989 0.7142517797391535, 63.77040864340369 -0.21270887520849013 M65.26838859394988 4.997426264722282 C65.64177334981532 3.120515843807354, 67.17531505830993 0.9832617012839308, 69.28018163093193 -0.021473460238085884 M65.10817088383114 4.528341122985268 C66.404942240883 3.351464196657714, 67.44692231922889 1.6793869049054375, 68.8401586211735 0.2278224899772241 M70.00529250167587 4.045212564536804 C72.26523662688743 2.0733857454730464, 74.00848594413058 0.5193224398104023, 74.92826126650216 -0.010128391197486097 M70.46917657638245 4.193382726063454 C71.3525998728902 3.226941002125219, 72.58540991236679 2.093245371975073, 74.59277341025191 -0.344437447319342 M74.96791094761107 5.139570741632194 C76.41045764874487 3.9386790817256996, 77.35802333278049 3.2194993662211564, 78.83870705160973 0.4577189750458529 M75.28435187382388 4.659519960542381 C76.41594651460606 3.738414328888021, 77.42825545762763 2.4887885473265663, 79.53508942043138 -0.0689556350511083 M81.0842616479396 4.663482758609798 C82.80664846378282 2.1344052792184054, 83.63697519278213 1.3329772887678044, 84.81941951493874 -0.21170721140671844 M80.94492555190988 4.170791307578083 C81.65366590757898 3.078430232800901, 82.46776986888483 2.270408312706995, 85.01058152121634 -0.030502185812036953 M86.39269651538359 4.262323586462025 C87.14351506104948 2.51519547935812, 89.39665296749476 1.2566329416253768, 89.86988338171133 0.46896020793720583 M86.05529261418285 4.438608725467656 C87.34943198600303 3.073371889882478, 88.78353468027099 1.4348634092081944, 89.88526580989921 0.19415126494496615 M91.06342643924144 4.80583256770827 C92.32270853010913 3.556114616843055, 93.34213920137518 2.2370834499933343, 95.19257000614003 -0.7657355376715148 M90.94003701080655 4.974732586964668 C92.65261599068532 3.37480967596482, 94.36100715833608 1.0239265487729021, 95.39050321699165 -0.63171451890006 M96.47228631289244 5.040050033586783 C98.0167425974201 2.8222976620836366, 99.28966172882186 1.787194820366464, 100.5173141368618 -0.10244702718901094 M96.60895697209551 4.6694404255736 C97.36502572907686 3.730006290453081, 98.18362436941412 2.3731915319888692, 100.60912040676453 -0.12199255222203789 M101.1647467968146 5.3394238794208135 C103.06906474542387 2.989504841447742, 103.9150183318933 2.0646758624610486, 106.06399477197733 -0.722252906684069 M101.96179521992791 4.8638559815925175 C102.74939344225527 3.8654657885878034, 103.8470403445367 2.182650050732702, 106.36730779375826 0.000621480250494022 M106.88934635576261 4.643990317381457 C108.08883252448608 3.0525968219825046, 109.31255905607033 1.5849708986740145, 110.97772783268609 -0.5743249959578136 M107.4317463147494 4.632410568672702 C108.02002439177575 3.920335393997924, 108.8526598882165 2.4548209399295433, 111.47352332821481 -0.22996032873963512 M111.79300770178327 5.020178519196099 C112.74723441533912 4.109784694205149, 114.02480878773547 2.9279507301419723, 115.87249654717597 0.14564658445790285 M112.33440339701139 5.131782876241243 C113.65236178837512 3.326162358894096, 115.22482954607061 1.3375563335800293, 116.2399330616914 0.6736387587666499 M-0.20861296487881575 3.2884879375449243 C-0.20861296487881575 3.2884879375449243, -0.20861296487881575 3.2884879375449243, -0.20861296487881575 3.2884879375449243 M-0.20861296487881575 3.2884879375449243 C-0.20861296487881575 3.2884879375449243, -0.20861296487881575 3.2884879375449243, -0.20861296487881575 3.2884879375449243 M5.764510680673723 3.569150957955487 C4.645597906126344 2.2357910605928373, 1.8800082678547427 -0.07702992789081109, 1.540147266465584 -1.1666009520674885 M6.322265701353629 3.5617541976985256 C4.829078895484 2.4911323441748117, 2.747955767634416 0.6326476653488589, 0.7732146229346936 -0.8722368316753597 M11.25267235862885 3.380763988151701 C10.248000199553816 2.117767066801332, 9.38960565163392 0.5057643077384779, 7.545937572118345 -1.233588233858436 M11.942999041572701 3.340076370771529 C10.803418122606299 2.197962384843349, 9.363961661991647 1.0288137184473378, 7.489536576882455 -0.9660292655118702 M17.785687400756064 3.979031064749237 C17.104162931928656 1.7159462317678331, 14.804428792810555 0.5724378237409167, 12.875800953405836 -1.2390536470925857 M18.13305198776688 3.1150106638358053 C17.1040618433583 2.402512978038966, 15.865606889250234 1.148048589748175, 12.970864903946334 -0.9070971067185004 M24.21604307036358 4.306727242825082 C23.831397093352102 3.010408846124513, 21.75239154231488 2.3917288161096746, 19.604830348141522 -0.8319718069378815 M24.527026598979486 3.5926370612638956 C23.238438787005876 2.754070869715282, 22.202415337526766 1.547650488617991, 19.143132504889774 -0.848563412156009 M30.73455157506204 2.990014000522482 C27.711998472707197 2.105882355158652, 26.281001335990947 0.3303286679121743, 24.80341211410097 -0.9322907201694406 M30.151475870310502 2.942669939116153 C28.442457441488084 1.7273913702590287, 26.2878205646037 -0.31441156021910377, 25.06618574007272 -1.2865914378529495 M37.220581416117305 4.165640133127135 C35.98387183482342 2.5108470965929954, 34.27503095234932 1.3051608425439767, 31.076403033800037 -0.35843737275155374 M36.57017534648283 3.414252807033744 C35.6782932300361 2.3515169377084293, 34.26486371060253 1.9582209649595241, 31.340274763593065 -0.9061685335431445 M42.529352177844146 3.5983359073256898 C40.89856641841421 1.172447737155778, 38.71358012265343 0.01706323133260429, 38.24645257352357 -0.6679368240228235 M42.510008761171946 2.8722697235612014 C41.12339709752043 2.0609760115899367, 39.75713575435648 0.8491496781510808, 37.90688303888281 -0.6289434681115984 M49.114074305938416 3.949349700064926 C46.56552558301061 1.3952897909975293, 44.875002292309695 0.8920496951163146, 43.70705965805718 -0.7441028439607338 M48.93730080144466 3.2155142818685305 C47.317440354416135 1.8383286413923878, 45.88509539748569 0.9074150228486848, 43.53912200123923 -1.2627351035997136 M55.083430772817664 2.984530981747951 C53.32607138632413 3.040513609163299, 52.987053924319014 1.7993814905820886, 49.31822034322867 -0.7775003777379168 M54.767850300886636 3.6995703992215505 C53.113470201527505 2.1648869347276585, 50.7684596839957 -0.191530907134363, 49.57364045476751 -1.193296286280267 M60.83150709138942 3.135761217075962 C59.46660591945784 1.9469178121251616, 57.483797149627016 -0.6970431796661916, 55.99819182247547 -1.9149691971279195 M60.43493372558375 3.042820411549512 C59.07493186696795 2.168639708500107, 57.484267821128476 0.5724471926261987, 55.456751854547015 -1.1636610715444646 M67.42370691916955 3.8428165450846716 C65.94931437387267 2.61496525907428, 63.6707665947877 0.7232855045712855, 62.17849103456345 -1.2625339443450443 M67.11165279213388 3.278050406618596 C66.07282919213665 2.1857174166660185, 64.63331915322146 1.2445125003096342, 62.15272002655064 -1.354861731001141 M72.58718758330211 2.5772113467023168 C71.0910221085163 1.5722098563338647, 68.83506580243423 0.46582905939618313, 68.81003844437461 -0.20834639593770476 M73.08541987466157 2.9481475589253394 C71.31793348222585 2.3074085313639907, 70.50510097135341 1.095052856269266, 68.21310880004701 -0.7992077340353063 M79.88161192599503 2.963826031150881 C77.96717670768673 2.0504188613544225, 76.8528077852396 0.5467352059869754, 73.48628560590312 -0.7223557952539303 M79.26421592638647 3.1415289262837054 C77.56115155882736 1.7584043998901109, 75.27108288267448 -0.292450474865476, 73.6727807003235 -1.1176530286883235 M85.15940583565383 3.9828077240582678 C84.53316630533732 2.5706750653109465, 83.7463649576255 1.9114712433861807, 80.6987212582682 -1.01181192659581 M85.77677335849143 3.8305026175655543 C83.95630038128927 1.7598071274131228, 82.10079573182306 0.24133949168315938, 80.2205982852452 -0.7858427189830122 M91.59291239485668 2.937183463384085 C89.94366393839859 2.4508979779641553, 87.55742815984387 0.9135255193331335, 86.06877690881016 -0.6962481570270727 M91.15042984036322 2.961795508998134 C89.15630973098664 1.891558882852161, 87.66041335156943 -0.14359300523982305, 86.10675948780487 -1.2391775035605648 M97.91169541736946 3.3416317319140307 C95.49218946488479 2.0696940967731483, 94.43869048126861 0.6191833762255833, 92.23275465254488 -1.0974117850120195 M97.40355076047473 3.3908987664726102 C96.80587000430478 2.855954844592082, 95.45112446706774 1.6218024119980632, 92.14547930585036 -1.3656720759048135 M104.14339700753973 4.438259394212287 C101.51717560173175 2.209979649798188, 100.0448151296213 1.0340402507068345, 99.25315987506985 -1.4877467272364693 M104.08973800100559 3.5643549845336335 C102.19909478690894 1.8503158479906923, 100.1752362781833 0.43224536683387615, 98.79714673093625 -0.7982272008461095 M109.480991300965 3.0116537835930353 C108.05322830218664 1.9572440335076786, 106.082227774392 0.9941373532204522, 104.58595499323268 -0.7753414316362429 M109.4353961785103 3.5894977733508573 C107.98926338931805 1.9115361097006265, 106.44065432985599 0.6612125582705999, 104.15589098899211 -1.1948901275898067 M116.45003820516114 4.303934789091976 C114.68081168289665 1.997124654739301, 113.76310261068413 1.4123410255586692, 110.74781319579918 -1.2888767904256269 M116.02729888458681 3.9541913456904454 C114.62279345163391 2.0058469454812387, 113.16898797309653 1.24124329562122, 110.68714778099087 -0.8691150027447179" stroke="#ced4da" stroke-width="0.5" fill="none"&gt;&lt;/path&gt;&lt;path d="M-1.221372226253152 -0.8587334658950567 C34.49522458871958 -1.6073016045615034, 70.70805820483977 -0.41734523754566477, 115.62666776117044 1.7088773343712091 M-0.012282784096896648 -0.9022711412981153 C42.82799382322792 0.7669470785154882, 85.50976689814307 -0.712552112530273, 117.25933603189992 0.23897371720522642 M117.70552085986255 -0.20255779791857156 C117.66186586536547 1.1778224326527134, 117.36872272844138 1.7096371458044965, 117.37118594234201 3.4710287106284503 M117.28380397201082 -0.08560229580788073 C117.46901262511183 1.0807296373240165, 117.19311787712661 2.4639649785923092, 117.20681725233472 3.348807800245013 M116.8414075010653 1.880694220744033 C92.34496209574054 2.5974649488085273, 71.73037360875074 4.011813253080225, 1.8388225641101599 5.273824582777877 M118.3009362890582 2.794819425761659 C79.64044344111988 3.96190058488064, 40.461644814678166 4.172115825597574, 0.7475630389526486 3.085285704553087 M-0.1542135723269599 3.60495917760911 C0.11780939412302584 2.699341419649306, -0.32461275187714356 1.7812937266881816, 0.19740933463365734 0.29219686287288 M-0.12114588461447473 3.345415729578865 C-0.1104389712543098 2.714665389973682, 0.15513103312274523 1.9415412849800786, -0.12194775469338784 0.03063584712024159" stroke="transparent" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(170.48678477545945 163.64008378937473) rotate(0 58.68576547639498 1.7349162106253004)"&gt;&lt;path d="M0 0 C0 0, 0 0, 0 0 M0 0 C0 0, 0 0, 0 0 M0.9414283818703943 4.909363591052372 C2.0867767494804412 3.0483022286944785, 3.9054874367785164 1.39219249789145, 5.132601585383064 0.7504824651253633 M0.941899512163257 4.851293957825531 C2.13747847664476 3.2865365175785097, 3.548668168931442 1.5682554237899962, 5.131461360728836 0.31069818090994294 M6.921675106426072 4.55826107641758 C7.22535172318142 2.767375261341396, 8.690514728816776 1.9356835629645026, 11.145132665102315 -0.002511948618316495 M6.919009312809748 4.277737422809764 C7.775958537057377 3.7054451528894248, 8.630874640324684 2.6108690907526424, 10.576945890531821 0.25369275902283794 M11.401177247486848 4.74546666037714 C13.46410150514223 3.043933913825917, 13.87002794273532 2.107811081772535, 15.25044333124854 -0.011991964939517763 M11.643196623924958 4.662786704436447 C12.529008245962126 3.636051598927434, 13.39715507378167 2.9279077794388897, 15.33060717396759 0.17685992623912 M17.224297736080608 4.953828028188637 C18.282883146037037 2.921411647881903, 19.4513600447251 2.7013432323339277, 20.89113870234963 -0.04647708608065815 M17.416365270091468 4.445762266283473 C18.461988005120762 3.325509584217287, 19.1278663290617 2.5508525985427313, 21.27804044186872 -0.1126087601133165 M22.429718074773227 4.7383514985007364 C23.06138920446536 3.3142058168305266, 24.021712217776745 2.658074291079943, 26.72289246847144 0.6789218191069305 M22.377218116179826 4.912850236569824 C23.440392262797875 3.5375694672478697, 24.53500778740666 2.8433255665796677, 25.982264965355757 0.2487189219345094 M28.36766056959257 4.71977538157102 C28.725255359644194 3.549846117693245, 30.147197339287505 2.0666078079669203, 31.731669123141863 0.09607587338082835 M27.7653542992726 4.393800766373392 C29.6986347537821 2.694860416067989, 31.15126342661289 0.8952531720203889, 32.04782128776128 0.07721421721048999 M33.12016609810452 4.653496621048162 C33.69407979000776 3.83349364660361, 33.978326485490356 3.0900841628823636, 36.454001731426764 0.35276483288970795 M33.18842199674036 4.833979671413163 C34.532874700429275 2.945456241938951, 35.66217860494218 1.3997908318822485, 36.68792399270584 0.014239035183610582 M38.418316341336954 4.4231441521897334 C39.65729633016644 2.8867301274855204, 40.289713796847984 1.721024043451135, 42.09421416185402 -0.193749554608857 M38.48621565816565 4.191066790267023 C39.52832024221131 3.1612786773398955, 40.302803664875704 2.681001565203191, 42.288558936022 0.022844308181757367 M43.06251155185574 4.50950554364441 C44.849037407479244 4.194163274378626, 45.50151069693447 2.4307227822264177, 46.9446988544895 0.09220309203380805 M43.79782877341905 4.807107626153958 C44.71716145596336 3.5571789666491553, 45.42951920580623 2.911993216127355, 47.775636013830336 0.2598216331739174 M48.63969278568876 4.584100355669775 C50.32921060113907 3.9614427817511686, 50.78612666432129 2.32636610732852, 52.7031390528611 -0.6817248544247142 M49.025751104583115 4.605211705151865 C50.31384764265893 3.1012641188940315, 51.71375770149806 1.6940805759947242, 53.17908639446339 -0.22759837001201433 M54.14361967040188 5.178106781914495 C55.5800775001887 2.671835610811852, 57.38592104247975 2.0990914110876537, 58.49278657203467 0.3330276121642435 M53.903021454028405 4.861604217979721 C55.02869231648769 3.6503130050243238, 56.0440424111871 2.779954120484246, 58.20001528654174 0.3703055830929422 M59.29209263702298 3.8759350076361443 C61.62596585238128 2.8057884565535014, 62.23493439271071 0.5765589550175122, 63.5796840305381 0.3195034895582566 M59.85732489633749 4.4023243782154164 C61.14564520110887 3.283647800015878, 61.78582871525072 1.942478343453303, 63.77479917089794 -0.0775562001833251 M65.14723068811794 4.184153851483399 C66.3723431316746 2.550864891249585, 67.34270002225676 1.5904660180387409, 68.59294261843725 0.2641544089059963 M64.67814554638093 4.975277064077939 C65.89340250925079 3.6299132247058123, 66.61742330809791 2.5231326162464804, 68.84223856865256 0.42896725212816744 M70.23269362971463 4.525064123713845 C71.13211731676772 3.4964295735959476, 72.44802229733097 2.3155428360852492, 74.64196432926002 -0.6495860070349408 M70.38086379124128 4.246931176645184 C71.60101492337965 3.2591152411921462, 72.6549323530431 1.853325278563798, 74.30765527313817 -0.4820340651446713 M75.95395983937892 4.705625267976136 C76.71188471046169 3.7137687243763935, 77.94996242309551 1.9072500824967333, 79.73671972807226 -0.3723755021159121 M75.47390905828911 4.774371281636119 C76.99071379674261 3.2768243856131924, 78.18443826621711 1.4589555961789835, 79.2100451179753 0.317520507467114 M81.5155484981387 4.511028719527867 C81.80992616707626 2.559318086914238, 83.83331220557048 1.3493185589348085, 85.10497018340186 -0.7953898285793173 M81.02285704710698 4.0019959676286545 C82.42453237780434 2.2331689523492373, 84.116148610843 0.7156368294117381, 85.28617520899654 -0.04759934770856372 M85.74129735855983 4.173624547017697 C86.7934681555785 3.671737891539838, 88.32878057101458 1.9253556533794678, 90.41254563531467 0.5186038947234997 M85.91758249756546 4.433569487841758 C87.66837936602937 2.75753800495764, 89.14963274025192 1.176803380199743, 90.13773669232243 0.016116716297264716 M90.91171437237497 4.912652856940108 C92.23681050838093 3.38053148131609, 93.49464344228797 1.9373493372602493, 95.21552653148815 -0.03130045823849048 M91.08061439163137 4.985482382531913 C92.647841394351 3.570142782417806, 93.46688773557817 1.548322044958825, 95.34954755025962 -0.10270334134905845 M97.18360848003566 4.472380204233093 C97.88869362816708 2.8510810362277255, 99.78131347149342 1.4617312680531167, 100.50572307453955 -0.4146163849507516 M96.81299887202248 4.375991799882366 C98.33377947495389 2.6661381629189362, 99.4340648378987 1.2101752841211768, 100.48617754950652 -0.09665525419386681 M102.10989035843859 5.164287031067964 C102.6314271569429 3.1128495475329956, 104.58057028838115 1.1781669994523725, 105.92359383682665 -0.6157935652995986 M101.63432246061029 4.826290961400103 C103.80927620383784 2.6318022602343625, 105.2966849893879 0.8534611177085872, 106.64646822376122 -0.34626184698404217 M107.4521334381814 4.139245755247311 C108.72785632730991 2.475537134083229, 110.12811552705952 0.7868941854116739, 110.69842978012181 -0.5706203255136827 M107.44055368947265 4.373195834945426 C108.46262580593526 3.461082780447329, 108.72856175188255 2.8432511343876303, 111.04279444734 -0.1437690368187944 M112.45522967256494 4.336930121860689 C113.8155157881226 3.348239274658195, 114.90814646956146 1.8775159427889057, 116.04530939310642 0.7945324935533095 M112.56683402961009 4.79084904799912 C113.87759529807009 3.2543380554942543, 115.00130543856552 1.5619619044025614, 116.57330156741517 0.10477855108231182 M-0.20861296487881575 3.2884879375449243 C-0.20861296487881575 3.2884879375449243, -0.20861296487881575 3.2884879375449243, -0.20861296487881575 3.2884879375449243 M-0.20861296487881575 3.2884879375449243 C-0.20861296487881575 3.2884879375449243, -0.20861296487881575 3.2884879375449243, -0.20861296487881575 3.2884879375449243 M6.208377248546188 3.8522487296969365 C5.34481313234163 1.9367223464240146, 3.517326736624261 2.149044457492854, 0.7820714798973595 -1.042894386828953 M6.200980488289226 3.8032343659330827 C4.636054252921042 1.6609144146513342, 2.238667082733335 0.10533487904613992, 1.0764356002894884 -1.0924389383784323 M12.057666920524579 2.8732612568433598 C10.402258052001098 2.0006658667580712, 8.460566509253264 0.8249793314138901, 6.851411391120854 -1.1068711637408433 M12.016979303144407 3.2412826985600605 C10.470774654274424 1.7279894074835025, 8.884738488138627 0.9514042657575552, 7.118970359467419 -0.7679101066552995 M18.792261190136557 3.9024182504196725 C16.323086477431463 1.7641498701114129, 14.95193212597685 0.4860808619694872, 12.88362261966888 -1.6794014637014318 M17.928240789223125 3.473645758131992 C16.926872592744214 2.2661328183735026, 15.38622942673137 1.1077703232021063, 13.215579160042966 -1.5052410662295719 M25.256284561226842 4.003732703422657 C22.92042193403976 1.25778604190675, 21.264630996524563 0.46074569986905933, 19.42703165283802 -1.3527928021718345 M24.542194379665656 3.55657856336934 C23.110687321707633 2.2429694581473485, 21.25000815225443 0.42888895815353967, 19.410440047619897 -0.7461682863981061 M29.977247960706414 2.5872454402097933 C28.908581121444545 1.4117186886678192, 26.86990135404948 -0.10181814879875328, 25.36438938138864 -0.7986214769752775 M29.929903899300086 3.4420572952893997 C28.7021204581903 1.6921894514147278, 26.59298396735589 0.2496893019348847, 25.010088663705133 -1.5485647776378402 M37.289201286325515 4.115394953332406 C34.66112080875867 1.9717934106484278, 32.56364194154894 -0.12121149120452496, 32.074569921820974 -1.6445857143104274 M36.53781396023212 3.697777099275526 C34.70135947827161 1.936567419655694, 33.60631219164353 0.5507582953308177, 31.52683876102938 -0.7754482851904083 M42.75957370230625 3.421543217540328 C40.76547403458837 2.0421140968178073, 40.03717912518207 1.787766157610463, 37.90139766356414 -0.8134954759320557 M42.03350751854176 3.280786721963976 C41.20949017244975 2.290976456430697, 40.02031769232142 1.3216596136651806, 37.94039101947536 -0.7652122851861953 M49.246914688059924 2.887435087153284 C46.542190185238 1.4884501075637193, 45.883507641172095 1.0025568801235214, 43.86290828540841 -1.3750119730066372 M48.513079269863525 3.4179633289683293 C47.349607649566245 2.4799519530941256, 46.543600313500534 1.6027098551040586, 43.344276025769425 -1.4728170062117143 M54.418423162757385 3.0021857622326866 C54.23524297618792 2.762982383547139, 52.789567348772465 1.0815611620502477, 49.96583794464567 -0.22826237748656397 M55.13346258023099 3.7699066303452065 C53.97928328389795 2.2607320512715665, 52.05131756746552 0.9332474235164292, 49.55004203610332 -0.7125636252602561 M60.60733003986758 3.8853072064477985 C59.59891333680909 2.4894254653826975, 57.090027534550025 1.1843711375316188, 54.866045767037846 -1.3372112266979628 M60.51438923434113 3.1396836391665075 C59.75940215117415 1.9981928263437516, 58.294226253928095 1.0799813200831299, 55.617353892621296 -1.2426811877432726 M67.45071256089074 4.032148068599955 C66.06340508906572 2.100559808759122, 64.07136552575867 1.1428343445265974, 61.654808212835164 -1.5560215905895107 M66.88594642242465 3.705878172690601 C64.92586741229286 1.7243156179509043, 63.028851871896975 0.33999230619981125, 61.562480426179064 -0.71061126702741 M72.22278400429055 3.2520488797143483 C71.34739992079582 1.5375117002964869, 70.37552486088536 1.1902856972542353, 68.84532295425694 -0.989769865943097 M72.59372021651357 2.913949097266087 C71.19049839335213 1.839170729626159, 69.28611970109503 0.11105527942988747, 68.25446161615935 -0.8574211752954652 M78.74572588175354 3.6085844502846025 C78.06012943848432 3.0766315826658257, 76.75386173239474 1.1651977112839977, 74.36899019672289 -0.689052333843825 M78.92342877688637 3.705035406869488 C77.97936430428892 2.344145192128929, 76.43360082611862 1.1020579845149723, 73.9736929632885 -1.4818074454766923 M85.90103476767538 3.752985336698231 C83.95371680284237 2.6861354737422887, 82.68978489434045 1.261004307038347, 80.21586125839545 -0.5468657838254595 M85.74872966118268 3.9331898269444823 C83.352320260449 2.340701248773473, 81.57798413726246 0.3439283139246983, 80.44183046600824 -0.8872451064716458 M90.89308714878338 3.672730258849137 C89.68566799186821 1.1276156396229045, 87.47300566907461 -0.22952369799383066, 86.56910166974636 -0.9197130056870051 M90.91769919439743 3.1207840946006256 C89.55636318090424 1.7752273622524384, 87.17788982624067 -0.025110899685733323, 86.02617232321288 -1.3621995238893547 M97.43386261032775 2.8742030217886447 C96.47169646919774 2.4289817618190543, 95.26935841668345 1.5809726003361648, 92.30426523477587 -1.610096913127136 M97.48312964488633 3.843228198971273 C96.75088388822874 2.6228798350818052, 95.3821081601106 1.3039591407601867, 92.03600494388307 -0.8025985992214661 M104.66681746564046 3.267192374846558 C102.39642390893162 2.297539732580114, 101.23513847020983 1.3663964060524152, 98.05025748556585 -0.3889904755660475 M103.79291305596182 3.827432776593264 C102.46545458103559 2.6752580226518665, 101.50725870341593 1.8018268866326959, 98.7397770119562 -0.8919320865561404 M109.27788849680339 3.236194098602689 C107.80136095025306 1.2142040802170229, 106.3913037558402 0.6361543552841995, 104.80033942294824 -1.024534631173938 M109.85573248656121 3.443423698378436 C107.96520933817462 2.086363019065811, 106.49510937610354 0.17119873059535545, 104.38079072699469 -1.359637005606547 M116.70649669531677 3.611713091858358 C113.96700687961939 2.629731219698785, 112.90141062520749 0.8126902635013277, 110.42313125717331 -1.0107361998994633 M116.35675325191524 3.745956656585143 C114.15197457159678 2.403687537754133, 113.09438169239156 0.7506239651535789, 110.84289304485422 -0.8894830547393099" stroke="#ced4da" stroke-width="0.5" fill="none"&gt;&lt;/path&gt;&lt;path d="M-0.8587334658950567 0.07686777971684933 C36.439791531592846 2.5881162271270606, 75.58494471816766 -1.0280915513267663, 119.08040828716116 1.2178074326366186 M-0.9022711412981153 0.4697383986786008 C40.54633724309996 0.8076725407825838, 78.87093268041552 -0.09777842915187973, 117.61050466999518 -0.4996967865154147 M117.16897315487138 0.3202997568255426 C117.6831363994378 0.7684579037763417, 117.347387797279 1.4822575573018093, 117.3727272421678 3.616698786657805 M117.28592865698208 0.11500952512992277 C117.26139919894354 0.7047859754394339, 117.45681844842944 1.5666286961342282, 117.25050633178436 3.3002162780156836 M115.78239275228339 2.1797557687088442 C89.2770872658783 4.9905524806122, 63.88708672637262 4.997547979746264, 1.803992161527276 3.9754615043922854 M116.69651795730101 3.917527174547274 C87.40235726329207 4.008368085007249, 56.96996243079436 5.308252732138215, -0.38454671669751406 3.0152707155495477 M0.13512675635850935 3.582503135975769 C0.13830517339502163 2.1505180575789993, 0.10348191241902917 1.688115891091058, 0.29219686287288 -0.2340246469053716 M-0.124416691671736 3.3376634354225616 C0.05095059263493708 2.7795838591617317, 0.08475156711114255 1.7005855052814434, 0.03063584712024159 -0.01960159582430271" stroke="transparent" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(162.70066630771385 174.65588838707578) rotate(0 66.45875398214207 3.3441116129241664)"&gt;&lt;path d="M0.2149947490543127 -1.988468399271369 L131.5594067360321 -0.9043889548629522 L133.15826777471693 8.405032627136961 L0.10660960339009762 4.840388469250456" stroke="none" stroke-width="0" fill="#f41d92"&gt;&lt;/path&gt;&lt;path d="M0.12425346113741398 1.8388225641101599 C33.10555586387472 -0.7117803593490728, 61.82459973694894 -1.5264592071388372, 134.77631863682063 -1.3500259909778833 M0.8836516244336963 0.7475630389526486 C45.44135276627561 0.2795704118378244, 91.50370307041 -0.002841312491170367, 132.47306698384227 0.3894330905750394 M133.0676825379529 0.38051338987149763 C133.50245738924718 1.7725530234427085, 132.52038410362763 4.462851402070203, 132.45048211356456 6.208588185362891 M133.08242596986037 -0.23505855795376052 C132.9965127192642 1.459163530755903, 133.00428722520002 3.029962879813268, 133.12229247144631 6.647240647672041 M131.99705615370067 7.558874551059262 C88.02335998477027 5.998541348679216, 45.21233204398928 7.952573590977342, -0.40627347491681576 7.46131551623489 M131.9509488624567 5.913818913364366 C80.89231842108057 6.773848694427449, 27.995990950590667 6.491206180913884, -0.8490435676649213 6.506168472433046 M0.04053492972923878 6.375700302711297 C0.20353963082480114 5.093501511315473, 0.2596929342667195 2.2340418589457673, 0.3734857793161992 -0.2424742245112672 M-0.1645692205659853 6.370102625237511 C0.21660612868082718 4.709325786648978, 0.02078827128944527 2.927595919743373, 0.0646250203024859 0.1270424856353129" stroke="transparent" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(169.96052485146174 184.89008378937461) rotate(0 58.68576547639498 1.7349162106253004)"&gt;&lt;path d="M0 0 C0 0, 0 0, 0 0 M0 0 C0 0, 0 0, 0 0 M0.7720197922688776 5.387334936238606 C2.8892656683254994 2.882304120121355, 3.4877562752851685 1.8891172397354645, 5.353462275261528 -0.08315641155119591 M1.253602374545192 4.86016399621569 C2.597011577732011 3.1606145399303847, 4.032872367143238 1.8583040023826989, 4.6903956112028125 0.6259028102119677 M6.893434538980317 4.5578299023777396 C7.822159960088756 3.7166794535678798, 8.926791848356157 1.7422022820031176, 10.879054687683416 -0.25255993631782514 M6.669181203664583 4.22508087657468 C8.064179400362042 2.7900025566796236, 9.348712718790477 1.7418460966188016, 10.420873217330259 0.06968464647305622 M11.45245309086873 4.281702547189351 C13.28256619972712 2.7335915521561898, 14.199860158286834 1.1585052606793997, 16.098079762704153 -0.15380917108385067 M11.508805927041756 4.689457455412056 C12.591232347904251 3.4025008905130405, 13.794794687347942 2.1396106207827073, 15.525656117129554 0.18439529742346908 M16.813555566462004 4.418558368431023 C18.920453272646583 2.7758941329454356, 19.780731759211832 1.4631942952163854, 21.442009806351628 0.2525841944083098 M17.52213427550929 4.621422790055713 C17.97175908518714 3.171621123745091, 18.97163105995517 2.274272541518663, 21.25371844768327 0.040409914141100955 M22.054290655826005 4.881411497251423 C24.33501659377223 3.6246421021376687, 25.334677006442863 0.8005487131291432, 25.8741356961917 0.7407671038073241 M22.58624802018774 4.795346602474631 C23.013370384707095 3.6516588629371793, 23.64711318710143 2.9299229471465527, 26.322083386361957 0.2237193123449327 M28.08249785248861 4.830258056408133 C28.81956618439908 3.4135094490909363, 29.67357124168558 2.4794234002110205, 31.40678911325226 -0.5868815076292271 M27.669750204409663 4.688537637496706 C29.24540235885721 3.2852407955780727, 30.278106467299576 1.5586717367465477, 31.779920382748195 0.03262833061285664 M32.8565852215157 4.941002802049825 C33.894742106583585 4.009735867296317, 34.86383777938648 3.33068919676734, 36.52162031009917 0.3691759287473282 M32.999917911795634 4.55329530459092 C33.550660653178205 3.720247066905668, 34.875180540599004 2.755102873786105, 36.87569792671253 0.4498074721638581 M38.902751631435144 4.199032481934073 C39.74966013101522 2.9633928302794166, 41.22689402212494 2.164939444649783, 43.08682034276031 0.2992089731945641 M38.4267994981782 4.113608036085161 C40.26608695476985 2.7040091891662055, 41.45352222338514 1.0212704464467839, 42.72128078033753 0.13899126307582316 M43.73526452231282 4.56814247720955 C44.651950256817535 3.754102012898804, 46.03848001927212 2.346565650217412, 47.37739883880156 -0.24685418063884512 M43.79036735967794 4.637204928948415 C44.83426907457057 3.7192330621245673, 45.3938065821895 2.394063542543734, 47.55908274599376 0.2170298940677391 M49.773682137676595 4.184110160633841 C50.642741026559186 2.9326968202222155, 50.85565157740864 1.587344521672506, 52.99240622925079 -0.6658533474953167 M49.284615164627404 4.632254103008576 C50.43432109417691 2.7768638137753165, 51.97240684605152 1.0862632562350365, 53.42616389826378 -0.3494124212824993 M54.763131292592355 4.423621538355611 C55.654223809140156 2.948000960851998, 56.97455124291416 1.9231797691178978, 58.27521151667388 0.1675302912738168 M54.02471062164716 4.919414342734627 C55.521601416320536 3.1087499014723377, 56.82320261373758 1.6763001398278519, 58.42138335201816 0.028194195244088144 M59.29152960558797 4.297844608132436 C60.89565035857071 3.5990798819976084, 62.48660215178714 1.7352335368910776, 63.230724507098685 0.0943475459261307 M60.011092711569766 4.328533455454453 C60.749606155455595 3.4476457736109776, 61.996990505504776 2.35340352026085, 63.96949649433945 -0.24305635527461078 M64.40986326672932 5.121356001112993 C65.52612097154717 3.7826722771124106, 66.627857060867 2.1190636565962566, 68.9456735780911 0.14030786011029617 M64.69662445551579 4.770699913137043 C65.71728871691786 3.2321181675768043, 67.12906057731097 2.174389177590864, 68.88830775259822 0.03454549288038741 M70.74940926954427 3.7057143400548638 C71.17547522702681 2.3503799308951305, 73.19554370887275 1.7219964819020825, 74.90582791067901 -0.49064733091609014 M70.3506901468178 4.534941210610294 C71.89608082404133 2.509774738706855, 73.21829709601037 1.0828980951603615, 74.25276782637702 -0.35397667171300784 M74.9420131790987 4.228386881106705 C76.69536422236025 2.7886751359824045, 77.80115625660433 1.8561430431640955, 79.61627525742827 -0.3474179585043856 M75.42634811909635 4.5602755073431736 C76.7517537668946 2.901050697872805, 78.14383829271796 1.7137614851838558, 79.26212645554645 0.3357664041641584 M80.73731136392048 4.226322872026112 C82.3301617854296 3.549879211500196, 83.31156279012183 2.19825756323776, 85.11864417436675 -0.73817196239699 M80.92442797818399 4.13240998566359 C82.22666330408696 2.5316341260633766, 83.33575518663774 1.1131787431999083, 84.90402965218902 -0.195772003410201 M85.82300876407551 4.064880193961744 C87.78596927811844 2.59322942809483, 88.39457099786168 1.5525101279047582, 90.41138178906033 -0.46141864894524554 M85.96068337020131 4.755792492540511 C87.19036355676583 3.028115434684894, 88.8586514793223 1.8918997851769075, 89.82167246867532 0.07997704628288321 M91.08731654319999 4.405040750736074 C92.74372327254017 3.671071549089661, 93.9213863864534 0.7043474860723924, 96.01633727088615 -0.8960989772538032 M91.07306931532074 4.777103791888507 C92.62106783934846 2.973380667793735, 94.23800810426621 0.997389534542433, 95.79574416744568 -0.045795152588777754 M96.71315275603473 5.104126955303116 C98.82511612568673 2.6416017359072423, 100.15517662529386 0.5683778207645924, 100.75223340736234 -0.314340695587272 M96.47299446664319 4.423339735916568 C98.16531054542027 3.1361053218485386, 99.0851446157711 2.006625360510006, 100.52234129979739 0.163735036424076 M101.22195550381194 4.660919347776529 C102.79749382585769 3.6308276859104494, 104.35675090239228 1.8359226368719916, 106.81960168163238 -1.021116948511688 M101.79793951274814 4.685014707640242 C103.42472161428952 2.886542590450785, 104.64376165351082 1.386908556998287, 106.24798529459109 -0.21573581841052925 M106.86962288935138 4.1725431807558095 C109.13298367530935 2.5338866115037737, 110.21138838376807 1.3126412572904465, 111.15694534670926 -0.3657055828054766 M107.26320816623448 4.527443604658828 C108.78667419534891 2.8104410027658027, 110.09837538825187 0.9726294932170371, 111.29609095672511 -0.06796450822477737 M112.37812054432817 4.6022010047056146 C113.21351733468701 4.392270812769404, 113.44913058803665 3.41530668392683, 116.289713440834 0.022320351801066396 M112.05306739812606 5.161780682306028 C113.96326021458519 3.1581906812710128, 115.19817011278512 1.4484099397447308, 116.088654141406 0.28887766204327225 M-0.20861296487881575 3.2884879375449243 C-0.20861296487881575 3.2884879375449243, -0.20861296487881575 3.2884879375449243, -0.20861296487881575 3.2884879375449243 M-0.20861296487881575 3.2884879375449243 C-0.20861296487881575 3.2884879375449243, -0.20861296487881575 3.2884879375449243, -0.20861296487881575 3.2884879375449243 M6.136495784422461 3.2620940133244467 C5.254714109157254 2.6919633450008478, 3.5483710330946017 2.2075345352610523, 1.4838666019247566 -0.49055900234084726 M6.231354170370926 3.4712421316434368 C3.9438965731670774 2.1084332518066717, 1.7466963134721512 0.047233569901790506, 1.1612681389538293 -1.1409650719753295 M11.615133636098843 3.749088742166312 C10.54802053868561 2.3316964459455796, 9.902761656784454 0.65995430825262, 7.270680589300099 -0.563405853405684 M11.7303690246572 3.0765298426631107 C10.349489050751458 1.7026922331570318, 8.407725480634221 0.024362363815115362, 7.429371742509756 -0.582749270077884 M18.467157641967418 3.4290445780535306 C17.40309157949331 2.840673194540449, 16.068401815856223 1.4960903011318052, 12.913195413510241 -0.7710699473163536 M17.933115222800183 3.567428956429016 C16.414230772515356 1.8590464777160731, 14.186241356181448 -0.3939719289374438, 12.63231296289798 -0.9478434518101116 M24.65122066683578 3.7644865525714075 C23.5637808215728 2.352057223110212, 21.465562093743085 0.8848946053514064, 19.59023968151215 -0.9380406734515511 M24.694477727808756 3.7260767789850897 C23.353809812829727 2.849092675511236, 22.887260348003206 1.7316961290474706, 19.514782044826106 -1.2536211453825765 M29.986214808960618 2.825977518881249 C28.132455598845898 1.3677939053249646, 27.4223654734955 0.4097860240543908, 24.666454202512718 -1.2276409966619766 M30.530964875388133 2.9564340414074683 C29.019561585363725 1.7248281347066265, 27.265445305340805 0.5073335182854706, 25.029823584266445 -1.6242143624676473 M36.343201684842164 3.568631750784379 C35.43692065676252 2.814000437534832, 34.371416776591985 1.4278375822318523, 31.804599811762188 -0.7717683618962962 M36.62129666826902 3.591576748454508 C34.97982522375143 1.9024843819468158, 32.75802029238404 0.7084467898276151, 31.073589224574704 -1.0838224889319588 M42.19293668100307 2.885887891889159 C41.76679134186904 1.7118858150044005, 40.84016958313902 1.4402932591786346, 37.818139052600316 -0.9899053105553925 M42.137124462497184 3.3742883941126807 C41.64883941229826 2.632878971755583, 40.34631741248446 1.7880461727389307, 38.033210176718136 -0.491673019195938 M49.09141248495304 3.4405889149397297 C47.39156692654475 2.347417254787234, 46.115178443207746 0.62051682460568, 43.21111501708546 -0.4878671898674085 M48.98675181630985 3.2654528394449187 C47.236101354359455 2.5516411496804765, 46.443782150380954 0.9770963420764935, 43.66058949666949 -1.10526318947597 M55.04178324526452 3.0167284640730876 C53.41767425454746 2.7823599343288956, 52.1072054504015 1.629123497405109, 49.331404886741424 -1.3464004732230679 M55.32476651438788 3.80211231780849 C53.47717947378399 2.040119626328446, 51.64618803936432 0.6963510997005429, 50.071891252624106 -0.7290329503854736 M61.30735209060592 2.849718396599967 C58.71988334486056 1.9161401002256337, 57.13025628411405 0.7338677754450866, 55.79237807091949 -0.9505705558023863 M60.5766952227125 3.5120483136750793 C59.4544540727929 2.4767783555400804, 58.26218210546973 1.2724295653550701, 55.282338036891716 -1.3930531102958583 M67.62158298122745 3.3182081425717036 C66.37428467426338 2.7736733279248544, 64.85340268273941 0.9676496885110881, 61.428572748619885 -0.7681147263040414 M67.05021302213942 3.4189130036780546 C65.69695915155646 2.260992793676653, 64.03775294212281 0.7421682449360609, 62.087349070953024 -1.27625938319877 M72.99778421290681 3.2871189950421833 C71.24363104908747 1.637179475861029, 70.84976179392382 1.2751806082913382, 68.60789468844693 -0.6895887810718052 M72.77700835263818 3.4155679683588827 C71.26652400390431 1.3135310053114162, 69.44852105692 -0.1587004925545989, 68.48564642357913 -0.7355822152439289 M78.98577412038861 3.1771116034189495 C78.06045440262304 2.277579707748488, 76.52442263361438 1.3604959734276907, 73.73004168166533 -1.3728226775051322 M78.8401563331746 3.460904996615487 C78.0100560988573 2.479453170067207, 76.70258420181159 1.2309505490876296, 73.88049338983126 -1.4184177999598202 M85.9915608925104 4.1395572476755955 C83.25461698275748 2.0356040689962867, 81.5074690070389 0.4896600896922163, 79.96266589809252 -0.5401029663234377 M85.76644584395102 3.7110482377201435 C83.50442117182573 1.6541114225249378, 81.41311010038503 0.05768427128901066, 80.17429145506625 -0.9628422868977666 M91.53257443618553 3.7635804481773305 C89.00341976980678 1.5867946683922547, 87.25089297194094 -0.9852286303055177, 86.08012320931526 -1.7989764752638409 M91.46833912327477 3.360790933339544 C89.57941996461578 1.7920909294497345, 87.72280730382127 0.24400663949492407, 85.70827001004005 -1.424758688949875 M97.07655605541423 3.3294907912254805 C96.437439046144 2.832506199373149, 95.24145899918214 1.640342276698816, 92.06060413808378 -0.5748953367750183 M97.9715288301266 3.3602989319820424 C96.72862242303539 2.3662909713129103, 95.3576893318294 1.7991812994872742, 92.41617835435832 -1.351901548232791 M103.39459878227846 4.399127807399851 C103.07242955623639 2.676764161196718, 101.17406217474526 1.272779421067152, 98.60752593522996 -1.483656739027162 M103.98586989844391 3.8471762295594774 C102.12008968731034 2.1605885001914995, 99.92673472561818 0.49091082013403564, 98.72999056258344 -0.7936297594596781 M109.17215111761008 3.5871630585302485 C108.27511148598452 1.8563311306507106, 107.262252513863 0.7089899374075188, 105.06053632033576 -1.8601008389404725 M109.52107077778902 3.5880795563586876 C107.8831195144891 1.7899684642616736, 105.71568090555184 0.11213265816360568, 104.5455967249028 -1.5093119829687032 M115.92002077165935 3.4324022246679973 C115.01087050788067 2.5995815493707886, 113.76666127475535 0.9542807294108469, 110.8065172224014 -1.1868086752136038 M115.9523846740177 3.9703329640130263 C114.46428607183394 1.8480508231312003, 112.82566180745371 0.886592009377907, 110.93849418779713 -1.2267120577514903" stroke="#ced4da" stroke-width="0.5" fill="none"&gt;&lt;/path&gt;&lt;path d="M-0.49748157523572445 -1.9331182036548853 C38.19960194539251 2.1934787216626335, 80.3293121902639 1.9100670400105644, 117.63873697530585 -1.6980871353298426 M-0.41478194762021303 0.06060642469674349 C32.60738130978343 0.9666121913147736, 66.79993469627642 1.0505706680966187, 116.78178883895384 0.5584230171516538 M117.28654811330972 -0.17075614786687024 C117.0371630495552 1.0822224937193772, 117.3100960843778 1.7941976700980449, 117.46181656062215 3.5368869985568487 M117.25372136096172 -0.07845195292511045 C117.39614583644264 1.3216943567362736, 117.38450886799978 2.1852447436986657, 117.29939871137634 3.551241869864277 M118.50657875549155 5.3623166298195315 C73.45173274806956 4.078675220705402, 31.242721472703224 5.1202490432037235, -0.268763592466712 1.982460460595746 M116.82965801462638 2.721234327152331 C88.05047287873637 3.7607098516642923, 57.32394105552946 3.526261269730317, 0.3698272807523608 3.4025016184597803 M-0.2832299080721211 3.512646083509471 C0.04820170466607624 2.140951809117664, -0.1499479836098199 1.199887624538762, 0.19676403945076032 -0.3223216037442839 M-0.166182294337272 3.589877156058053 C0.03064971805667819 2.3797309490305603, -0.0220911125960356 1.3597497097084106, -0.03474557313898158 0.11343144076098646" stroke="transparent" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(171.98678477545945 193.64008378937461) rotate(0 58.68576547639498 1.7349162106253004)"&gt;&lt;path d="M0 0 C0 0, 0 0, 0 0 M0 0 C0 0, 0 0, 0 0 M1.5496313135278277 5.473077627025326 C1.8041889953541403 3.3222775322096867, 3.573226905142686 2.27395172817741, 4.543751621017701 0.773852584682148 M1.0224603735049123 5.0046317808663145 C2.3714417688546003 3.380398390567686, 4.114311182156894 1.4028800390905802, 5.252810842780864 0.17154631436217965 M6.757802921449137 4.6840200547695305 C8.12802325109653 3.5303314664290615, 8.36767268072615 2.106171724215097, 10.412024738033248 0.1447405004024268 M6.425053895646077 4.555795515292153 C7.578679206337695 3.2857347888230253, 9.118981370285185 1.7691265160876033, 10.73426932082413 0.21299639903826129 M11.108583598829645 4.957452328544612 C12.536417982560444 2.9514913460508447, 13.938707273274149 2.4846448844313156, 15.13768353583612 0.15992368207545782 M11.51633850705235 4.69462714674374 C12.664548763941191 3.46526462133957, 13.837557047201473 2.018419082464015, 15.475888004343439 0.22782299890415575 M17.28311606185349 4.973231410951904 C17.769502642766962 3.75660801451212, 18.58719204681026 2.5590070962577807, 21.581753543110455 -0.5774987201974271 M17.48598048347818 4.241727656377098 C18.02900129175458 3.1916020842890767, 19.129228050942032 2.073420519990473, 21.369579262843246 0.1578185013658825 M22.372877223242792 5.373607272983821 C24.067690315387434 3.4674989111960253, 24.19975508240954 1.5749577063037066, 26.696844485078365 -0.28328454792380897 M22.286812328466002 4.73507150248933 C23.395919970451203 2.997822893112974, 24.929850103831985 1.7965462466877322, 26.179796693615973 0.1027737709705474 M28.359400424181676 4.394296252241697 C29.5258743163568 2.5711195700122937, 31.174161275708137 0.46291838454354783, 31.40687251542399 -0.16097527600235895 M28.217680005270246 4.607954978736457 C29.613302349483597 2.8274398650160477, 30.68596896777838 0.9370773779185669, 32.02638235366607 -0.40157349237583234 M33.09705320239227 4.970907694717953 C34.44766841923304 3.577338637797762, 36.049257319073526 1.2779092863197734, 36.989837984369444 -0.2954693709406534 M32.70934570493336 4.542297855915586 C33.70346553646172 4.018012097293633, 34.568857342083476 2.730824153842902, 37.070469527785974 0.2697628883738538 M38.39275952405869 4.370017313013913 C39.817087930006984 3.147397724602528, 41.68418044625365 1.9742387868995972, 42.957547670598856 0.178051067362631 M38.307335078209775 4.64659174018273 C39.349113842586846 3.4832094904887994, 40.118049634708 2.645418963569272, 42.79732996048011 -0.29103407437438306 M43.38877755190307 4.895593828122002 C45.26596717181432 3.489200741075238, 46.55239733474526 1.0054999475318303, 47.03839254933435 -0.019453052600081122 M43.457840003641934 5.009201714835 C45.05765836479117 2.9792413199714773, 46.24586824411422 1.6967081434658535, 47.50227662404093 0.12871710892656907 M49.042421877109525 4.7200723276435514 C50.13714284278094 2.53962257432617, 51.13787346390144 1.564412068115193, 52.657070024260044 0.3201955442725427 M49.49056581948426 4.076314698528417 C50.582177811967824 2.698223751630309, 51.84130984174802 1.8166509863812361, 52.973510950472864 -0.15985523681726976 M53.90884128740019 4.99078090757098 C55.15662680872018 3.52588944770854, 56.8574923340168 1.7149991980472297, 58.11736169559808 0.5988171414729201 M54.404634091779215 4.605241087060086 C55.61786081280648 2.925577693749725, 57.20486105788691 1.9154688501147907, 57.97802559956835 0.10612569044120548 M59.820740998959195 4.261014030505612 C61.21326103516876 3.833599244975956, 61.43734222468245 1.846400818523619, 64.08185559203257 -0.5570516108976236 M59.85142984628121 4.242363111586019 C60.96209137106634 3.489291293165262, 61.855155219222056 2.4336966284375525, 63.74445169083182 -0.3807664718919923 M65.27116042450865 4.396459167439873 C65.97147600227404 3.4844363766559163, 66.35026270563864 2.7100818674446314, 68.75472393878563 0.010268945653332584 M64.9205043365327 4.476436480403328 C65.77929230745399 3.4989371256619526, 67.11946000353701 2.180299701816563, 68.64896157155573 0.15504039073024528 M69.89319540523269 3.8062701559750978 C71.09104221975232 3.2496301922919617, 73.01949881284052 2.013430013531814, 74.16144538954141 0.22067483622713546 M70.72242227578812 4.2165387371680385 C71.51713077786806 2.792042437087208, 72.90918340547294 1.5874846496606514, 74.29811604874449 -0.14993477178604736 M75.04277597885344 4.549493842103691 C76.01290931820627 3.334654411632619, 77.49297406179794 2.7922077273865153, 78.93158279452203 0.4627050943161777 M75.3746646050899 4.545370902677239 C76.64684051826323 3.0305605007068785, 78.38584019419348 1.5062703680024443, 79.61476715719057 0.05507546760620982 M81.07838861155501 4.366143438426998 C82.71486364388524 3.060202731946405, 83.68101738175217 1.9264547805408179, 84.57850543241159 -0.17538487997819063 M80.9844757251925 4.0972058330869645 C81.73509933172562 2.9295732831778722, 82.66677151755786 2.0323282140692283, 85.12090539139838 -0.1869646286869454 M85.54385396605956 4.983625165937426 C86.5171332390313 3.21061390464558, 87.9267770093126 2.810574939554832, 89.48216677843223 0.20080332183643723 M86.23476626463832 4.41118986169431 C86.75934109705506 3.9083247138194688, 87.87883028871546 2.561766532147548, 90.02356247366035 0.31240767888158094 M90.51092255540279 4.890855792062061 C92.96742285923249 2.954318995901138, 93.19279115014547 2.5445152264668165, 95.08516309190587 0.19510746512465893 M90.88298559655522 4.845915261134209 C92.7716013228647 2.767952464059411, 94.48862942029632 1.1257477405770018, 95.93546691657089 0.011027870540476825 M97.247685401752 5.129468373316092 C97.66148529029721 3.6553477228953093, 98.4599325280639 2.1483478581519293, 100.29382940614128 0.06611636258912623 M96.56689818236545 4.849601365267051 C97.9988058914795 3.056295898661257, 99.59113029656756 1.3669598910908543, 100.77190513815263 0.059776282368874 M101.43138582679431 4.672040029799994 C103.70732212269553 2.9027866936301665, 105.22520240585246 1.5542206218275518, 105.62472979499904 -0.08195662630000577 M101.45548118665802 5.079156011108738 C102.60158321883347 3.399677491135645, 104.04713995002732 2.2003299914962327, 106.4301109251002 -0.12942551324353968 M106.98068630155576 4.914513698614672 C108.27269030024453 3.1130405386489484, 109.98243281326091 1.4676720679072521, 110.90704919327415 0.49707195094922973 M107.33558672545878 4.51287592120795 C108.79721821152745 2.666479191364677, 110.13557558341671 1.2179441407844966, 111.20479026785485 -0.24351696411942642 M112.03725215807447 4.9443281516500175 C114.03965602324949 2.90382984788429, 115.2755583795127 2.0516649044322177, 115.92198316044959 0.9139559153981495 M112.59683183567488 5.0149456012130145 C113.71987234189677 3.183973127707367, 115.13905644747044 1.616658765005467, 116.18854047069179 0.3018786169171321 M-0.20861296487881575 3.2884879375449243 C-0.20861296487881575 3.2884879375449243, -0.20861296487881575 3.2884879375449243, -0.20861296487881575 3.2884879375449243 M-0.20861296487881575 3.2884879375449243 C-0.20861296487881575 3.2884879375449243, -0.20861296487881575 3.2884879375449243, -0.20861296487881575 3.2884879375449243 M5.901320303915147 3.706474444760449 C4.385183230096921 1.4807903742904154, 3.00707994998702 0.9524963494863312, 1.4581134296240008 -0.42193913213264356 M6.110468422234137 3.3952938207206054 C4.519284835321317 1.5058062815707307, 2.2276239559719597 0.3076480844788293, 0.8077073599895184 -1.173326458226034 M12.42599167453919 3.0774622961427287 C10.545886225161693 2.252386674296111, 8.351882893969766 0.40043783426267265, 7.521593771573606 -0.3331843289435802 M11.75343277503599 3.3868243771153703 C10.360244536321957 1.7674192197239227, 8.5935924026065 0.5479069672946043, 7.502250354901406 -1.0592505127080676 M18.24227470344085 3.7047189619545904 C16.773512454952904 1.9056857101728453, 14.566415769362681 0.2465201287824128, 13.351606319445112 -0.6382295651948502 M18.380659081816333 3.5570475250170643 C17.298859050492904 2.241099378438929, 15.587098364818017 0.9900309184346809, 13.174832814951355 -1.3720649833912453 M24.714043870973168 3.9033734682775054 C22.90551635458559 1.7096903763849656, 20.96436713921715 1.154228193657299, 19.320962786324355 -1.6030482835118256 M24.675634097386848 3.425192683126307 C23.59777598068645 3.096329715478931, 22.327848549708058 1.9835478282457073, 19.005382314393326 -0.8880088660382264 M29.81321147906518 2.7822401362532254 C27.952821653732713 2.1707012293563404, 26.67632170465306 -0.0679429238925558, 25.069039104896106 -1.4518180481838145 M29.9436680015914 3.5490389993940914 C27.905220471451223 1.5354861369773607, 25.928982992375705 -0.1864174621032042, 24.672465739090434 -1.5447588537102648 M36.69219290398276 3.5237708861674486 C35.56141930626127 2.467210627453197, 33.86844607291691 1.5491210699596303, 31.661238932676227 -0.7447627201751073 M36.71513790165289 3.701753420961585 C34.57365203118419 1.5855967759271823, 32.99971226272929 -0.17499676926749524, 31.349184805640565 -1.3095288586411822 M42.04712568686972 3.643390080795233 C40.24933391039259 2.3598599632581334, 39.29099848753336 0.4418850844738067, 37.57942917703157 -1.3543088895669522 M42.53552618909324 3.4240329124001607 C41.1648803340786 1.7739116549130098, 39.68924941290213 0.8724840801638527, 38.07766146839102 -0.9833726773439289 M48.73815390293473 3.29557630881813 C47.486120524351904 2.262058437622486, 45.65774475760308 1.1793432611562502, 44.11914393950173 -1.6237532341088965 M48.563017827439914 3.2020760987993016 C47.208322425899 2.1920354676782825, 44.98373843425585 0.6680579287038343, 43.50174793989317 -1.4460503389760717 M54.45062064508252 3.2068550789044754 C53.20549683607919 1.3068160190439624, 51.04693044441157 -0.3616840344750778, 49.396937849160516 -0.6047715412015093 M55.23600449881793 3.8708587405053487 C53.41946334772132 2.461631063978777, 52.010979904510236 1.0876020056953983, 50.01430537199811 -0.7570766476942224 M60.321287219391586 3.1123200096670574 C59.08420252821678 1.6691017790190281, 57.50782013692981 0.19598612858253883, 55.83044440836338 -1.650395801875692 M60.9836171364667 3.3397471424207126 C59.13772490983876 1.812635793908702, 57.204311517004264 -0.21112867945197447, 55.3879618538699 -1.625783756261643 M66.92610415837777 3.8599503651415867 C65.91565710748698 2.311755896613032, 63.72408244570716 0.7859628847874712, 62.14922743087617 -1.2459475333457464 M67.02680901948412 3.4843008298115743 C66.04948720909883 2.3050157683218013, 64.69538901173289 1.8598107915824047, 61.64108277398144 -1.1966804987871669 M72.93269165263041 2.8041418934628757 C70.47141312817243 2.0702640151012237, 69.37106956630353 0.45203925498441044, 68.36408056912285 -0.2409426741283176 M73.06114062594712 3.364532863853165 C71.473465926698 2.434716996484003, 70.52698773809334 1.60011689060388, 68.31808713495073 -0.9900035967100193 M78.9590114540216 3.516443402233186 C77.5974179082453 1.781167307833925, 76.12195469938129 0.38019514087992246, 73.7185233144717 -1.57592548166674 M79.24280484721815 3.507326392323362 C77.9487338656398 2.168511862787188, 76.36213958482777 0.7914001759421838, 73.672928192017 -0.9980814919089201 M86.05778429129272 3.2092398687001427 C83.9303714551308 1.9284058212039636, 82.36081351889364 0.49457984899173374, 80.68757021866782 -0.2836444761678002 M85.62927528133727 3.650820309130114 C83.89294980219273 2.31108160634826, 82.5486286908263 0.8789816480897568, 80.26483089809349 -0.633387919569331 M91.71948413357663 3.0815832136167813 C89.6812725378638 1.8895984991376726, 87.15909297998006 0.45886441031945313, 85.4663733515096 -1.3183626127269932 M91.31669461873884 3.2289124788219707 C89.21987915121984 1.4109323420291395, 87.2293352052325 -0.09327110470597802, 85.84059113782357 -1.6515994512782313 M97.4217216696392 3.4188550447687973 C96.27860333680236 1.9690157867595879, 94.5381270167516 0.6212285750806978, 92.82678168301287 -1.618624064808619 M97.45252981039576 3.758617504790099 C96.36432061704319 2.610653660427744, 95.66766403537534 1.67894168428773, 92.0497754715551 -1.0854573430194534 M104.62768587882803 4.346365351720374 C103.17522492820228 2.958526573484984, 101.97990963153643 1.646821347638308, 98.05434747377515 -0.36116962409849773 M104.07573430098766 3.7917514190791506 C102.5309047293229 2.252128035367595, 101.0825101110835 1.272451088824185, 98.74437445334263 -0.8688389221233466 M109.8533977717406 3.4444045927856934 C108.28785705481556 2.8774044752804344, 107.28213956994992 2.0580263075750826, 103.71558001564402 -0.6458846135324524 M109.85431426956904 3.5846712693587763 C108.59833255067122 2.291277225170618, 107.39064101477724 1.3414233558028104, 104.0663688716158 -1.4170180002952666 M115.8349641308928 3.8632957820004536 C114.43959227990644 2.3468424727580683, 112.1445960855381 0.6686766068955579, 110.52519937238534 -0.6807468003247664 M116.37289487023783 3.818014705895142 C114.3399007294772 2.7134725891800655, 113.51454171518402 1.3602773198976832, 110.48529598984746 -1.1569252866440252" stroke="#ced4da" stroke-width="0.5" fill="none"&gt;&lt;/path&gt;&lt;path d="M-1.9331182036548853 -1.5488086249679327 C48.04332697501148 1.1244419200741358, 94.34437809545031 0.5591568930470057, 115.67344381746011 -0.3641095068305731 M0.06060642469674349 -0.4672734634950757 C35.86487851623516 1.1374610717570603, 71.5710718800514 -0.10290619179846627, 117.92995396994161 -0.3625390725210309 M117.20077480492309 -0.3300802430161512 C117.50975789451323 0.9409259159956732, 117.30657832729707 2.0864318562106643, 117.4385855300962 3.6016510285692163 M117.29307899986485 0.020884904899346246 C117.48595871523031 1.0874974503650356, 117.1767407684989 2.08436678640348, 117.45294040140362 3.6001093857161686 M119.26401516135888 3.5750642521008444 C90.2575159188685 6.057194503843591, 64.5393677353381 3.86718032032232, -1.4873719606548548 2.537919650755782 M116.62293285869168 3.891232203662355 C83.66954664813241 2.2711536735793363, 50.344216963757205 3.203655138661505, -0.0673308027908206 3.3446509055494857 M0.042813662258870344 3.4959332223242123 C-0.33332569976136206 2.4778428435977857, -0.2768760461371855 2.0267150798003004, -0.3223216037442839 -0.05755745263404327 M0.12004473480745206 3.5290828232650275 C-0.11340075576919306 2.149385426659648, -0.1499897213502056 0.781173716206288, 0.11343144076098646 0.053516240129377585" stroke="transparent" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;g transform="translate(21.200666307713618 196.15588838707583) rotate(0 66.45875398214207 3.3441116129241664)"&gt;&lt;path d="M-1.0960838105529547 -1.0616192016750574 L131.49702390758785 -0.18590078689157963 L131.30062392084392 6.078562322856442 L-0.9414483215659857 6.0362929141535915" stroke="none" stroke-width="0" fill="#f41d92"&gt;&lt;/path&gt;&lt;path d="M0.07504191063344479 -1.6519318129867315 C48.6717207366297 2.1251636629523842, 98.76146253564842 0.7274964337768167, 134.73962012781294 -0.8237543012946844 M-0.14539700280874968 -0.45872258115559816 C42.57345630329015 -0.7780584158653236, 87.34047111604188 -0.21417305490913163, 132.42589526633265 -0.6365428166463971 M133.34979469917167 -0.34348489165605 C132.676242273321 1.9144993480679706, 133.07805235207408 3.4745177784773227, 133.2153963306507 6.377620532226333 M132.870245864389 -0.035881700723496945 C133.18692599393896 2.315616917464927, 133.21328781639215 5.2897618465496015, 132.72819011305828 6.914442500883744 M133.58270917190703 8.615716390164152 C92.31425296018838 8.108846602087727, 54.408854838679716 6.13039552891802, 0.9311732854694128 7.353886119873778 M133.51983698467257 5.712366395482377 C92.0379204296055 7.022827596903826, 50.62717563509753 7.167296530247714, -0.04139300715178251 6.606375002154664 M0.10716979992885733 7.209792720621895 C-0.48025882347219 5.5130160182701164, 0.537929206635043 3.982754117283542, 0.16160612139750152 -0.25448465069912857 M-0.2701862171989351 6.564623220302453 C0.2565257148885263 5.052346940938092, -0.10389336805681824 3.9692018223104717, 0.04134105659023152 -0.19166792607665015" stroke="transparent" stroke-width="1" fill="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;/svg&gt;
&lt;figcaption&gt;Transactions table with FK to purchasing and cancelling user&lt;/figcaption&gt;&lt;/p&gt;
&lt;/figure&gt;
&lt;p&gt;We expected the index on the cancelling user to be significantly smaller than the index on the purchasing user, but they were exactly the same. Coming from Oracle, I was always taught that &lt;a href="https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/CREATE-INDEX.html#GUID-1F89BBC0-825F-4215-AF71-7588E31D8BFE" rel="noopener"&gt;NULLs are not indexed&lt;/a&gt;, but in PostgreSQL they are! This "Aha" moment led us to the realization that &lt;strong&gt;we were indexing a lot of unnecessary values for no reason&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;This was the original index we had for the cancelling user:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;INDEX&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;transaction_cancelled_by_ix&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;transactions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cancelled_by_user_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;To check our thesis, we replaced the index with a partial index that excludes null values:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;DROP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;INDEX&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;transaction_cancelled_by_ix&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;INDEX&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;transaction_cancelled_by_part_ix&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;transactions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cancelled_by_user_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cancelled_by_user_id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;IS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NOT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The full index after we reindexed it was 769MB in size, with more than 99% null values. The partial index that excluded null values was less than 5MB. That's more than 99% percent of dead weight shaved off the index!&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Index&lt;/th&gt;
&lt;th&gt;Size&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Full index&lt;/td&gt;
&lt;td&gt;769MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Partial Index&lt;/td&gt;
&lt;td&gt;5MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Difference&lt;/td&gt;
&lt;td&gt;-99%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;To make sure those NULL values were indeed unnecessary, we reset the stats on the table and waited a while. Not long after, we observed that the index is being used just like the old one! &lt;strong&gt;We just shaved off more than 760MB of unused indexed tuples without compromising performance!&lt;/strong&gt;&lt;/p&gt;
&lt;figure&gt;&lt;img alt="Clearing space, play by play" src="https://hakibenita.com/images/01-postgresql-unused-index-size.png"&gt;&lt;figcaption&gt;Clearing space, play by play&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="utilizing-partial-indexes"&gt;&lt;a class="toclink" href="#utilizing-partial-indexes"&gt;Utilizing Partial Indexes&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Once we had a good experience with one partial index, we figured we might have more indexes like that. To find good candidates for partial index we wrote a query to search for indexes on fields with high &lt;code&gt;null_frac&lt;/code&gt;, the percent of values of the column that PostgreSQL estimates are NULL:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;-- Find indexed columns with high null_frac&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;oid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relname&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;pg_size_pretty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pg_relation_size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;oid&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;index_size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;indisunique&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;unique&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attname&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;indexed_column&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;CASE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;null_frac&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;WHEN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;THEN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;ELSE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;to_char&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;null_frac&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;999.00%&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;null_frac&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;pg_size_pretty&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;pg_relation_size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;oid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;null_frac&lt;/span&gt;&lt;span class="p"&gt;)::&lt;/span&gt;&lt;span class="nb"&gt;bigint&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;expected_saving&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;-- Uncomment to include the index definition&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;--, ixs.indexdef&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;pg_class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;JOIN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg_index&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;indexrelid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;oid&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;JOIN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg_attribute&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attrelid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;oid&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;JOIN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg_class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c_table&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c_table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;oid&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;indrelid&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;JOIN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg_indexes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ixs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relname&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ixs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;indexname&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;LEFT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;JOIN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg_stats&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tablename&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;c_table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relname&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AND&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attname&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attname&lt;/span&gt;

&lt;span class="k"&gt;WHERE&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;-- Primary key cannot be partial&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;NOT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;indisprimary&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;-- Exclude already partial indexes&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;AND&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;indpred&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;IS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NULL&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;-- Exclude composite indexes&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;AND&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;array_length&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;indkey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;-- Larger than 10MB&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;AND&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pg_relation_size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;oid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;

&lt;span class="k"&gt;ORDER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;pg_relation_size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;oid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;null_frac&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The results of this query can look like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;oid&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="k"&gt;index&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;index_size&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;unique&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;indexed_column&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;null_frac&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;expected_saving&lt;/span&gt;
&lt;span class="c1"&gt;---------+--------------------+------------+--------+----------------+-----------+-----------------&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mf"&gt;138247&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tx_cancelled_by_ix&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1418&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;MB&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cancelled_by&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="mf"&gt;96.15&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1363&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;MB&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="mf"&gt;16988&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tx_op1_ix&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1651&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;MB&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;op1&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="mf"&gt;6.11&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;101&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;MB&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1473377&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tx_token_ix&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;22&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;MB&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="mf"&gt;11.21&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;2494&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;kB&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mf"&gt;138529&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tx_op_name_ix&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1160&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;MB&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;name&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;bytes&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;In the table above we can identify several types of results:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;tx_cancelled_by_ix&lt;/code&gt; is a large index with many null values: great potential here!&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tx_op_1_ix&lt;/code&gt; is a large index with few null values: there's not much potential&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tx_token_ix&lt;/code&gt; is a small index with few null values: I wouldn't bother with this index&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tx_op_name_ix&lt;/code&gt; is a large index with no null values: nothing to do here&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The results show that by turning &lt;code&gt;tx_cancelled_by_ix&lt;/code&gt; into a partial index that excludes null we can potentially save ~1.3GB.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Is it always beneficial to exclude nulls from indexes?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;No. &lt;code&gt;NULL&lt;/code&gt; is as meaningful as any other value. If your queries are searching for null values using &lt;code&gt;IS NULL&lt;/code&gt;, these queries might benefit from an index on &lt;code&gt;NULL&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;So is this method beneficial only for null values?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Using partial indexes to exclude values that are not queried very often or not at all can be beneficial for any value, not just null values. &lt;code&gt;NULL&lt;/code&gt; usually indicate a lack of value, and in our case not many queries were searching for null values, so it made sense to exclude them from the index.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;So how did you end up clearing more than 20GB?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;You may have noticed that the title mentions more than 20GB of free space but the charts only show half, well... indexes are also dropped from replications! When you release 10GB from your primary database, you also release roughly the same amount of storage from each replica.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="bonus-migrating-with-django-orm"&gt;&lt;a class="toclink" href="#bonus-migrating-with-django-orm"&gt;Bonus: Migrating with Django ORM&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This story is taken from a large application built with Django. To put the above techniques to practice with Django, there are several things to note.&lt;/p&gt;
&lt;h3 id="prevent-implicit-creation-of-indexes-on-foreign-keys"&gt;&lt;a class="toclink" href="#prevent-implicit-creation-of-indexes-on-foreign-keys"&gt;Prevent Implicit Creation of Indexes on Foreign Keys&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Unless you explicitly set &lt;code&gt;db_index=False&lt;/code&gt;, &lt;a href="9-django-tips-for-working-with-databases#fk-indexes"&gt;Django will implicitly create a B-Tree index on a &lt;code&gt;models.ForeignKey&lt;/code&gt; field&lt;/a&gt;. Consider the following example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.contrib.auth.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Transaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;span class="hll"&gt;    &lt;span class="n"&gt;cancelled_by_user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;        &lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;null&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;on_delete&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CASCADE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The model is used to keep track of transaction data. If a transaction is cancelled, we keep a reference to user that cancelled it. As previously described, most transactions don't end up being cancelled, so we set &lt;code&gt;null=True&lt;/code&gt; on the field.&lt;/p&gt;
&lt;p&gt;In the &lt;code&gt;ForeignKey&lt;/code&gt; definition above we did not explicitly set &lt;code&gt;db_index&lt;/code&gt;, so Django will implicitly create a full index on the field. To create a partial index instead, make the following changes:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.contrib.auth.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Transaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# ...&lt;/span&gt;
    &lt;span class="n"&gt;cancelled_by_user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;null&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;on_delete&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CASCADE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="hll"&gt;        &lt;span class="n"&gt;db_index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&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;indexes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="hll"&gt;            &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;                &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;cancelled_by_user_id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;                &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="si"&gt;%(class_name)s&lt;/span&gt;&lt;span class="s1"&gt;_cancelled_by_part_ix&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;                &lt;span class="n"&gt;condition&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Q&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cancelled_by_user_id__isnull&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;            &lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;        &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We first tell Django not to create the index on the FK field, and then add a partial index using &lt;a href="https://docs.djangoproject.com/en/3.1/ref/models/indexes/" rel="noopener"&gt;&lt;code&gt;models.Index&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;div class="admonition tip"&gt;
&lt;p class="admonition-title"&gt;take away&lt;/p&gt;
&lt;p&gt;Nullable foreign keys are good candidates for a partial index!&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;To prevent implicit features such as this one from sneaking indexes without us noticing, &lt;a href="/automating-the-boring-stuff-in-django-using-the-check-framework"&gt;we create Django checks&lt;/a&gt; to force ourselves to &lt;a href="/automating-the-boring-stuff-in-django-using-the-check-framework#h008-must-set-db_index-explicitly-on-a-foreignkey-field"&gt;always explicitly set &lt;code&gt;db_index&lt;/code&gt; in foreign keys&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="migrate-exiting-full-indexes-to-partial-indexes"&gt;&lt;a class="toclink" href="#migrate-exiting-full-indexes-to-partial-indexes"&gt;Migrate Exiting Full Indexes to Partial Indexes&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;One of the challenges we were facing during this migration is to replace the existing full indexes with partial indexes without causing downtime or degraded performance during the migration. After we identified the full indexes we want to replace, we took the following steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Replace full indexes with partial indexes&lt;/strong&gt;: Adjust the relevant Django models and replace full indexes with partial indexes, as demonstrated above. The migration Django generates will first disable the FK constraint (if the field is a foreign key), drop the existing full index and create the new partial index. Executing this migration may cause both downtime and degraded performance, so we won't actually run it.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Create the partial indexes manually&lt;/strong&gt;: Use Django's &lt;a href="https://docs.djangoproject.com/en/3.1/ref/django-admin/#django-admin-sqlmigrate" rel="noopener"&gt;&lt;code&gt;./manage.py sqlmigrate&lt;/code&gt;&lt;/a&gt; utility to produce a script for the migration, extract only the &lt;code&gt;CREATE INDEX&lt;/code&gt; statements and adjust them to create the indexes &lt;code&gt;CONCURRENTLY&lt;/code&gt;. Then, create the indexes manually and concurrently in the database. Since the full indexes are not dropped yet, they can still be used by queries so performance should not be impacted in the process. It is possible to &lt;a href="how-to-create-django-index-without-downtime"&gt;create indexes concurrently in Django migrations&lt;/a&gt;, but this time we decided it's best to do it manually.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Reset full index statistics counters&lt;/strong&gt;: To make sure it's safe to drop the full indexes, we wanted to first make sure the new partial indexes are being used. To keep track of their use we reset the counters for the full indexes using &lt;a href="https://www.postgresql.org/docs/current/monitoring-stats.html#MONITORING-STATS-FUNCTIONS" rel="noopener"&gt;&lt;code&gt;pg_stat_reset_single_table_counters(&amp;lt;full index oid&amp;gt;)&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Monitor use of partial indexes&lt;/strong&gt;: After resetting the stats we monitored both overall query performance and the partial index usage by observing the values of &lt;code&gt;idx_scan&lt;/code&gt;, &lt;code&gt;idx_tup_read&lt;/code&gt; and &lt;code&gt;idx_tup_fetch&lt;/code&gt; in the &lt;a href="https://www.postgresql.org/docs/13/monitoring-stats.html#MONITORING-PG-STAT-ALL-INDEXES-VIEW" rel="noopener"&gt;&lt;code&gt;pg_stat_all_indexes&lt;/code&gt;&lt;/a&gt; tables, for both the partial and the full indexes.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Drop the full indexes&lt;/strong&gt;: Once we were convinced the partial indexes are being used, we dropped the full indexes. This is a good point to check the sizes of both partial and full indexes to find out exactly how much storage you  are about to free.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Fake the Django migration&lt;/strong&gt;: Once the database state was effectively in-sync with the model state, we fake the migration using &lt;code&gt;./manage.py migrate --fake&lt;/code&gt;. When faking a migration, Django will register the migration as executed, but it won't actually execute anything. This is useful for situations like this when you need better control over a migration process. Note that on other environments such as dev, QA or staging where there is not downtime considerations, the Django migrations will execute normally and the full indexes will be replaced with the partial ones. For more advanced Django migration operations such as "fake", check out &lt;a href="move-django-model"&gt;How to Move a Django Model to Another App&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h2 id="conclusion"&gt;&lt;a class="toclink" href="#conclusion"&gt;Conclusion&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Optimizing disks, storage parameters and configuration can only affect performance so much. At some point, to squeeze that final drop of performance you need to make changes to the underlying objects. In this case, it was the index definition.&lt;/p&gt;
&lt;p&gt;To sum up the process we took to clear an much storage as we could:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Remove unused indexes&lt;/li&gt;
&lt;li&gt;Repack tables and indexes (and activate B-Tree deduplication when possible)&lt;/li&gt;
&lt;li&gt;Utilize partial indexes to index only what's necessary&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Hopefully, after applying these techniques you can gain a few more days before you need to reach into your pocket and provision more storage.&lt;/p&gt;</content><category term="articles"></category><category term="PostgreSQL"></category><category term="Django"></category><category term="ORM"></category></entry><entry><title>Exhaustiveness Checking with Mypy</title><link href="https://hakibenita.com/python-mypy-exhaustive-checking" rel="alternate"></link><published>2020-12-08T00:00:00+02:00</published><updated>2020-12-08T00:00:00+02:00</updated><author><name>Haki Benita</name></author><id>tag:hakibenita.com,2020-12-08:/python-mypy-exhaustive-checking</id><summary type="html">&lt;p&gt;What if mypy could warn you about possible problems at "compile time"? In this article I share a little trick to get mypy to fail when a value in an enumeration type is left unhandled.&lt;/p&gt;</summary><content type="html">&lt;hr&gt;
&lt;p&gt;&lt;a href="https://mypy-lang.org/" rel="noopener"&gt;Mypy&lt;/a&gt; is an optional static type checker for Python. It's been around since 2012 and is gaining traction even since. One of the main benefits of using a type checker is getting errors at "compile time" rather than at run time.&lt;/p&gt;
&lt;p&gt;Exhaustiveness checking is a common feature of type checkers, and a very useful one! In this article I'm going to show you &lt;strong&gt;how you can get mypy to perform exhaustiveness checking!&lt;/strong&gt;&lt;/p&gt;
&lt;figure&gt;&lt;img alt="Playing cards are also useful for explaining enumeration types...&amp;lt;br&amp;gt;&amp;lt;small&amp;gt;Photo by &amp;lt;a href=&amp;quot;https://unsplash.com/photos/G6wlppP4EN8&amp;quot;&amp;gt;Daniel Rykhev&amp;lt;/a&amp;gt;&amp;lt;/small&amp;gt;" src="https://hakibenita.com/images/01-python-mypy-exhaustive-checking.png"&gt;&lt;figcaption&gt;Playing cards are also useful for explaining enumeration types...&lt;br&gt;&lt;small&gt;Photo by &lt;a href="https://unsplash.com/photos/G6wlppP4EN8"&gt;Daniel Rykhev&lt;/a&gt;&lt;/small&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;details class="toc-container" open&gt;
    &lt;summary&gt;Table of Contents&lt;/summary&gt;&lt;/p&gt;
&lt;div class="toc"&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#exhaustiveness-checking"&gt;Exhaustiveness Checking&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#enumeration-types"&gt;Enumeration types&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#type-narrowing-in-mypy"&gt;Type Narrowing in Mypy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#the-future"&gt;The Future&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#bonus-exhaustiveness-checking-in-django"&gt;Bonus: Exhaustiveness Checking in Django&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#updates"&gt;Updates&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;p&gt;&lt;/details&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="exhaustiveness-checking"&gt;&lt;a class="toclink" href="#exhaustiveness-checking"&gt;Exhaustiveness Checking&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Say you have a system to manage orders. To represent the status of an order, you have the following enum:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;enum&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Enum&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;Ready&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;ready&amp;#39;&lt;/span&gt;
    &lt;span class="n"&gt;Shipped&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;shipped&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;You also have the following code to process an &lt;code&gt;Order&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle_order&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;OrderStatus&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;OrderStatus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Ready&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ship order&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;OrderStatus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Shipped&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;charge order&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;When the order is ready, you ship it; and when it's shipped, you charge it.&lt;/p&gt;
&lt;p&gt;A few months go by and your system becomes big. So big in fact, that you can no longer ship orders immediately, and you add a new status:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;enum&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Enum&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;Ready&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;ready&amp;#39;&lt;/span&gt;
&lt;span class="hll"&gt;    &lt;span class="n"&gt;Scheduled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;scheduled&amp;#39;&lt;/span&gt;
&lt;/span&gt;    &lt;span class="n"&gt;Shipped&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;shipped&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Before you push this change to production, you run a quick check with mypy to make sure everything is OK:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;mypy&lt;span class="w"&gt; &lt;/span&gt;main.py
Success:&lt;span class="w"&gt; &lt;/span&gt;no&lt;span class="w"&gt; &lt;/span&gt;issues&lt;span class="w"&gt; &lt;/span&gt;found&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;source&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;file
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Mypy does not see anything wrong in this code, Do you? The problem is that &lt;strong&gt;you forgot to handle the new status in your function&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;One way to make sure you always handle all possible order statuses is to add an assert, or throw an exception:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle_order&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;OrderStatus&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;OrderStatus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Ready&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ship order&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;OrderStatus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Shipped&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;charge order&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="hll"&gt;    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Unhandled status &amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;&amp;quot;&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Now, when you execute the function with the new status &lt;code&gt;OrderStatus.Scheduled&lt;/code&gt;, you will get a runtime error:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;handle_order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OrderStatus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Scheduled&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="ne"&gt;AssertionError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Unhandled&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;OrderStatus.Scheduled&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Another way to with deal with cases like this is to go over your test suite and add scenarios in all the places that use order status. But... if you forgot to change the function when you added the status, what are the chances you'll remember to update the tests? That's not a good solution...&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Exhaustiveness Checking in Mypy&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;What if mypy could warn you at "compile time" about such cases? Well... it can, using this little magic function:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;NoReturn&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;enum&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;assert_never&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;NoReturn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;NoReturn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Unhandled value: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt; (&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;)&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Before you dig into the implementation, try to use it to see how it works. In the function above, place &lt;code&gt;assert_never&lt;/code&gt; after you handled all the possible order statuses, where you previously used &lt;code&gt;assert&lt;/code&gt; or raises an exception:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle_order&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;OrderStatus&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;OrderStatus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Ready&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ship order&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;OrderStatus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Shipped&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;charge order&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="hll"&gt;    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;        &lt;span class="n"&gt;assert_never&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&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Now, check the code with Mypy:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;mypy&lt;span class="w"&gt; &lt;/span&gt;main.py
error:&lt;span class="w"&gt; &lt;/span&gt;Argument&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;assert_never&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;has&lt;span class="w"&gt; &lt;/span&gt;incompatible&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Literal[OrderStatus.Scheduled]&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
expected&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;NoReturn&amp;quot;&lt;/span&gt;
Found&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;error&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;file&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;checked&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;source&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;file&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Amazing! &lt;strong&gt;Mypy warns you about a status you forgot to handle!&lt;/strong&gt; The message also includes the value, &lt;code&gt;OrderStatus.Scheduled&lt;/code&gt;. If you use a modern editor such as VSCode you can get these warnings immediately as you type:&lt;/p&gt;
&lt;figure&gt;&lt;img alt="mypy Error in VSCode" src="https://hakibenita.com/images/00-python-mypy-exhaustive-checking.png"&gt;&lt;figcaption&gt;mypy Error in VSCode&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;You can now go ahead and fix your function to handle the missing status:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle_order&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;OrderStatus&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;OrderStatus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pending&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;schedule order&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="hll"&gt;    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;OrderStatus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Scheduled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;        &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ship order&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;OrderStatus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Shipped&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;charge order&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;assert_never&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;/pre&gt;&lt;/div&gt;


&lt;p&gt;Check with mypy again:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;mypy&lt;span class="w"&gt; &lt;/span&gt;main.py
Success:&lt;span class="w"&gt; &lt;/span&gt;no&lt;span class="w"&gt; &lt;/span&gt;issues&lt;span class="w"&gt; &lt;/span&gt;found&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;source&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;file
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Great! You can now rest assure you handled all order statuses. The best part is that you did that with &lt;strong&gt;no unit tests&lt;/strong&gt;, and there were &lt;strong&gt;no runtime errors&lt;/strong&gt;. If you include mypy in your CI, the &lt;strong&gt;bad code will never make it into production&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="enumeration-types"&gt;&lt;a class="toclink" href="#enumeration-types"&gt;Enumeration types&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In the previous section you used mypy to perform exhaustiveness check on an &lt;code&gt;Enum&lt;/code&gt;. You can use mypy, and &lt;code&gt;assert_never&lt;/code&gt; to perform exhaustiveness check on other enumeration types as well.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Exhaustiveness Checking of a Union&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A &lt;code&gt;Union&lt;/code&gt; type represents several possible types. For example, a function that casts an argument to &lt;code&gt;float&lt;/code&gt; can look like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Union&lt;/span&gt;
&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Union&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;assert_never&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Check the function with mypy:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;mypy&lt;span class="w"&gt; &lt;/span&gt;main.py
error:&lt;span class="w"&gt; &lt;/span&gt;Argument&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;assert_never&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;has&lt;span class="w"&gt; &lt;/span&gt;incompatible&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;float&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;expected&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;NoReturn&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Whoops... you forgot to handle the &lt;code&gt;float&lt;/code&gt; type in the code:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Union&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Union&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="hll"&gt;    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="nb"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;num&lt;/span&gt;
&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;assert_never&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Check again:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;mypy&lt;span class="w"&gt; &lt;/span&gt;main.py
Success:&lt;span class="w"&gt; &lt;/span&gt;no&lt;span class="w"&gt; &lt;/span&gt;issues&lt;span class="w"&gt; &lt;/span&gt;found&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;source&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;file
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Great! mypy is happy...&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Exhaustiveness Checking of a Literal&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Another useful type is &lt;code&gt;Literal&lt;/code&gt;. It is included in the built-in &lt;code&gt;typing&lt;/code&gt; module since Python3.8, and prior to that it is part of the complementary &lt;a href="https://pypi.org/project/typing-extensions/" rel="noopener"&gt;&lt;code&gt;typing_extensions&lt;/code&gt; package&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;A &lt;code&gt;Literal&lt;/code&gt; is used to type primitive values such as strings and numbers. &lt;code&gt;Literal&lt;/code&gt; is also an enumeration type, so you can use exhaustiveness checking on it as well:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;typing_extensions&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Literal&lt;/span&gt;

&lt;span class="n"&gt;Color&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Literal&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;R&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;G&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;B&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_color_name&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;color&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;R&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Red&amp;#39;&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;color&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;G&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Green&amp;#39;&lt;/span&gt;
    &lt;span class="c1"&gt;# elif color == &amp;#39;B&amp;#39;:&lt;/span&gt;
    &lt;span class="c1"&gt;#     return &amp;#39;Blue&amp;#39;&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;assert_never&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Checking the code without the commented part will produce the following error:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;mypy&lt;span class="w"&gt; &lt;/span&gt;main.py
error:&lt;span class="w"&gt; &lt;/span&gt;Argument&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;to&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;assert_never&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;has&lt;span class="w"&gt; &lt;/span&gt;incompatible&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Literal[&amp;#39;B&amp;#39;]&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;expected&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;NoReturn&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Very handy indeed!&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="type-narrowing-in-mypy"&gt;&lt;a class="toclink" href="#type-narrowing-in-mypy"&gt;Type Narrowing in Mypy&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Now that you've seen what &lt;code&gt;assert_never&lt;/code&gt; can do, you can try and understand how it works. &lt;code&gt;assert_never&lt;/code&gt; works alongside &lt;strong&gt;"type narrowing"&lt;/strong&gt;, which is a mypy feature where the type of a variable is narrowed based on the control flow of the program. In other words, mypy is gradually eliminating possible types for a variable.&lt;/p&gt;
&lt;p&gt;First, it's important to understand how various things translate to a &lt;code&gt;Union&lt;/code&gt; type in mypy:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="c1"&gt;# Equivalent to Union[int, None]&lt;/span&gt;

&lt;span class="n"&gt;Literal&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;string&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="c1"&gt;# Equivalent to Union[Literal[&amp;#39;string&amp;#39;], Literal[42], Literal[True]]&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Suit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Enum&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;Clubs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;♣&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;Diamonds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;♦&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;Hearts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;♥&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;Spades&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;♠&amp;quot;&lt;/span&gt;

&lt;span class="n"&gt;Suit&lt;/span&gt;
&lt;span class="c1"&gt;# ~Equivalent to Union[&lt;/span&gt;
&lt;span class="c1"&gt;#   Literal[Suit.Clubs],&lt;/span&gt;
&lt;span class="c1"&gt;#   Literal[Suit.Diamonds],&lt;/span&gt;
&lt;span class="c1"&gt;#   Literal[Suit.Hearts],&lt;/span&gt;
&lt;span class="c1"&gt;#   Literal[Suit.Spades]&lt;/span&gt;
&lt;span class="c1"&gt;# ]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;To display the type of an expression, mypy provides a useful utility called &lt;a href="https://mypy.readthedocs.io/en/stable/common_issues.html#reveal-type" rel="noopener"&gt;&lt;code&gt;reveal_type&lt;/code&gt;&lt;/a&gt;. Using &lt;code&gt;reveal_type&lt;/code&gt; you can ask mypy to show you the inferred type for a variable at the point it's called:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;describe_suit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;suit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Suit&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="hll"&gt;    &lt;span class="c1"&gt;# Revealed type is Union[Suit, None]&lt;/span&gt;
&lt;/span&gt;    &lt;span class="n"&gt;reveal_type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;suit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;In the function above, the reveled type of &lt;code&gt;suit&lt;/code&gt; is &lt;code&gt;Union[Suit, None]&lt;/code&gt;, which is the type of the argument &lt;code&gt;suit&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;At this point you haven't done anything in the function, so mypy is unable to narrow down the type. Next, add some logic and see how mypy narrows down the type of the variable &lt;code&gt;suit&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;describe_suit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;suit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Suit&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="hll"&gt;    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;suit&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;    &lt;span class="c1"&gt;# Revealed type is Suit&lt;/span&gt;
&lt;/span&gt;    &lt;span class="n"&gt;reveal_type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;suit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;After eliminating the option of suit being &lt;code&gt;None&lt;/code&gt;, the revealed type is &lt;code&gt;Suit&lt;/code&gt;. Mypy used your program's logic to narrow the type of the variable.&lt;/p&gt;
&lt;p&gt;Keep in mind, the type &lt;code&gt;Suit&lt;/code&gt; is equivalent to the type &lt;code&gt;Union[Literal[Suit.Clubs], Literal[Suit.Diamonds], Literal[Suit.Hearts], Literal[Suit.Spades]]&lt;/code&gt;, so next, try to narrow down the type even more:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;describe_suit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;suit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Suit&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;suit&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;

&lt;span class="hll"&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;suit&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;Suit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Clubs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;        &lt;span class="c1"&gt;# Revealed type is Literal[Suit.Clubs]&lt;/span&gt;
&lt;/span&gt;        &lt;span class="n"&gt;reveal_type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;suit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Clubs&amp;quot;&lt;/span&gt;

&lt;span class="hll"&gt;    &lt;span class="c1"&gt;# Revealed type is Literal[Suit.Diamonds, Suit.Hearts, Suit.Spades]&lt;/span&gt;
&lt;/span&gt;    &lt;span class="n"&gt;reveal_type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;suit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;After checking if &lt;code&gt;suit&lt;/code&gt; is &lt;code&gt;Suit.Clubs&lt;/code&gt;, mypy is able to narrow down the type to &lt;code&gt;Suit.Clubs&lt;/code&gt;. Mypy is also smart enough to understand that if the condition does not hold, the variable &lt;em&gt;is definitely not&lt;/em&gt; &lt;code&gt;Clubs&lt;/code&gt;, and narrows down the type to &lt;code&gt;Diamonds&lt;/code&gt;, &lt;code&gt;Hearts&lt;/code&gt; or &lt;code&gt;Spades&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Mypy can also use other conditional statements to further narrow the type, for example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;describe_suit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;suit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Suit&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;suit&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;suit&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;Suit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Clubs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Revealed type is Literal[Suit.Clubs]&lt;/span&gt;
        &lt;span class="n"&gt;reveal_type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;suit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Clubs&amp;quot;&lt;/span&gt;

    &lt;span class="c1"&gt;# Revealed type is Literal[Suit.Diamonds, Suit.Hearts, Suit.Spades]&lt;/span&gt;
    &lt;span class="n"&gt;reveal_type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;suit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="hll"&gt;    &lt;span class="c1"&gt;# `and`, `or` and `not` also work.&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;suit&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;Suit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Diamonds&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;suit&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;Suit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Spades&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;        &lt;span class="c1"&gt;# Revealed type is Literal[Suit.Diamonds, Suit.Spades]&lt;/span&gt;
&lt;/span&gt;        &lt;span class="n"&gt;reveal_type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;suit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Diamonds or Spades&amp;quot;&lt;/span&gt;

&lt;span class="hll"&gt;    &lt;span class="c1"&gt;# Revealed type is Literal[Suit.Hearts]&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;    &lt;span class="n"&gt;reveal_type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;suit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;By the end of the function, mypy narrowed down the type of &lt;code&gt;suit&lt;/code&gt; to &lt;code&gt;Suit.Hearts&lt;/code&gt;. If, for example, you add a condition that imply a different type for &lt;code&gt;suit&lt;/code&gt;, mypy will issue an error:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;describe_suit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;suit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Suit&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;suit&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;suit&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;Suit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Clubs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Revealed type is Literal[Suit.Clubs]&lt;/span&gt;
        &lt;span class="n"&gt;reveal_type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;suit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Clubs&amp;quot;&lt;/span&gt;

    &lt;span class="c1"&gt;# Revealed type is Literal[Suit.Diamonds, Suit.Hearts, Suit.Spades]&lt;/span&gt;
    &lt;span class="n"&gt;reveal_type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;suit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# `and`, `or` and `not` also work.&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;suit&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;Suit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Diamonds&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;suit&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;Suit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Spades&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Revealed type is Literal[Suit.Diamonds, Suit.Spades]&lt;/span&gt;
        &lt;span class="n"&gt;reveal_type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;suit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Diamonds or Spades&amp;quot;&lt;/span&gt;

    &lt;span class="c1"&gt;# Revealed type is Literal[Suit.Hearts]&lt;/span&gt;
    &lt;span class="n"&gt;reveal_type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;suit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="hll"&gt;    &lt;span class="c1"&gt;# mypy error [comparison-overlap]: Non-overlapping identity check&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;    &lt;span class="c1"&gt;# left operand type: &amp;quot;Literal[Suit.Hearts]&amp;quot;&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;    &lt;span class="c1"&gt;# right operand type: &amp;quot;Literal[Suit.Diamonds]&amp;quot;&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;suit&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;Suit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Diamonds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;        &lt;span class="c1"&gt;# mypy error [unreachable]: Statement is unreachable&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Diamonds&amp;quot;&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;After mypy narrowed down the type of &lt;code&gt;suit&lt;/code&gt; to &lt;code&gt;Literal[Suit.Hearts]&lt;/code&gt;, it knows the next condition &lt;code&gt;suit is Suit.Diamonds&lt;/code&gt; will always evaluate to False, and issues an error.&lt;/p&gt;
&lt;p&gt;Once all the possibilities have been narrowed-out, the rest of the function becomes unreachable:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;describe_suit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;suit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Suit&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;suit&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;suit&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;Suit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Clubs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Clubs&amp;quot;&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;suit&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;Suit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Diamonds&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;suit&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;Suit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Spades&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Diamonds or Spades&amp;quot;&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;suit&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;Suit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Hearts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Hearts&amp;#39;&lt;/span&gt;

&lt;span class="hll"&gt;    &lt;span class="c1"&gt;# This is currently unreachable&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;    &lt;span class="n"&gt;assert_never&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;suit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;code&gt;assert_never&lt;/code&gt; works by taking an argument of type &lt;code&gt;NoReturn&lt;/code&gt;, which is only possible when the argument type is "empty". That is, when all possibilities have been narrowed-out and the statement is unreachable. If the statement does become reachable, then the &lt;code&gt;NoReturn&lt;/code&gt; is not allowed and mypy issues an error. To illustrate, remove the last condition and check the code with mypy:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;describe_suit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;suit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Suit&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;suit&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;suit&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;Suit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Clubs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Clubs&amp;quot;&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;suit&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;Suit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Diamonds&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;suit&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;Suit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Spades&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Diamonds or Spades&amp;quot;&lt;/span&gt;

&lt;span class="hll"&gt;    &lt;span class="c1"&gt;# if suit == Suit.Hearts:&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;    &lt;span class="c1"&gt;#     return &amp;#39;Hearts&amp;#39;&lt;/span&gt;
&lt;/span&gt;
&lt;span class="hll"&gt;    &lt;span class="c1"&gt;# mypy error: Argument 1 to &amp;quot;assert_never&amp;quot; has&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;    &lt;span class="c1"&gt;# incompatible type &amp;quot;Literal[Suit.Hearts]&amp;quot;; expected &amp;quot;NoReturn&amp;quot;&lt;/span&gt;
&lt;/span&gt;    &lt;span class="n"&gt;assert_never&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;suit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Mypy narrowed down the type of &lt;code&gt;suit&lt;/code&gt; to &lt;code&gt;Suit.Hearts&lt;/code&gt;, but &lt;code&gt;assert_never&lt;/code&gt; expects &lt;code&gt;NoReturn&lt;/code&gt;. This mismatch triggers the error, which &lt;strong&gt;effectively performs exhaustiveness checking&lt;/strong&gt; for &lt;code&gt;suit&lt;/code&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="the-future"&gt;&lt;a class="toclink" href="#the-future"&gt;The Future&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In 2018 &lt;a href="https://github.com/python/mypy/issues/5818#issuecomment-431863917" rel="noopener"&gt;Guido though &lt;code&gt;assert_never&lt;/code&gt; is a pretty clever trick&lt;/a&gt;, but it never made it into mypy. Instead, exhaustiveness checking will become officially available as part of mypy if/when &lt;a href="https://www.python.org/dev/peps/pep-0622/" rel="noopener"&gt;PEP 622 - Structural Pattern Matching&lt;/a&gt; is implemented. Until then, you can use &lt;code&gt;assert_never&lt;/code&gt; instead.&lt;/p&gt;
&lt;div class="admonition warning"&gt;
&lt;p class="admonition-title"&gt;UPDATE&lt;/p&gt;
&lt;p&gt;Starting at Python 3.11 &lt;code&gt;assert_never&lt;/code&gt; was added to the &lt;a href="https://docs.python.org/3.11/library/typing.html#typing.assert_never" rel="noopener"&gt;built-in typing module&lt;/a&gt;. Python 3.11 was released on Oct 2022, this article was originally published in Dec 2020.&lt;/p&gt;
&lt;/div&gt;
&lt;hr&gt;
&lt;h2 id="bonus-exhaustiveness-checking-in-django"&gt;&lt;a class="toclink" href="#bonus-exhaustiveness-checking-in-django"&gt;Bonus: Exhaustiveness Checking in Django&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Django provides a very useful attribute to most model field types called &lt;a href="https://docs.djangoproject.com/en/3.1/ref/models/fields/#choices" rel="noopener"&gt;&lt;code&gt;choices&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.utils.translation&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;gettext_lazy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&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;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&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;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;choices&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ready&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Ready&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;scheduled&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Scheduled&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;shipped&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Shipped&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;When you provide choices to a field, Django adds all sorts of nice things to it:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Add a validation check to &lt;code&gt;ModelForm&lt;/code&gt; (which are used by Django admin, among others)&lt;/li&gt;
&lt;li&gt;Render the field as a &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt; html element in forms&lt;/li&gt;
&lt;li&gt;Add a &lt;code&gt;get_{field}_display_name&lt;/code&gt; method to get the description&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;However, mypy can't know that a Django field with choices has a limited set of values, so it cannot perform exhaustiveness checking on it. To adapt our example from before:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# Will not perform exhaustiveness checking!&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle_order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;ready&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ship order&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;shipped&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;charge order&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;assert_never&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;/pre&gt;&lt;/div&gt;


&lt;p&gt;The function is not handling the status "scheduled", but mypy can't know that.&lt;/p&gt;
&lt;p&gt;One way to overcome this is to use an enum to generate the choices:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;enum&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;

&lt;span class="hll"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;enum&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Enum&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;    &lt;span class="n"&gt;Ready&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;ready&amp;#39;&lt;/span&gt;
    &lt;span class="n"&gt;Scheduled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;scheduled&amp;#39;&lt;/span&gt;
    &lt;span class="n"&gt;Shipped&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;shipped&amp;#39;&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&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;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&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;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="hll"&gt;        &lt;span class="n"&gt;choices&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;OrderStatus&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Now, you can achieve exhaustiveness checking with a slight change to the code:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle_order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="hll"&gt;    &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;OrderStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;OrderStatus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pending&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ship order&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;OrderStatus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Shipped&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;charge order&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;assert_never&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;/pre&gt;&lt;/div&gt;


&lt;p&gt;The tricky part here is that the model field &lt;code&gt;status&lt;/code&gt; is actually a string, so to achieve exhaustiveness checking you have to turn the value into an instance of the &lt;code&gt;OrderStatus&lt;/code&gt; enum. There are two downsides to this approach:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;You have to cast the value every time&lt;/strong&gt;: This is not very convenient. This can possibly be solved by implementing a custom "Enum field" in Django.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;The status descriptions are not translated&lt;/strong&gt;: Previously you used gettext (&lt;code&gt;_&lt;/code&gt;) to translate the enum values, but now you just used the description of the enum.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;While the first is still a pain, the second issue was addressed in Django 3.1 with the addition of &lt;a href="https://docs.djangoproject.com/en/3.1/ref/models/fields/#enumeration-types" rel="noopener"&gt;Django enumeration types&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;

&lt;span class="hll"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TextChoices&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;    &lt;span class="n"&gt;Ready&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;ready&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Ready&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;Scheduled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;scheduled&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Scheduled&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;Shipped&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;shipped&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Shipped&amp;#39;&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;Order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&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;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&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;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="hll"&gt;        &lt;span class="n"&gt;choices&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;OrderStatus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Notice how you replaced the enum with a &lt;code&gt;TextChoices&lt;/code&gt;. The new enumeration type looks a lot like an Enum (it actually extends Enum under the hood), but it let's you provide a tuple with a value and a description instead of just the value.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="updates"&gt;&lt;a class="toclink" href="#updates"&gt;Updates&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;After publishing this article a few readers suggested ways to improve the implementation, so I made the following edits:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;2020-12-09&lt;/strong&gt;: The initial version of the article had &lt;code&gt;assert_never&lt;/code&gt; take a value of type &lt;code&gt;NoReturn&lt;/code&gt;. &lt;a href="https://lobste.rs/s/1un01t/exhaustiveness_checking_with_mypy#c_ws1qku" rel="noopener"&gt;A commenter on Lobsters&lt;/a&gt; made an excellent suggestion to use the more intuitive &lt;code&gt;Union[()]&lt;/code&gt; type instead. This also results in a better error message.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;2020-12-09&lt;/strong&gt;: The initial version of the article used &lt;code&gt;assert False, ...&lt;/code&gt; in &lt;code&gt;assert_never&lt;/code&gt; instead of &lt;code&gt;raise AssertionError(...)&lt;/code&gt;. &lt;a href="https://lobste.rs/s/1un01t/exhaustiveness_checking_with_mypy#c_l3obsb" rel="noopener"&gt;A commenter on Lobsters&lt;/a&gt; mentioned that &lt;code&gt;assert&lt;/code&gt; statements are removed when python is run with the &lt;code&gt;-O&lt;/code&gt; flag. Since the &lt;code&gt;assert&lt;/code&gt; in &lt;code&gt;assert_never&lt;/code&gt; should not be removed, I changed it to &lt;code&gt;raise AssertionError&lt;/code&gt; instead.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;2020-12-10&lt;/strong&gt;: After looking some more, &lt;a href="https://lobste.rs/s/1un01t/exhaustiveness_checking_with_mypy#c_oeezlr" rel="noopener"&gt;tmcb found&lt;/a&gt; that &lt;code&gt;Union[()]&lt;/code&gt; is not currently accepted by Python &lt;em&gt;at runtime&lt;/em&gt;, so I reverted the argument to &lt;code&gt;NoReturn&lt;/code&gt; again.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;</content><category term="articles"></category><category term="Python"></category><category term="Django"></category></entry><entry><title>Stop Using datetime.now!</title><link href="https://hakibenita.com/python-dependency-injection" rel="alternate"></link><published>2020-06-01T00:00:00+03:00</published><updated>2020-06-01T00:00:00+03:00</updated><author><name>Haki Benita</name></author><id>tag:hakibenita.com,2020-06-01:/python-dependency-injection</id><summary type="html">&lt;p&gt;If you ever had a test that one day just started to fail, unprovoked, or a test that fails once every blue moon for no apparent reason, it's possible your code is relying on something that is not deterministic. In this article I describe a practical approach to dependency injection in Python that when used correctly, can eliminate nondeterminism and make your code easier to maintain and to test.&lt;/p&gt;</summary><content type="html">&lt;hr&gt;
&lt;p&gt;One of my favorite job interview questions is this:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Write a function that returns tomorrow's date&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This looks innocent enough for someone to suggest this as a solution:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;datetime&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;tomorrow&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;date&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;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;today&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;days&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This will work, but there is a followup question:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;How would you test this function?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Before you move on.... take a second to think about &lt;em&gt;your&lt;/em&gt; answer.&lt;/p&gt;
&lt;figure&gt;&lt;img alt="One of these pigeons is a mock&amp;lt;br&amp;gt;&amp;lt;small&amp;gt;Photo by &amp;lt;a href=&amp;quot;https://www.pexels.com/photo/two-pigeon-perched-on-white-track-light-681447/&amp;quot;&amp;gt;Pedro Figueras&amp;lt;/a&amp;gt;&amp;lt;/small&amp;gt;" src="https://hakibenita.com/images/00-python-dependency-injection.jpg"&gt;&lt;figcaption&gt;One of these pigeons is a mock&lt;br&gt;&lt;small&gt;Photo by &lt;a href="https://www.pexels.com/photo/two-pigeon-perched-on-white-track-light-681447/"&gt;Pedro Figueras&lt;/a&gt;&lt;/small&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;details class="toc-container" open&gt;
    &lt;summary&gt;Table of Contents&lt;/summary&gt;&lt;/p&gt;
&lt;div class="toc"&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#naive-approach"&gt;Naive Approach&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#dependency-injection"&gt;Dependency Injection&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#dependency-injection-in-the-wild"&gt;Dependency Injection in The Wild&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#injecting-functions"&gt;Injecting Functions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#injecting-values"&gt;Injecting Values&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#when-to-instantiate-injected-values"&gt;When to Instantiate Injected Values&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#dependency-injection-in-practice"&gt;Dependency Injection in Practice&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#ip-lookup"&gt;IP Lookup&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#assigning-responsibility"&gt;Assigning Responsibility&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#using-a-service"&gt;Using a Service&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#changing-implementations"&gt;Changing Implementations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#typing-services"&gt;Typing Services&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#using-a-protocol"&gt;Using a Protocol&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#nondeterminism-and-side-effects"&gt;Nondeterminism and Side-Effects&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#conclusion"&gt;Conclusion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;p&gt;&lt;/details&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="naive-approach"&gt;&lt;a class="toclink" href="#naive-approach"&gt;Naive Approach&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The most naive approach to test a function that returns tomorrow's date is this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# Bad&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;tomorrow&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2020&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This test will pass &lt;em&gt;today&lt;/em&gt;, but it will fail on any other day.&lt;/p&gt;
&lt;p&gt;Another way to test the function is this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# Bad&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;tomorrow&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;today&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;days&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This will also work, but there is an inherent problem with this approach. The same way you can't define a word in the dictionary using itself, &lt;strong&gt;you should not test a function by repeating its implementation.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Another problem with this approach is that it's only testing one scenario, for the day it is executed. What about getting the next day across a month or a year? What about the day after 2020-02-28?&lt;/p&gt;
&lt;p&gt;The problem with both implementations is that &lt;code&gt;today&lt;/code&gt; is set inside the function, and to simulate different test scenarios you need to control this value. One solution that comes to mind is to mock &lt;code&gt;datetime.date&lt;/code&gt;, and try to set the value returned by &lt;code&gt;today()&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;unittest&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;mock&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;mock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;datetime.date.today&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;return_value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2020&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
&lt;span class="gp"&gt;... &lt;/span&gt;    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;tomorrow&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2020&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;...&lt;/span&gt;
&lt;span class="gt"&gt;Traceback (most recent call last):&lt;/span&gt;
  File &lt;span class="nb"&gt;&amp;quot;&amp;lt;stdin&amp;gt;&amp;quot;&lt;/span&gt;, line &lt;span class="m"&gt;1&lt;/span&gt;, in &lt;span class="n"&gt;&amp;lt;module&amp;gt;&lt;/span&gt;
  File &lt;span class="nb"&gt;&amp;quot;/usr/lib/python3.7/unittest/mock.py&amp;quot;&lt;/span&gt;, line &lt;span class="m"&gt;1410&lt;/span&gt;, in &lt;span class="n"&gt;__enter__&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;setattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attribute&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_attr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="gr"&gt;TypeError&lt;/span&gt;: &lt;span class="n"&gt;can&amp;#39;t set attributes of built-in/extension type &amp;#39;datetime.date&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;As the exception suggests, built-in modules written in C cannot be mocked. The &lt;code&gt;unittest.mock&lt;/code&gt; documentation specifically addresses this attempt to &lt;a href="https://docs.python.org/3/library/unittest.mock-examples.html#partial-mocking" rel="noopener"&gt;mock the datetime module&lt;/a&gt;. Apparently, this is a very common issue and the writers of the official documentation felt it's worth mentioning. They even go the extra mile and link to a &lt;a href="https://williambert.online/2011/07/how-to-unit-testing-in-django-with-mocking-and-patching/" rel="noopener"&gt;blog post&lt;/a&gt; on this exact problem. The article is worth a read, and we are going to address the solution it presents later on.&lt;/p&gt;
&lt;p&gt;Like every other problem in Python, there are libraries that provide a solution. Two libraries that stand out are &lt;a href="https://pypi.org/project/freezegun/" rel="noopener"&gt;&lt;code&gt;freezegun&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://pypi.org/project/libfaketime/" rel="noopener"&gt;&lt;code&gt;libfaketime&lt;/code&gt;&lt;/a&gt;. Both provide the ability to mock time at different levels. However, resorting to external libraries is a luxury only developers of legacy system can afford. For new projects, or projects that are small enough to change, there are other alternatives that can keep the project free of these dependencies.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="dependency-injection"&gt;&lt;a class="toclink" href="#dependency-injection"&gt;Dependency Injection&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The problem we were trying to solve with mock, can also be solved by changing the function's API:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;datetime&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;tomorrow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;asof&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;date&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;asof&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;days&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;To control the reference time of the function, the time can be provided as an argument. This makes it easier to test the function in different scenarios:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;datetime&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;tomorrow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;asof&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2020&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;   &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2020&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;tomorrow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;asof&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2019&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2020&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;tomorrow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;asof&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2020&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;28&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2020&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;29&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;tomorrow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;asof&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2021&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;28&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2021&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;To remove the function's dependency on &lt;code&gt;datetime.date.today&lt;/code&gt;, we provide today's date as an argument. This pattern of providing, or "injecting" dependencies into functions and objects is often called "dependency injection", or in short "DI".&lt;/p&gt;
&lt;h3 id="dependency-injection-in-the-wild"&gt;&lt;a class="toclink" href="#dependency-injection-in-the-wild"&gt;Dependency Injection in The Wild&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Dependency injection is a way to decouple modules from each other. As our previous example shows, the function &lt;code&gt;tomorrow&lt;/code&gt; no longer depends on &lt;code&gt;today&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Using dependency injection is very common and often very intuitive. It's very likely that you already use it without even knowing. For example, &lt;a href="https://python-patterns.guide/gang-of-four/factory-method/#dodge-use-dependency-injection" rel="noopener"&gt;this article&lt;/a&gt; suggests that providing an open file to &lt;code&gt;json.load&lt;/code&gt; is a form of dependency injection:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;json&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;path/to/file.json&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;r&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The popular test framework pytest builds its entire fixture infrastructure around the &lt;a href="https://docs.pytest.org/en/latest/fixture.html#fixtures-a-prime-example-of-dependency-injection" rel="noopener"&gt;concept of dependency injection&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pytest&lt;/span&gt;

&lt;span class="nd"&gt;@pytest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fixture&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;one&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

&lt;span class="nd"&gt;@pytest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fixture&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;two&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_one_is_less_than_two&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;one&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;two&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;one&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;two&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The functions &lt;code&gt;one&lt;/code&gt; and &lt;code&gt;two&lt;/code&gt; are declared as fixtures. When pytest executes the test function &lt;code&gt;test_one_is_less_than_two&lt;/code&gt;, it will provide it with the values returned by the fixture functions matching the attribute names. In pytest, the injection is magically happening simply by using the name of a known fixture as an argument.&lt;/p&gt;
&lt;p&gt;Dependency injection is not limited just to Python. The popular JavaScript framework &lt;a href="https://angular.io/" rel="noopener"&gt;Angular&lt;/a&gt; is also built around &lt;a href="https://angular.io/guide/dependency-injection" rel="noopener"&gt;dependency injection&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;@Component&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;selector&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;order-list&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;template&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="sb"&gt;`...`&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;OrderListComponent&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;orders&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kr"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;orderService&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;OrderService&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;orders&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;orderService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getOrders&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Notice how the &lt;code&gt;orderService&lt;/code&gt; is provided, or injected, to the constructor. The component is using the order service, but is not instantiating it.&lt;/p&gt;
&lt;h3 id="injecting-functions"&gt;&lt;a class="toclink" href="#injecting-functions"&gt;Injecting Functions&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Sometimes injecting a value is not enough. For example, what if we need to get the current date before and after some operation:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Tuple&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;datetime&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;go&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Tuple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="n"&gt;started_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="c1"&gt;# Do something ...&lt;/span&gt;
    &lt;span class="n"&gt;ended_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;now&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;started_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ended_at&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;To test this function, we can provide the start time like we did before, but we can't provide the end time. One way to approach this is to make the calls to start and end outside the function. This is a valid solution, but for the sake of discussion we'll assume they need to be called inside.&lt;/p&gt;
&lt;p&gt;Since we can't mock &lt;code&gt;datetime.datetime&lt;/code&gt; itself, one way to make this function testable is to create a separate function that returns the current date:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Tuple&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;datetime&lt;/span&gt;

&lt;span class="hll"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;go&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Tuple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
&lt;span class="hll"&gt;    &lt;span class="n"&gt;started_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;    &lt;span class="c1"&gt;# Do something ...&lt;/span&gt;
&lt;span class="hll"&gt;    &lt;span class="n"&gt;ended_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;started_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ended_at&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;To control the values returned by the function &lt;code&gt;now&lt;/code&gt; in tests, we can use a mock:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;unittest&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;mock&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;fake_start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2020&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;fake_end&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2020&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;__main__.now&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;side_effect&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;fake_start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fake_end&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt;
&lt;span class="gp"&gt;... &lt;/span&gt;   &lt;span class="n"&gt;go&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="go"&gt;(datetime.datetime(2020, 1, 1, 15, 0),&lt;/span&gt;
&lt;span class="go"&gt; datetime.datetime(2020, 1, 1, 15, 1, 30))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Another way to approach this without mocking, is to rewrite the function once again:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Callable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Tuple&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;datetime&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;go&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Callable&lt;/span&gt;&lt;span class="p"&gt;[[],&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Tuple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="n"&gt;started_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="c1"&gt;# Do something ...&lt;/span&gt;
    &lt;span class="n"&gt;ended_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;now&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;started_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ended_at&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This time we provide the function with another function that returns a datetime. This is very similar to the first solution we suggested, when we injected the datetime itself to the function.&lt;/p&gt;
&lt;p&gt;The function can now be used like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;go&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;(datetime.datetime(2020, 4, 18, 14, 14, 5, 687471),&lt;/span&gt;
&lt;span class="go"&gt; datetime.datetime(2020, 4, 18, 14, 14, 5, 687475))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;To test it, we provide a different function that returns known datetimes:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;fake_start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2020&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;fake_end&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2020&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;gen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;iter&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;fake_start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fake_end&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;go&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gen&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="go"&gt;(datetime.datetime(2020, 1, 1, 15, 0),&lt;/span&gt;
&lt;span class="go"&gt; datetime.datetime(2020, 1, 1, 15, 1, 30))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This pattern can be generalized even more using a utility object:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Iterator&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;datetime&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;ticker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Iterator&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Generate an unending stream of datetimes in fixed intervals.&lt;/span&gt;

&lt;span class="sd"&gt;    Useful to test processes which require datetime for each step.&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;
        &lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;interval&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Using &lt;code&gt;ticker&lt;/code&gt;, the test will now look like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;gen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ticker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2020&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seconds&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;go&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gen&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;span class="go"&gt;(datetime.datetime(2020, 1, 1, 15, 0),&lt;/span&gt;
&lt;span class="go"&gt; datetime.datetime(2020, 1, 1, 15, 1, 30))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Fun fact: the name "ticker" was &lt;a href="https://gobyexample.com/tickers" rel="noopener"&gt;stolen from Go&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="injecting-values"&gt;&lt;a class="toclink" href="#injecting-values"&gt;Injecting Values&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The previous sections demonstrate injection of both values and functions. It's clear from the examples that injecting values is much simpler. This is why it's usually favorable to inject values rather than functions.&lt;/p&gt;
&lt;p&gt;Another reason is consistency. Take this common pattern that is often used in Django models:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&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;created&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DateTimeField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auto_now_add&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;modified&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DateTimeField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auto_now&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The model &lt;code&gt;Order&lt;/code&gt; includes two datetime fields, &lt;code&gt;created&lt;/code&gt; and &lt;code&gt;modified&lt;/code&gt;. It uses Django's &lt;a href="https://docs.djangoproject.com/en/3.0/ref/models/fields/#django.db.models.DateField.auto_now" rel="noopener"&gt;&lt;code&gt;auto_now_add&lt;/code&gt;&lt;/a&gt; attribute to automatically set &lt;code&gt;created&lt;/code&gt; when the object is saved for the first time, and &lt;a href="https://docs.djangoproject.com/en/3.0/ref/models/fields/#django.db.models.DateField.auto_now_add" rel="noopener"&gt;&lt;code&gt;auto_now&lt;/code&gt;&lt;/a&gt; to set &lt;code&gt;modified&lt;/code&gt; every time the object is saved.&lt;/p&gt;
&lt;p&gt;Say we create a new order and save it to the database:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Would you expect this test to fail:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;created&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;modified&lt;/span&gt;
&lt;span class="go"&gt;False&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This is very unexpected. How can an object that was just created have two different values for &lt;code&gt;created&lt;/code&gt; and &lt;code&gt;modified&lt;/code&gt;? Can you imagine what would happen if you rely on &lt;code&gt;modified&lt;/code&gt; and &lt;code&gt;created&lt;/code&gt; to be equal when an object was never changed, and actually use it to identify unchanged objects:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;F&lt;/span&gt;

&lt;span class="c1"&gt;# Wrong!&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_unchanged_objects&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;Order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;created&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;F&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;modified&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;For the &lt;code&gt;Order&lt;/code&gt; model above, this function will always return an empty queryset.&lt;/p&gt;
&lt;p&gt;The reason for this unexpected behavior is that each individual &lt;code&gt;DateTimeField&lt;/code&gt; is using &lt;code&gt;django.timezone.now&lt;/code&gt; internally during &lt;code&gt;save()&lt;/code&gt; to get the current time. The time between when the two fields are populated by Django causes the values to end up slightly different:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;created&lt;/span&gt;
&lt;span class="go"&gt;datetime.datetime(2020, 4, 18, 11, 41, 35, 740909, tzinfo=&amp;lt;UTC&amp;gt;)&lt;/span&gt;

&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;modified&lt;/span&gt;
&lt;span class="go"&gt;datetime.datetime(2020, 4, 18, 11, 41, 35, 741015, tzinfo=&amp;lt;UTC&amp;gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;If we treat &lt;code&gt;timezone.now&lt;/code&gt; like an injected function, we understand the inconsistencies it may cause.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;So, can this be avoided?&lt;/strong&gt; Can &lt;code&gt;created&lt;/code&gt; and &lt;code&gt;modified&lt;/code&gt; be equal when the object is first created? I'm sure there are a lot of hacks, libraries and other such exotic solutions but the truth is much simpler. If you want to make sure these two fields are equal when the object is first created, you better avoid &lt;code&gt;auto_now&lt;/code&gt; and &lt;code&gt;auto_now_add&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&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;created&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DateTimeField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;modified&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DateTimeField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Then, when you create a new instance, explicitly provide the values for both fields:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.utils&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;timezone&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;asof&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;created&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;asof&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;modified&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;asof&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;created&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;modified&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;created&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;F&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;modified&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="go"&gt;&amp;lt;QuerySet [&amp;lt;Order: Order object (2)&amp;gt;]&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;To quote the "Zen of Python", explicit is better than implicit. Explicitly providing the values for the fields requires a bit more work, but this is a small price to pay for reliable and predictable data.&lt;/p&gt;
&lt;div class="admonition info"&gt;
&lt;p class="admonition-title"&gt;using auto_now and auto_now_add&lt;/p&gt;
&lt;p&gt;When is it OK to use &lt;code&gt;auto_now&lt;/code&gt; and &lt;code&gt;auto_now_add&lt;/code&gt;? Usually when a date is used for audit purposes and not for business logic, it's fine to make this shortcut and use &lt;code&gt;auto_now&lt;/code&gt; or &lt;code&gt;auto_now_add&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id="when-to-instantiate-injected-values"&gt;&lt;a class="toclink" href="#when-to-instantiate-injected-values"&gt;When to Instantiate Injected Values&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Injecting values poses another interesting question, at what point should the value be set? The answer to this is "it depends", but there is a rule of thumb that is usually correct: &lt;strong&gt;values should be instantiated at the topmost level&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;For example, if &lt;code&gt;asof&lt;/code&gt; represents when an order is created, a website backend serving a store front may set this value when the request is received. In a normal Django setup, this means that the value should be set by the view. Another common example is a scheduled job. If you have jobs that use management commands, &lt;code&gt;asof&lt;/code&gt; should be set by the management command.&lt;/p&gt;
&lt;p&gt;Setting the values at the topmost level guarantees that the &lt;strong&gt;lower levels remain decoupled and easier to test&lt;/strong&gt;. The level at which injected values are set, is the level that you will usually need to use mock to test. In the example above, setting &lt;code&gt;asof&lt;/code&gt; in the view will make the models easier to test.&lt;/p&gt;
&lt;p&gt;Other than testing and correctness, another benefit of setting values explicitly rather than implicitly, is that it gives you more control over your data. For example, in the website scenario, an order's creation date is set by the view immediately when the request is received. However, if you process a batch file from a large customer, the time in which the order was created may well be in the past, when the customer first created the files. By avoiding "auto-magically" generated dates, we can implement this by passing the past date as an argument.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="dependency-injection-in-practice"&gt;&lt;a class="toclink" href="#dependency-injection-in-practice"&gt;Dependency Injection in Practice&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The best way to understand the benefits of DI and the motivation for it is using a real life example.&lt;/p&gt;
&lt;h3 id="ip-lookup"&gt;&lt;a class="toclink" href="#ip-lookup"&gt;IP Lookup&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Say we want to try and guess where visitors to our Django site are coming from, and we decide to try an use the IP address from the request to do that. An initial implementation can look like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.http&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;HttpRequest&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;requests&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_country_from_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;HttpRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="n"&gt;ip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;META&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;REMOTE_ADDR&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;META&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;HTTP_X_FORWARDED_FOR&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ip&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;ip&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;None&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;requests&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;https://ip-api.com/json/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ip&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&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;ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;

    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;status&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;success&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;countryCode&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This single function accepts an &lt;code&gt;HttpRequest&lt;/code&gt;, tries to extract an IP address from the request headers, and then uses the &lt;code&gt;requests&lt;/code&gt; library to call an external service to get the country code.&lt;/p&gt;
&lt;div class="admonition info"&gt;
&lt;p class="admonition-title"&gt;ip lookup&lt;/p&gt;
&lt;p&gt;I'm using the free service &lt;a href="https://ip-api.com" rel="noopener"&gt;https://ip-api.com&lt;/a&gt; to lookup a country from an IP. I'm using this service just for demonstration purposes. I'm not familiar with it, so don't see this as a recommendation to use it.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Let's try to use this function:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.test&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;RequestFactory&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;rf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;RequestFactory&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;REMOTE_ADDR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;216.58.210.46&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;get_country_from_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;&amp;#39;US&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;OK, so it works. Notice that to use it we created an &lt;a href="https://docs.djangoproject.com/en/3.0/ref/request-response/#django.http.HttpRequest" rel="noopener"&gt;&lt;code&gt;HttpRequest&lt;/code&gt; object&lt;/a&gt; using &lt;a href="https://docs.djangoproject.com/en/3.0/topics/testing/advanced/#django.test.RequestFactory" rel="noopener"&gt;Django's &lt;code&gt;RequestFactory&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Let's try to write a test for a scenario when a country code is found:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;re&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;responses&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.test&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;RequestFactory&lt;/span&gt;

&lt;span class="n"&gt;rf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;RequestFactory&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;responses&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RequestsMock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;rsps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;url_pattern&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;http://ip-api.com/json/[0-9\.]+&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;rsps&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;responses&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url_pattern&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;application/json&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;status&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;success&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;countryCode&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;US&amp;#39;&lt;/span&gt;
    &lt;span class="p"&gt;}))&lt;/span&gt;
    &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;REMOTE_ADDR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;216.58.210.46&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;countryCode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_country_from_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;countryCode&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;US&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The function is using the &lt;code&gt;requests&lt;/code&gt; library internally to make a request to the external API. To mock the response, we used the &lt;a href="https://github.com/getsentry/responses" rel="noopener"&gt;&lt;code&gt;responses&lt;/code&gt;&lt;/a&gt; library.&lt;/p&gt;
&lt;p&gt;If you look at this test and feel like it's very complicated than you are right. To test the function we had to do the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Generate a Django request using a &lt;code&gt;RequestFactory&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Mock a &lt;code&gt;requests&lt;/code&gt; response using &lt;code&gt;responses&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Have knowledge of the inner works of the function (what url it uses).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That last point is where it gets hairy. To test the function we used our knowledge of how the function is implemented: what endpoint it uses, how the URL is structured, what method it uses and what the response looks like. This creates an implicit dependency between the test and the implementation. In other words, &lt;strong&gt;the implementation of the function cannot change without changing the test as well&lt;/strong&gt;. This type of unhealthy dependency is both unexpected, and prevents us from treating the function as a "black box".&lt;/p&gt;
&lt;p&gt;Also, notice that that we only tested one scenario. If you look at the coverage of this test you'll find that it's very low. So next, we try and simplify this function.&lt;/p&gt;
&lt;h3 id="assigning-responsibility"&gt;&lt;a class="toclink" href="#assigning-responsibility"&gt;Assigning Responsibility&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;One of the techniques to make functions easier to test is to remove dependencies. Our IP function currently depends on Django's &lt;code&gt;HttpRequest&lt;/code&gt;, the &lt;code&gt;requests&lt;/code&gt; library and implicitly on the external service. Let's start by moving the part of the function that handles the external service to a separate function:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_country_from_ip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;http://ip-api.com/json/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ip&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&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;ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;

    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;status&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;success&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;countryCode&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_country_from_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;HttpRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="n"&gt;ip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;META&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;REMOTE_ADDR&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;META&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;HTTP_X_FORWARDED_FOR&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ip&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;ip&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;get_country_from_ip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We now have two functions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;get_country_from_ip&lt;/code&gt;: receives an IP address and returns the country code.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;get_country_from_request&lt;/code&gt;: accepts a Django &lt;code&gt;HttpRequest&lt;/code&gt;, extract the IP from the header, and then uses the first function to find the country code.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;After splitting the function we can now search an IP directly, without crating a request:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;get_country_from_ip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;216.58.210.46&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;&amp;#39;US&amp;#39;&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.test&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;RequestFactory&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;RequestFactory&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;REMOTE_ADDR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;216.58.210.46&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;get_country_from_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;&amp;#39;US&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Now, let's write a test for this function:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;re&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;responses&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;responses&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RequestsMock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;rsps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;url_pattern&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;http://ip-api.com/json/[0-9\.]+&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;rsps&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;responses&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url_pattern&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;application/json&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;status&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;success&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;countryCode&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;US&amp;#39;&lt;/span&gt;
    &lt;span class="p"&gt;}))&lt;/span&gt;
    &lt;span class="n"&gt;country_code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_country_from_ip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;216.58.210.46&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;country_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;US&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This test looks similar to the previous one, but we no longer need to use &lt;code&gt;RequestFactory&lt;/code&gt;. Because we have a separate function that retrieves the country code for an IP directly, we don't need to "fake" a Django &lt;code&gt;HttpRequest&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Having said that, we still want to make sure the top level function works, and that the IP is being extracted from the request correctly:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# BAD EXAMPLE!&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;re&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;responses&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.test&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;RequestFactory&lt;/span&gt;

&lt;span class="n"&gt;rf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;RequestFactory&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;request_with_no_ip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;country_code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_country_from_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request_with_no_ip&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;country_code&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We created a request with no IP and the function returned &lt;code&gt;None&lt;/code&gt;. With this outcome, can we really say for sure that the function works as expected? Can we tell that the function returned &lt;code&gt;None&lt;/code&gt; because it couldn't extract the IP from the request, or because the country lookup returned nothing?&lt;/p&gt;
&lt;p&gt;Someone once told me that if to describe what a function does you need to use the words "and" or "or", you can probably benefit from splitting it. This is the layman's version of the &lt;a href="https://en.wikipedia.org/wiki/Single-responsibility_principle" rel="noopener"&gt;Single-responsibility principle&lt;/a&gt; that dictates that &lt;strong&gt;every class or function should have just one reason to change&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The function &lt;code&gt;get_country_from_request&lt;/code&gt; extracts the IP from a request &lt;em&gt;and&lt;/em&gt; tries to find the country code for it. So, if the rule is correct, we need to split it up:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_ip_from_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;HttpRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="n"&gt;ip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;META&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;REMOTE_ADDR&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;META&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;HTTP_X_FORWARDED_FOR&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ip&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;ip&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ip&lt;/span&gt;


&lt;span class="c1"&gt;# Maintain backward compatibility&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_country_from_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;HttpRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="n"&gt;ip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_ip_from_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ip&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;get_country_from_ip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;To be able to test if we extract an IP from a request correctly, we yanked this part to a separate function. We can now test this function separately:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;rf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;RequestFactory&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;get_ip_from_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;get_ip_from_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;REMOTE_ADDR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;0.0.0.0&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;0.0.0.0&amp;#39;&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;get_ip_from_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HTTP_X_FORWARDED_FOR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;0.0.0.0&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;0.0.0.0&amp;#39;&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;get_ip_from_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;REMOTE_ADDR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;0.0.0.0&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HTTP_X_FORWARDED_FOR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;1.1.1.1&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;0.0.0.0&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;With just these 5 lines of code we covered a lot more possible scenarios.&lt;/p&gt;
&lt;h3 id="using-a-service"&gt;&lt;a class="toclink" href="#using-a-service"&gt;Using a Service&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;So far we've implemented unit tests for the function that extracts the IP from the request, and made it possible to do a country lookup using just an IP address. The tests for the top level function are still very messy. Because we use &lt;code&gt;requests&lt;/code&gt; inside the function, we were forced to use &lt;code&gt;responses&lt;/code&gt; as well to test it. There is nothing wrong with &lt;code&gt;responses&lt;/code&gt;, but the less dependencies the better.&lt;/p&gt;
&lt;p&gt;Invoking a request inside the function creates an implicit dependency between this function and the &lt;code&gt;requests&lt;/code&gt; library. One way to eliminate this dependency is to extract the part making the request to a separate service:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;requests&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;IpLookupService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;base_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;base_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base_url&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_country_from_ip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;base_url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;/json/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ip&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&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;ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;

        &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;status&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;success&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;countryCode&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The new &lt;code&gt;IpLookupService&lt;/code&gt; is instantiated with the base url for the service, and provides a single function to get a country from an IP:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;ip_lookup_service&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;IpLookupService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;http://ip-api.com&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;ip_lookup_service&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_country_from_ip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;216.58.210.46&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;&amp;#39;US&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Constructing services this way has many benefits:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Encapsulate all the logic related to IP lookup&lt;/li&gt;
&lt;li&gt;Provides a single interface with type annotations&lt;/li&gt;
&lt;li&gt;Can be reused&lt;/li&gt;
&lt;li&gt;Can be tested separately&lt;/li&gt;
&lt;li&gt;Can be developed separately (as long as the API it provides remains unchanged)&lt;/li&gt;
&lt;li&gt;Can be adjusted for different environments (for example, use a different URL for test and production)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The top level function should also change. Instead of making requests on its own, it uses the service:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_country_from_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;HttpRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ip_lookup_service&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;IpLookupService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="n"&gt;ip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_ip_from_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ip&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ip_lookup_service&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_country_from_ip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;To use the function, we pass an instance of the service to it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;ip_lookup_service&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;IpLookupService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;http://ip-api.com&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;RequestFactory&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;REMOTE_ADDR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;216.58.210.46&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;get_country_from_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ip_lookup_service&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;&amp;#39;US&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Now that we have full control of the service, we can test the top level function without using &lt;code&gt;responses&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;unittest&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;mock&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.test&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;RequestFactory&lt;/span&gt;

&lt;span class="n"&gt;fake_ip_lookup_service&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_autospec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IpLookupService&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;fake_ip_lookup_service&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_country_from_ip&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;return_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;US&amp;#39;&lt;/span&gt;

&lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;RequestFactory&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;REMOTE_ADDR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;216.58.210.46&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;country_code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_country_from_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fake_ip_lookup_service&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;country_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;US&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;To test the function without actually making http requests we created a mock of the service. We then set the return value of &lt;code&gt;get_country_from_ip&lt;/code&gt;, and passed the mock service to the function.&lt;/p&gt;
&lt;h3 id="changing-implementations"&gt;&lt;a class="toclink" href="#changing-implementations"&gt;Changing Implementations&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Another benefit of DI which is often mentioned, is the ability to completely change the underlying implementation of an injected service. For example, one day you discover that you don't have to use a remote service to lookup an IP. Instead, you can use a &lt;a href="https://github.com/maxmind/geoip-api-python" rel="noopener"&gt;local IP database&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Because our &lt;code&gt;IpLookupService&lt;/code&gt; does not leak its internal implementation, it's an easy switch:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;GeoIP&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LocalIpLookupService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path_to_db_file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;GeoIP&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path_to_db_file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;GeoIP&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GEOIP_STANDARD&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_country_from_ip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;country_code_by_addr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The service API remained unchanged, so you can use it the same way as the old service:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;ip_lookup_service&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;LocalIpLookupService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/usr/share/GeoIP/GeoIP.dat&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;ip_lookup_service&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_country_from_ip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;216.58.210.46&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;&amp;#39;US&amp;#39;&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.test&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;RequestFactory&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;RequestFactory&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;REMOTE_ADDR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;216.58.210.46&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;get_country_from_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ip_lookup_service&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;&amp;#39;US&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The best part here is that the tests are unaffected. All the tests should pass without making any changes.&lt;/p&gt;
&lt;div class="admonition info"&gt;
&lt;p class="admonition-title"&gt;GeoIP&lt;/p&gt;
&lt;p&gt;In the example I use the &lt;a href="https://github.com/maxmind/geoip-api-python" rel="noopener"&gt;MaxMind GeoIP Legacy Python Extension API&lt;/a&gt; because it uses files I already have in my OS as part of &lt;a href="https://linux.die.net/man/1/geoiplookup" rel="noopener"&gt;&lt;code&gt;geoiplookup&lt;/code&gt;&lt;/a&gt;. If you really need to lookup IP addresses check out &lt;a href="https://geoip2.readthedocs.io/en/latest/" rel="noopener"&gt;GeoIP2&lt;/a&gt; and make sure to check the license and usage restrictions.&lt;/p&gt;
&lt;p&gt;Also, Django users might be delighted to know that &lt;a href="https://docs.djangoproject.com/en/3.0/ref/contrib/gis/geoip2/" rel="noopener"&gt;Django provides a wrapper around &lt;code&gt;geoip2&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id="typing-services"&gt;&lt;a class="toclink" href="#typing-services"&gt;Typing Services&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;In the last section we cheated a bit. We injected the new service &lt;code&gt;LocalIpLookupService&lt;/code&gt; into a function that expects an instance of &lt;code&gt;IpLookupService&lt;/code&gt;. We made sure that these two are the same, but the type annotations are now wrong. We also used a mock to test the function which is also not of type &lt;code&gt;IpLookupService&lt;/code&gt;. So, how can we use type annotations and still be able to inject different services?&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;abc&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ABCMeta&lt;/span&gt;
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;GeoIP&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;requests&lt;/span&gt;

&lt;span class="hll"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;IpLookupService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;metaclass&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ABCMeta&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_country_from_ip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="ne"&gt;NotImplementedError&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;RemoteIpLookupService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IpLookupService&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;base_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;base_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base_url&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_country_from_ip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;base_url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;/json/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ip&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&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;ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;

        &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;status&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;success&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;countryCode&amp;#39;&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;LocalIpLookupService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IpLookupService&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path_to_db_file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;GeoIP&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path_to_db_file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;GeoIP&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GEOIP_STANDARD&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_country_from_ip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;country_code_by_addr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We defined a base class called &lt;code&gt;IpLookupService&lt;/code&gt; that acts as an interface. The base class defines the public API for users of &lt;code&gt;IpLookupService&lt;/code&gt;. Using the base class, we can provide two implementations:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;RemoteIpLookupService&lt;/code&gt;: uses the &lt;code&gt;requests&lt;/code&gt; library to lookup the IP at an external.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;LocalIpLookupService&lt;/code&gt;: uses the local GeoIP database.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now, any function that needs an instance of &lt;code&gt;IpLookupService&lt;/code&gt; can use this type, and the function will be able to accept any subclass of it.&lt;/p&gt;
&lt;p&gt;Before we wrap things up, we still need to handle the tests. Previously we removed the test's dependency on &lt;code&gt;responses&lt;/code&gt;, now we can ditch &lt;code&gt;mock&lt;/code&gt; as well. Instead, we subclass &lt;code&gt;IpLookupService&lt;/code&gt; with a simple implementation for testing:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Iterable&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FakeIpLookupService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IpLookupService&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Iterable&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]]):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;iter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_country_from_ip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The &lt;code&gt;FakeIpLookupService&lt;/code&gt; implements &lt;code&gt;IpLookupService&lt;/code&gt;, and is producing results from a list of predefined results we provide to it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.test&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;RequestFactory&lt;/span&gt;

&lt;span class="hll"&gt;&lt;span class="n"&gt;fake_ip_lookup_service&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FakeIpLookupService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;US&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;RequestFactory&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;REMOTE_ADDR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;216.58.210.46&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;country_code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_country_from_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fake_ip_lookup_service&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;country_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;US&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The test no longer uses &lt;code&gt;mock&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="using-a-protocol"&gt;&lt;a class="toclink" href="#using-a-protocol"&gt;Using a Protocol&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The form of class hierarchy demonstrated in the previous section is called &lt;a href="https://en.wikipedia.org/wiki/Nominal_type_system" rel="noopener"&gt;"nominal subtyping"&lt;/a&gt;. There is another way to utilize typing without classes, using &lt;a href="https://mypy.readthedocs.io/en/latest/protocols.html" rel="noopener"&gt;&lt;code&gt;Protocols&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Iterable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;typing_extensions&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Protocol&lt;/span&gt;
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;GeoIP&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;requests&lt;/span&gt;


&lt;span class="hll"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;IpLookupService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Protocol&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_country_from_ip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="k"&gt;pass&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RemoteIpLookupService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;base_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;base_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base_url&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_country_from_ip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;base_url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;/json/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ip&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&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;ok&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;

        &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;status&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;success&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;countryCode&amp;#39;&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;LocalIpLookupService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path_to_db_file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;GeoIP&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path_to_db_file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;GeoIP&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GEOIP_STANDARD&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_country_from_ip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;country_code_by_addr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ip&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;FakeIpLookupService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Iterable&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]]):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;iter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_country_from_ip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="k"&gt;yield from&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The switch from classes to protocols is mild. Instead of creating &lt;code&gt;IpLookupService&lt;/code&gt; as a base class, we declare it a &lt;code&gt;Protocol&lt;/code&gt;. A protocol is used to define an interface and cannot be instantiated. Instead, a protocol is used only for typing purposes. When a class implements the interface defined by the protocol, is means "Structural Subtyping" exits and the type check will validate.&lt;/p&gt;
&lt;p&gt;In our case, we use a protocol to make sure an argument of type &lt;code&gt;IpLookupService&lt;/code&gt; implements the functions we expect an IP service to provide.&lt;/p&gt;
&lt;div class="admonition info"&gt;
&lt;p class="admonition-title"&gt;structural and nominal subtyping&lt;/p&gt;
&lt;p&gt;I've written about protocols, structural and nominal subtyping to in the past. Check out &lt;a href="https://realpython.com/modeling-polymorphism-django-python/#generic-foreign-key" rel="noopener"&gt;Modeling Polymorphism in Django With Python&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;So which to use?&lt;/strong&gt; Some languages, like Java, use nominal typing exclusively, while other languages, like Go, use structural typing for interfaces. There are advantages and disadvantages to both ways, but we won't get into that here. In Python, nominal typing is easier to use and understand, so my recommendation is to stick to it, unless you need the flexibility afforded by protocols.&lt;/p&gt;
&lt;h3 id="nondeterminism-and-side-effects"&gt;&lt;a class="toclink" href="#nondeterminism-and-side-effects"&gt;Nondeterminism and Side-Effects&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;If you ever had a test that one day just started to fail, unprovoked, or a test that fails once every blue moon for no apparent reason, it's possible your code is relying on something that is not deterministic. In the &lt;code&gt;datetime.date.today&lt;/code&gt; example, the result of &lt;code&gt;datetime.date.today&lt;/code&gt; relies on the current time which is always changing, hence it's not deterministic.&lt;/p&gt;
&lt;p&gt;There are many sources of nondeterminism. Common examples include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Randomness&lt;/li&gt;
&lt;li&gt;Network access&lt;/li&gt;
&lt;li&gt;Filesystem access&lt;/li&gt;
&lt;li&gt;Database access&lt;/li&gt;
&lt;li&gt;Environment variables&lt;/li&gt;
&lt;li&gt;Mutable global variables&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Dependency injection provides a good way to control nondeterminism in tests. The basic recipe is this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Identify the source of nondeterminism and encapsulate it in a service&lt;/strong&gt;: For example, TimeService, RandomnessService, HttpService, FilesystemService and DatabaseService.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use dependency injection to access these services&lt;/strong&gt;: Never bypass them by using datetime.now() and similar directly.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Provide deterministic implementations of these services in tests&lt;/strong&gt;: Use a mock, or a custom implementation suited for tests instead.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If you follow the recipe diligently, your tests will not be affected by external circumstances and you will not have flaky tests!&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="conclusion"&gt;&lt;a class="toclink" href="#conclusion"&gt;Conclusion&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Dependency injection is a design pattern just like any other. Developers can decide to what degree they want to take advantage of it. The main benefits of DI are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Decouple modules, functions and objects.&lt;/li&gt;
&lt;li&gt;Switch implementations, or support several different implementations.&lt;/li&gt;
&lt;li&gt;Eliminate nondeterminism from tests.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In the use-case above we took several twists and turns to illustrate a point, which might have caused the implementation to seem more complicated than it really is. In addition to that, searching for information about dependency injection in Python often result in libraries and packages than seem to completely change the way you structure your application. This can be very intimidating.&lt;/p&gt;
&lt;p&gt;In reality, DI can be used sparingly and in appropriate places to achieve the benefits listed above. When implemented correctly, DI can make your code easier to maintain and to test.&lt;/p&gt;</content><category term="articles"></category><category term="Python"></category><category term="Django"></category><category term="Testing"></category></entry><entry><title>How to Move a Django Model to Another App</title><link href="https://hakibenita.com/move-django-model" rel="alternate"></link><published>2020-05-06T00:00:00+03:00</published><updated>2020-05-06T00:00:00+03:00</updated><author><name>Haki Benita</name></author><id>tag:hakibenita.com,2020-05-06:/move-django-model</id><summary type="html">&lt;p&gt;In my latest article for RealPython I cover some exotic migration operations, many of the built-in migration CLI commands and demonstrate important migrations concepts such as reversible migrations, migration plans and introspection.&lt;/p&gt;</summary><content type="html">&lt;hr&gt;
&lt;p&gt;In my latest article for &lt;a href="https://realpython.com" rel="noopener"&gt;RealPython&lt;/a&gt; I share three ways to tackle one of the most challenging tasks involving Django migrations: moving a model from one Django app to another.&lt;/p&gt;
&lt;p&gt;The article covers some exotic migration operations and many of the built-in migration CLI commands such &lt;code&gt;sqlmigrate&lt;/code&gt;, &lt;code&gt;showmigrations&lt;/code&gt; and &lt;code&gt;sqlsequencereset&lt;/code&gt;. In the article I also demonstrate important migrations concepts such as  reversible migrations, migration plans and introspection.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://realpython.com/move-django-model/" rel="noopener"&gt;&lt;strong&gt;Read "How to Move a Django Model to Another App" on RealPython ≫&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;figure&gt;&lt;img alt="How to Move a Django Model to Another App" src="https://hakibenita.com/images/00-move-django-model.png"&gt;&lt;figcaption&gt;How to Move a Django Model to Another App&lt;/figcaption&gt;
&lt;/figure&gt;</content><category term="articles"></category><category term="Python"></category><category term="Django"></category><category term="ORM"></category></entry><entry><title>Testing an Interactive Voice Response System With Python and Pytest</title><link href="https://hakibenita.com/python-django-pytest-twilio-ivr" rel="alternate"></link><published>2020-05-01T00:00:00+03:00</published><updated>2020-05-01T00:00:00+03:00</updated><author><name>Haki Benita</name></author><id>tag:hakibenita.com,2020-05-01:/python-django-pytest-twilio-ivr</id><summary type="html">&lt;p&gt;It can be very challenging to test a system that rely heavily on a third party service such as Twilio. In this article, I show how to organize your code in a way that would isolate your bushiness logic and make it easier for you to test it separately.&lt;/p&gt;</summary><content type="html">&lt;hr&gt;
&lt;p&gt;Following my previous article on &lt;a href="/python-django-twilio-ivr"&gt;how to build an Interactive Voice Response (IVR) system with Twilio, Python and Django&lt;/a&gt;, in this follow-up tutorial I show how to write automated tests for this system.&lt;/p&gt;
&lt;p&gt;It can be very challenging to test a system that rely heavily on a third party service such as Twilio. In this article, I show how to organize your code in a way that would isolate your business logic and make it easier to test separately. The article demonstrate useful testing patterns using Django's &lt;code&gt;RequestFactory&lt;/code&gt;, &lt;code&gt;unittest.mock&lt;/code&gt;, Pytest fixtures, build-in &lt;code&gt;django-pytest&lt;/code&gt; and many more.&lt;/p&gt;
&lt;div class="admonition info"&gt;
&lt;p class="admonition-title"&gt;Source Code&lt;/p&gt;
&lt;p&gt;The source code for this article and the previous one can be found &lt;a href="https://github.com/hakib/twilio-ivr-test" rel="noopener"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a href="https://www.twilio.com/blog/testing-twilio-ivr-system-python-pytest" rel="noopener"&gt;&lt;strong&gt;Read "Testing a Twilio Interactive Voice Response System With Python and Pytest" on the Twilio blog ≫&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;figure&gt;&lt;img alt="Building an IVR System with Django and Twilio" src="https://hakibenita.com/images/00-python-django-pytest-twilio-ivr.png"&gt;&lt;figcaption&gt;Building an IVR System with Django and Twilio&lt;/figcaption&gt;
&lt;/figure&gt;</content><category term="articles"></category><category term="Python"></category><category term="Django"></category><category term="Testing"></category></entry><entry><title>How to Provide Test Fixtures for Django Models in Pytest</title><link href="https://hakibenita.com/django-pytest-fixtures" rel="alternate"></link><published>2020-04-08T00:00:00+03:00</published><updated>2020-04-08T00:00:00+03:00</updated><author><name>Haki Benita</name></author><id>tag:hakibenita.com,2020-04-08:/django-pytest-fixtures</id><summary type="html">&lt;p&gt;One of the most challenging aspects of writing good tests is maintaining test fixtures. Good test fixtures motivate developers to write better tests, and bad fixtures can cripple a system to a point where developers fear and avoid them all together. The article covers everything from setting up Pytest for a Django project, creating test fixtures and how to create dependency between fixtures.&lt;/p&gt;</summary><content type="html">&lt;hr&gt;
&lt;p&gt;One of the most challenging aspects of writing good tests is maintaining test fixtures. Good test fixtures motivate developers to write better tests, and bad fixtures can cripple a system to a point where developers fear and avoid them all together. The key to maintaining good fixtures is to find a good balance between flexibility and usability. Good fixtures are ones that are easy to use and easy to modify.&lt;/p&gt;
&lt;p&gt;In my latest article for &lt;a href="https://realpython.com" rel="noopener"&gt;RealPython&lt;/a&gt; I share some insights on how to maintain good test fixtures for Django models using Pytest. The article covers everything from setting up Pytest for a Django project, creating test fixtures and how to create dependency between fixtures.&lt;/p&gt;
&lt;p&gt;The article focuses on a pattern called &lt;strong&gt;"factory as a service"&lt;/strong&gt;. Using this pattern, you can create fixture for Django models that depend on other fixtures. This makes it easier to set up data for tests and focus on the the scenario at hand rather than setting up the data.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://realpython.com/django-pytest-fixtures/" rel="noopener"&gt;&lt;strong&gt;Read "How to Provide Test Fixtures for Django Models in Pytest" on RealPython ≫&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;figure&gt;&lt;img alt="How to Provide Test Fixtures for Django Models in Pytest" src="https://hakibenita.com/images/00-django-pytest-fixtures.png"&gt;&lt;figcaption&gt;How to Provide Test Fixtures for Django Models in Pytest&lt;/figcaption&gt;
&lt;/figure&gt;</content><category term="articles"></category><category term="Python"></category><category term="Django"></category><category term="Testing"></category></entry><entry><title>Using Markdown in Django</title><link href="https://hakibenita.com/django-markdown" rel="alternate"></link><published>2020-03-30T00:00:00+03:00</published><updated>2020-03-30T00:00:00+03:00</updated><author><name>Haki Benita</name></author><id>tag:hakibenita.com,2020-03-30:/django-markdown</id><summary type="html">&lt;p&gt;How we developed a Markdown extension to manage content in Django sites.&lt;/p&gt;</summary><content type="html">&lt;hr&gt;
&lt;p&gt;As developers, we rely on static analysis tools to check, lint and transform our code. We use these tools to help us be more productive and produce better code. However, when we write content using &lt;a href="https://wikipedia.org/wiki/Markdown" rel="noopener"&gt;markdown&lt;/a&gt; the tools at our disposal are scarce.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;In this article we describe how we developed a Markdown extension to address challenges in managing content using Markdown in Django sites.&lt;/strong&gt;&lt;/p&gt;
&lt;figure&gt;&lt;img alt="Do you think they had a linter?&amp;lt;br&amp;gt;&amp;lt;small&amp;gt;Photo by &amp;lt;a href=&amp;quot;https://www.pexels.com/photo/typing-writing-typography-vintage-102100/&amp;quot;&amp;gt;mali maeder from Pexels&amp;lt;/a&amp;gt;&amp;lt;/small&amp;gt;" src="https://hakibenita.com/images/00-django-markdown.jpg"&gt;&lt;figcaption&gt;Do you think they had a linter?&lt;br&gt;&lt;small&gt;Photo by &lt;a href="https://www.pexels.com/photo/typing-writing-typography-vintage-102100/"&gt;mali maeder from Pexels&lt;/a&gt;&lt;/small&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;details class="toc-container" open&gt;
    &lt;summary&gt;Table of Contents&lt;/summary&gt;&lt;/p&gt;
&lt;div class="toc"&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#the-problem"&gt;The Problem&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#prior-work"&gt;Prior Work&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#using-markdown"&gt;Using Markdown&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#converting-markdown-to-html"&gt;Converting Markdown to HTML&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#using-markdown-extensions"&gt;Using Markdown Extensions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#creating-a-markdown-extension-to-process-inline-links"&gt;Creating a Markdown Extension to Process Inline Links&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#validate-and-transform-django-links"&gt;Validate and Transform Django Links&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#validating-mailto-links"&gt;Validating mailto Links&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#handling-internal-and-external-links"&gt;Handling Internal and External Links&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#resolving-url-names"&gt;Resolving URL Names&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#handling-external-links"&gt;Handling External Links&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#requiring-scheme"&gt;Requiring Scheme&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#putting-it-all-together"&gt;Putting it All Together&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#conclusion"&gt;Conclusion&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#taking-it-further"&gt;Taking it Further&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;p&gt;&lt;/details&gt;&lt;/p&gt;
&lt;h2 id="the-problem"&gt;&lt;a class="toclink" href="#the-problem"&gt;The Problem&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Like every website, we have different types of (mostly) static content in places like our home page, FAQ section and "About" page. For a very long time, we managed all of this content directly in Django templates.&lt;/p&gt;
&lt;p&gt;When we finally decided it's time to move this content out of templates and into the database, we thought it's best to use Markdown. It's safer to produce HTML from Markdown, it provides a certain level of control and uniformity, and is easier for non-technical users to handle. As we progressed with the move, we noticed we are missing a few things:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Internal Links&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Links to internal pages can get broken when the URL changes. In Django templates and views we use &lt;code&gt;reverse&lt;/code&gt; and &lt;code&gt;{% url %}&lt;/code&gt;, but this is not available in plain Markdown.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Copy Between Environments&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Absolute internal links cannot be copied between environments. This can be resolved using relative links, but there is no way to enforce this out of the box.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Invalid Links&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Invalid links can harm user experience and cause the user to question the reliability of the entire content. This is not something that is unique to Markdown, but HTML templates are maintained by developers who know a thing or two about URLs. Markdown documents on the other hand, are intended for non-technical writers.&lt;/p&gt;
&lt;h3 id="prior-work"&gt;&lt;a class="toclink" href="#prior-work"&gt;Prior Work&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;When I was researching this issue I searched for Python linters, Markdown preprocessor and extensions to help produce better Markdown. I found very few results. One approach that stood out was to use Django templates to produce Markdown documents.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Preprocess Markdown using Django Template&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Using Django templates, you can use template tags such as &lt;a href="https://docs.djangoproject.com/en/3.0/ref/templates/builtins/#url" rel="noopener"&gt;&lt;code&gt;url&lt;/code&gt;&lt;/a&gt; to reverse URL names, as well as conditions, variables, date formats and all the other Django template features. This approach essentially uses Django template as a preprocessor for Markdown documents.&lt;/p&gt;
&lt;p&gt;I personally felt like this may no be the best solution for non-technical writers. In addition, I was worried that providing access to Django template tags might be dangerous.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="using-markdown"&gt;&lt;a class="toclink" href="#using-markdown"&gt;Using Markdown&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;With a better understanding of the problem, we were ready to dig a bit deeper into Markdown in Python.&lt;/p&gt;
&lt;h3 id="converting-markdown-to-html"&gt;&lt;a class="toclink" href="#converting-markdown-to-html"&gt;Converting Markdown to HTML&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To start using Markdown in Python, install the &lt;a href="https://python-markdown.github.io/" rel="noopener"&gt;&lt;code&gt;markdown&lt;/code&gt;&lt;/a&gt; package:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;markdown
&lt;span class="go"&gt;Collecting markdown&lt;/span&gt;
&lt;span class="go"&gt;Installing collected packages: markdown&lt;/span&gt;
&lt;span class="go"&gt;Successfully installed markdown-3.2.1&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Next, create a &lt;code&gt;Markdown&lt;/code&gt; object and use the function &lt;code&gt;convert&lt;/code&gt; to turn some Markdown into HTML:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;markdown&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;md&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;markdown&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Markdown&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;md&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;convert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;My name is **Haki**&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;&amp;lt;p&amp;gt;My name is &amp;lt;strong&amp;gt;Haki&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;You can now use this HTML snippet in your template.&lt;/p&gt;
&lt;h3 id="using-markdown-extensions"&gt;&lt;a class="toclink" href="#using-markdown-extensions"&gt;Using Markdown Extensions&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The basic Markdown processor provides the essentials for producing HTML content. For more "exotic" options, the Python &lt;code&gt;markdown&lt;/code&gt; package includes some &lt;a href="https://python-markdown.github.io/extensions/" rel="noopener"&gt;built-in extensions&lt;/a&gt;. A popular extension is the &lt;a href="https://python-markdown.github.io/extensions/extra/" rel="noopener"&gt;"extra" extension&lt;/a&gt; that adds, among other things, support for &lt;a href="https://python-markdown.github.io/extensions/fenced_code_blocks/" rel="noopener"&gt;fenced code blocks&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;markdown&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;md&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;markdown&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Markdown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;extensions&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;extra&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;md&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;convert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&amp;quot;&amp;quot;```python&lt;/span&gt;
&lt;span class="gp"&gt;... &lt;/span&gt;&lt;span class="s2"&gt;print(&amp;#39;this is Python code!&amp;#39;)&lt;/span&gt;
&lt;span class="gp"&gt;... &lt;/span&gt;&lt;span class="s2"&gt;```&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;&amp;lt;pre&amp;gt;&amp;lt;code class=&amp;quot;python&amp;quot;&amp;gt;print(\&amp;#39;this is Python code!\&amp;#39;)\n&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;To extend Markdown with our unique Django capabilities, we are going to develop an extension of our own.&lt;/p&gt;
&lt;h3 id="creating-a-markdown-extension-to-process-inline-links"&gt;&lt;a class="toclink" href="#creating-a-markdown-extension-to-process-inline-links"&gt;Creating a Markdown Extension to Process Inline Links&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;If you look at the source, you'll see that to convert markdown to HTML, &lt;code&gt;Markdown&lt;/code&gt; uses different processors. One type of processor is an &lt;a href="https://github.com/Python-Markdown/markdown/blob/c116cfcca8bf610b643cbc7eafe9228f7a832fc3/markdown/inlinepatterns.py#L73" rel="noopener"&gt;inline processor&lt;/a&gt;. Inline processors match specific inline patterns such as links, backticks, bold text and underlined text, and converts them to HTML.&lt;/p&gt;
&lt;p&gt;The main purpose of our Markdown extension is to validate and transform links. So, the inline processor we are most interested in is the &lt;a href="https://github.com/Python-Markdown/markdown/blob/c116cfcca8bf610b643cbc7eafe9228f7a832fc3/markdown/inlinepatterns.py#L593" rel="noopener"&gt;&lt;code&gt;LinkInlineProcessor&lt;/code&gt;&lt;/a&gt;. This processor takes markdown in the form of &lt;code&gt;[Haki's website](https://hakibenita.com)&lt;/code&gt;, parses it and returns a tuple containing the link and the text.&lt;/p&gt;
&lt;p&gt;To extend the functionality, we extend &lt;code&gt;LinkInlineProcessor&lt;/code&gt; and create a &lt;code&gt;Markdown.Extension&lt;/code&gt; that uses it to handle links:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;markdown&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;markdown.inlinepatterns&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;LinkInlineProcessor&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;LINK_RE&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_site_domain&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# TODO: Get your site domain here&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;example.com&amp;#39;&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;clean_link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;href&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;site_domain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# TODO: This is where the magic happens!&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;href&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DjangoLinkInlineProcessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LinkInlineProcessor&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;getLink&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;href&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="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;handled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getLink&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="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;site_domain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_site_domain&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;href&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;clean_link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;href&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;site_domain&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;href&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="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;handled&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DjangoUrlExtension&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;markdown&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Extension&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;extendMarkdown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;md&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwrags&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;md&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inlinePatterns&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DjangoLinkInlineProcessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LINK_RE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;md&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;link&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;160&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Let's break it down:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The extension &lt;code&gt;DjangoUrlExtension&lt;/code&gt; registers an inline link processor called &lt;code&gt;DjangoLinkInlineProcessor&lt;/code&gt;. This processor will replace any other existing link processor.&lt;/li&gt;
&lt;li&gt;The inline processor &lt;code&gt;DjangoLinkInlineProcessor&lt;/code&gt; extends the built-in &lt;code&gt;LinkInlineProcessor&lt;/code&gt;, and calls the function &lt;code&gt;clean_link&lt;/code&gt; on every link it processes.&lt;/li&gt;
&lt;li&gt;The function &lt;code&gt;clean_link&lt;/code&gt; receives a link and a domain, and returns a transformed link. This is where we are going to plug in our implementation.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="admonition info"&gt;
&lt;p class="admonition-title"&gt;How to get the site domain&lt;/p&gt;
&lt;p&gt;To identify links to your own site you must know the domain of your site. If you are using Django's &lt;a href="https://docs.djangoproject.com/en/3.0/ref/contrib/sites" rel="noopener"&gt;sites framework&lt;/a&gt; you can use it to &lt;a href="https://docs.djangoproject.com/en/3.0/ref/contrib/sites/#getting-the-current-domain-for-full-urls" rel="noopener"&gt;get the current domain&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I did not include this in my implementation because we don't use the sites framework. Instead, we set a variable in Django settings.&lt;/p&gt;
&lt;p&gt;Another way to get the current domain is from an &lt;a href="https://docs.djangoproject.com/en/3.0/ref/request-response/#django.http.HttpRequest" rel="noopener"&gt;&lt;code&gt;HttpRequest&lt;/code&gt; object&lt;/a&gt;. If content is only edited in your own site, you can try to plug the site domain from the request object. This may require some changes to the implementation.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;To use the extension, add it when you initialize a new &lt;code&gt;Markdown&lt;/code&gt; instance:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;md&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;markdown&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Markdown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;extensions&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;DjangoUrlExtension&lt;/span&gt;&lt;span class="p"&gt;()])&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;md&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;convert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;[haki&amp;#39;s site](https://hakibenita.com)&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;https://hakibenita.com&amp;quot;&amp;gt;haki\&amp;#39;s site&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Great, the extension is being used and we are ready for the interesting part!&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="validate-and-transform-django-links"&gt;&lt;a class="toclink" href="#validate-and-transform-django-links"&gt;Validate and Transform Django Links&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Now that we got the extension to call &lt;code&gt;clean_link&lt;/code&gt; on all links, we can implement our validation and transformation logic.&lt;/p&gt;
&lt;h3 id="validating-mailto-links"&gt;&lt;a class="toclink" href="#validating-mailto-links"&gt;Validating &lt;code&gt;mailto&lt;/code&gt; Links&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To get the ball rolling, we'll start with a simple validation. &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a#Linking_to_an_email_address" rel="noopener"&gt;&lt;code&gt;mailto&lt;/code&gt; links&lt;/a&gt; are useful for opening the user's email client with a predefined recipient address, subject and even message body.&lt;/p&gt;
&lt;p&gt;A common &lt;code&gt;mailto&lt;/code&gt; link can look like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;mailto:support@service.com?subject=I need help!&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Help!&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This link will open your email client set to compose a new email to "support@service.com" with subject line "I need help!".&lt;/p&gt;
&lt;p&gt;&lt;code&gt;mailto&lt;/code&gt; links do not have to include an email address. If you look at the "share" buttons at the bottom of this article, you'll find a &lt;code&gt;mailto&lt;/code&gt; link that looks like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;
  &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;mailto:?subject=Django Markdown by Haki Benita&amp;amp;body=http://hakibenita.com/django-markdown&amp;quot;&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Email&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  Share via Email
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This &lt;code&gt;mailto&lt;/code&gt; link does not include a recipient, just a subject line and message body.&lt;/p&gt;
&lt;p&gt;Now that we have a good understanding of what &lt;code&gt;mailto&lt;/code&gt; links look like, we can add the first validation to the &lt;code&gt;clean_link&lt;/code&gt; function:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;re&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.core.exceptions&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ValidationError&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.core.validators&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;EmailValidator&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ne"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;InvalidMarkdown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__str__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt; &amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;&amp;quot;&amp;#39;&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;clean_link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;href&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;site_domain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;href&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;mailto:&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;email_match&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;^(mailto:)?([^?]*)&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;href&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;email_match&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;InvalidMarkdown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Invalid mailto link&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;href&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;email_match&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;EmailValidator&lt;/span&gt;&lt;span class="p"&gt;()(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;ValidationError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;InvalidMarkdown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Invalid email address&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;email&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;href&lt;/span&gt;

    &lt;span class="c1"&gt;# More validations to come...&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;href&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;To validate a &lt;code&gt;mailto&lt;/code&gt; link we added the following code to &lt;code&gt;clean_link&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Check if the link starts with &lt;code&gt;mailto:&lt;/code&gt; to identify relevant links.&lt;/li&gt;
&lt;li&gt;Split the link to its components using a regular expression.&lt;/li&gt;
&lt;li&gt;Yank the actual email address from the &lt;code&gt;mailto&lt;/code&gt; link, and validate it using &lt;a href="https://docs.djangoproject.com/en/3.0/ref/validators/#emailvalidator" rel="noopener"&gt;Django's &lt;code&gt;EmailValidator&lt;/code&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Notice that we also added a new type of exception called &lt;code&gt;InvalidMarkdown&lt;/code&gt;. We defined our own custom &lt;code&gt;Exception&lt;/code&gt; type to distinguish it from other errors raised by &lt;code&gt;markdown&lt;/code&gt; itself.&lt;/p&gt;
&lt;div class="admonition tip"&gt;
&lt;p class="admonition-title"&gt;Custom error class&lt;/p&gt;
&lt;p&gt;I wrote about &lt;a href="bullet-proofing-django-models#a-better-approach"&gt;custom error classes&lt;/a&gt; in the past, why they are useful and when you should use them.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Before we move on, let's add some tests and see this in action:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;md&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;markdown&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Markdown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;extensions&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;DjangoUrlExtension&lt;/span&gt;&lt;span class="p"&gt;()])&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;md&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;convert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;[Help](mailto:support@service.com?subject=I need help!)&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;&amp;#39;&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;mailto:support@service.com?subject=I need help!&amp;quot;&amp;gt;Help&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;&amp;#39;&lt;/span&gt;

&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;md&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;convert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;[Help](mailto:?subject=I need help!)&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;&amp;lt;p&amp;gt;&amp;lt;a href=&amp;quot;mailto:?subject=I need help!&amp;quot;&amp;gt;Help&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;&lt;/span&gt;

&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;md&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;convert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;[Help](mailto:invalidemail?subject=I need help!)&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;InvalidMarkdown: Invalid email address &amp;quot;invalidemail&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Great! Worked as expected.&lt;/p&gt;
&lt;h3 id="handling-internal-and-external-links"&gt;&lt;a class="toclink" href="#handling-internal-and-external-links"&gt;Handling Internal and External Links&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Now that we got our toes wet with &lt;code&gt;mailto&lt;/code&gt; links, we can handle other types of links:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;External Links&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Links outside our Django app.&lt;/li&gt;
&lt;li&gt;Must contains a scheme: either http or https.&lt;/li&gt;
&lt;li&gt;Ideally, we also want to make sure these links are not broken, but we won't do that now.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Internal Links&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Links to pages inside our Django app.&lt;/li&gt;
&lt;li&gt;Link must be relative: this will allow us to move content between environments.&lt;/li&gt;
&lt;li&gt;Use Django's URL names instead of a URL path: this will allow us to safely move views around without worrying about broken links in markdown content.&lt;/li&gt;
&lt;li&gt;Links may contain query parameters (&lt;code&gt;?&lt;/code&gt;) and a fragment (&lt;code&gt;#&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="admonition info"&gt;
&lt;p class="admonition-title"&gt;SEO&lt;/p&gt;
&lt;p&gt;From an SEO standpoint, public URL's should not change. When they do, you should handle it properly with redirects, otherwise you might get penalized by search engines.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;With this list of requirements we can start working.&lt;/p&gt;
&lt;h4 id="resolving-url-names"&gt;&lt;a class="toclink" href="#resolving-url-names"&gt;Resolving URL Names&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;To link to internal pages we want writers to provide a &lt;strong&gt;URL name&lt;/strong&gt;, not a &lt;strong&gt;URL path&lt;/strong&gt;. For example, say we have this view:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.urls&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;app.views&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;home&lt;/span&gt;

&lt;span class="n"&gt;urlpatterns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;home&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;home&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The URL path to this page is &lt;code&gt;https://example.com/&lt;/code&gt;, the URL name is &lt;code&gt;home&lt;/code&gt;. We want to use the URL name &lt;code&gt;home&lt;/code&gt; in our markdown links, like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;Go back to [&lt;span class="nt"&gt;homepage&lt;/span&gt;](&lt;span class="na"&gt;home&lt;/span&gt;)
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This should render to:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Go back to &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;/&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;homepage&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We also want to support query params and hash:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;Go back to [homepage](home#top)
Go back to [homepage](home?utm_source=faq)
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This should render to the following HTML:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Go back to &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;/#top&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;homepage&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Go back to &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;/?utm_source=faq&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;homepage&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Using URL names, if we change the URL path, the links in the content will not be broken. To check if the href provided by the writer is a valid &lt;code&gt;url_name&lt;/code&gt;, we can try to &lt;a href="https://docs.djangoproject.com/en/3.0/ref/urlresolvers/#reverse" rel="noopener"&gt;&lt;code&gt;reverse&lt;/code&gt;&lt;/a&gt; it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.urls&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;reverse&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;reverse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;home&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;&amp;#39;/&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The URL name "home" points to the url path "/". When there is no match, an exception is raised:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.urls&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;reverse&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;reverse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;foo&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;NoReverseMatch: Reverse for &amp;#39;foo&amp;#39; not found.&lt;/span&gt;
&lt;span class="go"&gt;&amp;#39;foo&amp;#39; is not a valid view function or pattern name.&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Before we move forward, what happens when the URL name include query params or a hash:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.urls&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;reverse&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;reverse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;home#top&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;NoReverseMatch: Reverse for &amp;#39;home#top&amp;#39; not found.&lt;/span&gt;
&lt;span class="go"&gt;&amp;#39;home#top&amp;#39; is not a valid view function or pattern name.&lt;/span&gt;

&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;reverse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;home?utm_source=faq&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;NoReverseMatch: Reverse for &amp;#39;home?utm_source=faq&amp;#39; not found.&lt;/span&gt;
&lt;span class="go"&gt;&amp;#39;home?utm_source=faq&amp;#39; is not a valid view function or pattern name.&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This makes sense because query parameters and hash are not part of the URL name.&lt;/p&gt;
&lt;p&gt;To use &lt;code&gt;reverse&lt;/code&gt; &lt;em&gt;and&lt;/em&gt; support query params and hashes, we first need to clean the value. Then, check that it is a valid URL name and return the URL path including the query params and hash, if provided:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;re&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.urls&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;reverse&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;clean_link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;href&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;site_domain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# ... Same as before ...&lt;/span&gt;

    &lt;span class="c1"&gt;# Remove fragments or query params before trying to match the URL name.&lt;/span&gt;
    &lt;span class="n"&gt;href_parts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;#|\?&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;href&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;href_parts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;start_ix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;href_parts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;url_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url_extra&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;href&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="n"&gt;start_ix&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;href&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;start_ix&lt;/span&gt;&lt;span class="p"&gt;:]&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;url_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url_extra&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;href&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;reverse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;NoReverseMatch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;pass&lt;/span&gt;
    &lt;span class="k"&gt;else&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;url&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;url_extra&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;href&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This snippet uses a regular expression to split &lt;code&gt;href&lt;/code&gt; in the occurrence of either &lt;code&gt;?&lt;/code&gt; or &lt;code&gt;#&lt;/code&gt;, and return the parts.&lt;/p&gt;
&lt;p&gt;Make sure that it works:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;md&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;markdown&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Markdown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;extensions&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;DjangoUrlExtension&lt;/span&gt;&lt;span class="p"&gt;()])&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;md&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;convert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Go back to [homepage](home)&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;&amp;lt;p&amp;gt;Go back to &amp;lt;a href=&amp;quot;/&amp;quot;&amp;gt;homepage&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;&lt;/span&gt;

&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;md&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;convert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Go back to [homepage](home#top)&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;&amp;lt;p&amp;gt;Go back to &amp;lt;a href=&amp;quot;/#top&amp;quot;&amp;gt;homepage&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;&lt;/span&gt;

&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;md&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;convert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Go back to [homepage](home?utm_source=faq)&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;&amp;lt;p&amp;gt;Go back to &amp;lt;a href=&amp;quot;/?utm_source=faq&amp;quot;&amp;gt;homepage&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;&lt;/span&gt;

&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;md&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;convert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Go back to [homepage](home?utm_source=faq#top)&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;&amp;lt;p&amp;gt;Go back to &amp;lt;a href=&amp;quot;/?utm_source=faq#top&amp;quot;&amp;gt;homepage&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Amazing! Writers can now use URL names in Markdown. They can also include query parameters and fragment to be added to the URL.&lt;/p&gt;
&lt;h4 id="handling-external-links"&gt;&lt;a class="toclink" href="#handling-external-links"&gt;Handling External Links&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;To handle external links properly we want to check two things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;External links always provide a scheme, either &lt;code&gt;http:&lt;/code&gt; or &lt;code&gt;https:&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Prevent absolute links to our own site. Internal links should use URL names.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;So far, we handled URL names and &lt;code&gt;mailto&lt;/code&gt; links. If we passed these two checks it means &lt;code&gt;href&lt;/code&gt; is a URL. Let's start by checking if the link is to our own site:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;urllib.parse&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;urlparse&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;clean_link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;href&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;site_domain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;parsed_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;urlparse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;href&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;parsed_url&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;netloc&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;site_domain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# TODO: URL is internal.&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The function &lt;a href="https://docs.python.org/3/library/urllib.parse.html#urllib.parse.urlparse" rel="noopener"&gt;&lt;code&gt;urlparse&lt;/code&gt;&lt;/a&gt; returns a named tuple that contains the different parts of the URL. If the &lt;code&gt;netloc&lt;/code&gt; property equals the &lt;code&gt;site_domain&lt;/code&gt;, the link is really an internal link.&lt;/p&gt;
&lt;p&gt;If the URL is in fact internal, we need to fail. But, keep in mind that writers are not necessarily technical people, so we want to help them out a bit and provide a useful error message. We require that internal links use a URL name and not a URL path, so it's best to let writers know what is the URL name for the path they provided.&lt;/p&gt;
&lt;p&gt;To get the URL name of a URL path, Django provides a function called &lt;a href="https://docs.djangoproject.com/en/3.0/ref/urlresolvers/#resolve" rel="noopener"&gt;&lt;code&gt;resolve&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.utils&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;resolve&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;ResolverMatch(&lt;/span&gt;
&lt;span class="go"&gt;    func=app.views.home,&lt;/span&gt;
&lt;span class="go"&gt;    args=(),&lt;/span&gt;
&lt;span class="go"&gt;    kwargs={},&lt;/span&gt;
&lt;span class="go"&gt;    url_name=home,&lt;/span&gt;
&lt;span class="go"&gt;    app_names=[],&lt;/span&gt;
&lt;span class="go"&gt;    namespaces=[],&lt;/span&gt;
&lt;span class="go"&gt;    route=,&lt;/span&gt;
&lt;span class="go"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url_name&lt;/span&gt;
&lt;span class="go"&gt;&amp;#39;home&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;When a match is found, &lt;code&gt;resolve&lt;/code&gt; returns a &lt;code&gt;ResolverMatch&lt;/code&gt; object that contains, among other information, the URL name. When a match is not found, it raises an error:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/foo&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;Resolver404: {&amp;#39;tried&amp;#39;: [[&amp;lt;URLPattern &amp;#39;&amp;#39; [name=&amp;#39;home&amp;#39;]&amp;gt;]], &amp;#39;path&amp;#39;: &amp;#39;foo&amp;#39;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This is actually what Django does under the hood to determine which view function to execute when a new request comes in.&lt;/p&gt;
&lt;p&gt;To provide writers with better error messages we can use the URL name from the &lt;code&gt;ResolverMatch&lt;/code&gt; object:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;urllib.parse&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;urlparse&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;clean_link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;href&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;site_domain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# ...&lt;/span&gt;

    &lt;span class="n"&gt;parsed_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;urlparse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;href&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;parsed_url&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;netloc&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;site_domain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;resolver_match&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parsed_url&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;Resolver404&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;InvalidMarkdown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s2"&gt;&amp;quot;Should not use absolute links to the current site.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
                &lt;span class="s2"&gt;&amp;quot;We couldn&amp;#39;t find a match to this URL. Are you sure it exists?&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;href&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;InvalidMarkdown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s2"&gt;&amp;quot;Should not use absolute links to the current site.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
                &lt;span class="s1"&gt;&amp;#39;Try using the url name &amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s1"&gt;&amp;quot;.&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resolver_match&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url_name&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;href&lt;/span&gt;&lt;span class="p"&gt;,&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;href&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;When we identify that the link in internal, we handle two cases:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We don't recognize the URL: The url is most likely incorrect. Ask the writer to check the URL for mistakes.&lt;/li&gt;
&lt;li&gt;We recognize the URL: The url is correct so tell the writer what URL name to use instead.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let's see it in action:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;clean_link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;https://example.com/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;example.com&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;InvalidMarkdown: Should not use absolute links to the current site.&lt;/span&gt;
&lt;span class="go"&gt;Try using the url name &amp;quot;home&amp;quot;. &amp;quot;https://example.com/&amp;quot;&lt;/span&gt;

&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;clean_link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;https://example.com/foo&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;example.com&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;InvalidMarkdown: Should not use absolute links to the current site.&lt;/span&gt;
&lt;span class="go"&gt;We couldn&amp;#39;t find a match to this URL.&lt;/span&gt;
&lt;span class="go"&gt;Are you sure it exists? &amp;quot;https://example.com/foo&amp;quot;&lt;/span&gt;

&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;clean_link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;https://external.com&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;example.com&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;&amp;#39;https://external.com&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Nice! External links are accepted and internal links are rejected with a helpful message.&lt;/p&gt;
&lt;h4 id="requiring-scheme"&gt;&lt;a class="toclink" href="#requiring-scheme"&gt;Requiring Scheme&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;The last thing we want to do is to make sure external links include a scheme, either &lt;code&gt;http:&lt;/code&gt; or &lt;code&gt;https:&lt;/code&gt;. Let's add that last piece to the function &lt;code&gt;clean_link&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;clean_link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;href&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;site_domain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# ...&lt;/span&gt;
    &lt;span class="n"&gt;parsed_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;urlparse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;href&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;#...&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;parsed_url&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scheme&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;http&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;https&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;InvalidMarkdown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;Must provide an absolute URL &amp;#39;&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;(be sure to include https:// or http://)&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;href&lt;/span&gt;&lt;span class="p"&gt;,&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;href&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Using the parsed URL we can easily check the scheme. Let's make sure it's working:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;clean_link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;external.com&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;example.com&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;InvalidMarkdown: Must provide an absolute URL (be sure to include https:// or http://) &amp;quot;external.com&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We provided the function with a link that has no scheme, and it failed with a helpful message. Cool!&lt;/p&gt;
&lt;h3 id="putting-it-all-together"&gt;&lt;a class="toclink" href="#putting-it-all-together"&gt;Putting it All Together&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;This is the complete code for the &lt;code&gt;clean_link&lt;/code&gt; function:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;clean_link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;href&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;site_domain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;href&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;mailto:&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;email_match&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;^(mailto:)?([^?]*)&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;href&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;email_match&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;InvalidMarkdown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Invalid mailto link&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;href&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;email_match&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;groups&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;EmailValidator&lt;/span&gt;&lt;span class="p"&gt;()(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;ValidationError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;InvalidMarkdown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Invalid email address&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;email&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;href&lt;/span&gt;

    &lt;span class="c1"&gt;# Remove fragments or query params before trying to match the url name&lt;/span&gt;
    &lt;span class="n"&gt;href_parts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;#|\?&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;href&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;href_parts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;start_ix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;href_parts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;url_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url_extra&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;href&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="n"&gt;start_ix&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;href&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;start_ix&lt;/span&gt;&lt;span class="p"&gt;:]&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;url_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url_extra&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;href&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;reverse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;NoReverseMatch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;pass&lt;/span&gt;
    &lt;span class="k"&gt;else&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;url&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;url_extra&lt;/span&gt;

    &lt;span class="n"&gt;parsed_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;urlparse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;href&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;parsed_url&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;netloc&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;site_domain&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;resolver_match&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parsed_url&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;Resolver404&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;InvalidMarkdown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s2"&gt;&amp;quot;Should not use absolute links to the current site.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
                &lt;span class="s2"&gt;&amp;quot;We couldn&amp;#39;t find a match to this URL. Are you sure it exists?&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;href&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;InvalidMarkdown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s2"&gt;&amp;quot;Should not use absolute links to the current site.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;
                &lt;span class="s1"&gt;&amp;#39;Try using the url name &amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s1"&gt;&amp;quot;.&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resolver_match&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url_name&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;href&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;parsed_url&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scheme&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;http&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;https&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;InvalidMarkdown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;Must provide an absolute URL &amp;#39;&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;(be sure to include https:// or http://)&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;href&lt;/span&gt;&lt;span class="p"&gt;,&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;href&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;To get a sense of what a real use case for all of these features look like, take a look at the following content:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gh"&gt;# How to Get Started?&lt;/span&gt;

Download the [&lt;span class="nt"&gt;mobile app&lt;/span&gt;](&lt;span class="na"&gt;https://some-app-store.com/our-app&lt;/span&gt;) and log in to your account.
If you don&amp;#39;t have an account yet, [&lt;span class="nt"&gt;sign up now&lt;/span&gt;](&lt;span class="na"&gt;signup?utm_source=getting_started&lt;/span&gt;).
For more information about pricing, check our [&lt;span class="nt"&gt;pricing plans&lt;/span&gt;](&lt;span class="na"&gt;home#pricing-plans&lt;/span&gt;)
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This will produce the following HTML:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;How to Get Started?&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Download the &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;https://some-app-store.com/our-app&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;mobile app&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; and log in to your account.
If you don&amp;#39;t have an account yet, &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;signup/?utm_source=getting_started&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;sign up now&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;.
For more information about pricing, check our &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;/#pricing-plans&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;pricing plans&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Nice!&lt;/p&gt;
&lt;h2 id="conclusion"&gt;&lt;a class="toclink" href="#conclusion"&gt;Conclusion&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We now have a pretty sweet extension that can validate and transform links in Markdown documents! It is now much easier to move documents between environments and keep our content tidy and most importantly, correct and up to date!&lt;/p&gt;
&lt;div class="admonition source"&gt;
&lt;p class="admonition-title"&gt;Source&lt;/p&gt;
&lt;p&gt;The full source code can be found in &lt;a href="https://gist.github.com/hakib/73fccc340e855bb65f42197e298c0c7d" rel="noopener"&gt;this gist&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id="taking-it-further"&gt;&lt;a class="toclink" href="#taking-it-further"&gt;Taking it Further&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The capabilities described in this article worked well for us, but you might want to adjust it to fit your own needs.&lt;/p&gt;
&lt;p&gt;If you need some ideas, then in addition to this extension we also created a markdown &lt;a href="https://python-markdown.github.io/extensions/api/#preprocessors" rel="noopener"&gt;Preprocessor&lt;/a&gt; that lets writers use constants in Markdown. For example, we defined a constant called &lt;code&gt;SUPPORT_EMAIL&lt;/code&gt;, and we use it like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;Contact our support at [&lt;span class="nt"&gt;$SUPPORT_EMAIL&lt;/span&gt;](&lt;span class="na"&gt;mailto:$SUPPORT_EMAIL&lt;/span&gt;)
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The preprocessor will replace the string &lt;code&gt;$SUPPORT_EMAIL&lt;/code&gt; with the text we defined, and only then render the Markdown.&lt;/p&gt;</content><category term="articles"></category><category term="Django"></category><category term="Python"></category></entry><entry><title>Building an IVR System with Python, Django and Twilio</title><link href="https://hakibenita.com/python-django-twilio-ivr" rel="alternate"></link><published>2020-02-12T00:00:00+02:00</published><updated>2020-02-12T00:00:00+02:00</updated><author><name>Haki Benita</name></author><id>tag:hakibenita.com,2020-02-12:/python-django-twilio-ivr</id><summary type="html">&lt;p&gt;Last year my team and I worked on a very challenging IVR system. After almost a year in production and thousands of processed transactions, I teamed up with the great people over at the Twilio blog to write an introductory tutorial for developing IVR systems using Django and Twilio IVR.&lt;/p&gt;</summary><content type="html">&lt;hr&gt;
&lt;p&gt;Last year my team and I worked on a very challenging IVR system. After almost a year in production and thousands of processed transactions, I teamed up with the great people over at the &lt;a href="https://www.twilio.com/blog" rel="noopener"&gt;Twilio blog&lt;/a&gt; to write an introductory  tutorial for developing IVR systems using Django and Twilio IVR.&lt;/p&gt;
&lt;p&gt;Aside from "making your server talk" and diving into the cool speech features, I found the most challenging part working on IVR is designing the views. Unlike APIs and Forms, IVR is very limited in the type of input it takes (DTMF tones, transcribed speech), and the amount of data it can communicate and process is limited.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.twilio.com/blog/building-interactive-voice-response-ivr-system-python-django-twilio" rel="noopener"&gt;&lt;strong&gt;Read "Building an Interactive Voice Response (IVR) System with Python, Django and Twilio" on the Twilio blog ≫&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;figure&gt;&lt;img alt="Building an IVR System with Django and Twilio" src="https://hakibenita.com/images/00-python-django-twilio-ivr.png"&gt;&lt;figcaption&gt;Building an IVR System with Django and Twilio&lt;/figcaption&gt;
&lt;/figure&gt;</content><category term="articles"></category><category term="Django"></category><category term="Python"></category></entry><entry><title>Understand Group by in Django with SQL</title><link href="https://hakibenita.com/django-group-by-sql" rel="alternate"></link><published>2020-02-11T00:00:00+02:00</published><updated>2020-02-11T00:00:00+02:00</updated><author><name>Haki Benita</name></author><id>tag:hakibenita.com,2020-02-11:/django-group-by-sql</id><summary type="html">&lt;p&gt;Understand GROUP BY in Django ORM by comparing QuerySets and SQL side by side. If SQL is where you are most comfortable, this is the Django GROUP BY tutorial for you.&lt;/p&gt;</summary><content type="html">&lt;hr&gt;
&lt;p&gt;Aggregation is a source of confusion in any type of ORM and Django is no different. The documentation provides a variety of examples and cheat-sheets that demonstrate how to group and aggregate data using the ORM, but I decided to approach this from a different angle.&lt;/p&gt;
&lt;p&gt;In this article I put &lt;strong&gt;QuerySets and SQL side by side&lt;/strong&gt;. If SQL is where you are most comfortable, this is the Django GROUP BY cheat-sheet for you.&lt;/p&gt;
&lt;figure&gt;&lt;img alt="Image by &amp;lt;a href=&amp;quot;https://unsplash.com/photos/D4YrzSwyIEc&amp;quot;&amp;gt;Jason Leung&amp;lt;/a&amp;gt;" src="https://hakibenita.com/images/00-django-group-by-sql.jpg"&gt;&lt;figcaption&gt;Image by &lt;a href="https://unsplash.com/photos/D4YrzSwyIEc"&gt;Jason Leung&lt;/a&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;details class="toc-container" open&gt;
    &lt;summary&gt;Table of Contents&lt;/summary&gt;&lt;/p&gt;
&lt;div class="toc"&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#how-to-group-by-in-django"&gt;How to Group By in Django&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#how-to-count-rows"&gt;How to Count Rows&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#how-to-use-aggregate-functions"&gt;How to Use Aggregate Functions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#how-to-group-by"&gt;How to Group By&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#how-to-filter-a-queryset-with-group-by"&gt;How to Filter a QuerySet With Group By&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#how-to-sort-a-queryset-with-group-by"&gt;How to Sort a QuerySet With Group By&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#how-to-combine-multiple-aggregations"&gt;How to Combine Multiple Aggregations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#how-to-group-by-multiple-fields"&gt;How to Group by Multiple Fields&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#how-to-group-by-an-expression"&gt;How to Group by an Expression&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#how-to-use-conditional-aggregation"&gt;How to Use Conditional Aggregation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#how-to-use-having"&gt;How to Use Having&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#how-to-group-by-distinct"&gt;How to Group by Distinct&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#how-to-create-expressions-using-aggregate-fields"&gt;How to Create Expressions Using Aggregate Fields&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#how-to-group-by-across-relations"&gt;How to Group By Across Relations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#how-to-group-by-a-many-to-many-relationship"&gt;How to Group By a Many to Many Relationship&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#going-further"&gt;Going Further&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;p&gt;&lt;/details&gt;&lt;/p&gt;
&lt;style&gt;
.side-by-side {
    display: flex;
}
.side-by-side .highlight {
    flex-grow: 1;
    flex-shrink: 0;
    width: 50%;
}
.side-by-side .highlight pre {
    height: 100%;
}

/* Desktop */
@media only screen and (min-width: 52rem) {
    .side-by-side .highlight:first-child {
        margin-right: 1em;
    }
}
/* Mobile */
@media only screen and (max-width: 52rem) {
    .side-by-side {
        flex-direction: column;
    }
    .side-by-side .highlight {
        width: 100%;
    }
    .side-by-side .highlight:first-child {
        margin-bottom: 0;
        border-bottom: 1px solid rgba(0,0,0,0.2);
    }
}
&lt;/style&gt;

&lt;hr&gt;
&lt;h2 id="how-to-group-by-in-django"&gt;&lt;a class="toclink" href="#how-to-group-by-in-django"&gt;How to Group By in Django&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To demonstrate different GROUP BY queries, I will use models from Django's built-in &lt;code&gt;django.contrib.auth&lt;/code&gt; app.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.contrib.auth.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Django ORM produces SQL statements with long aliases. For brevity, I will show a cleaned-up, but equivalent, version of what Django executes.&lt;/p&gt;
&lt;div class="admonition tip"&gt;
&lt;p class="admonition-title"&gt;SQL Logging&lt;/p&gt;
&lt;p&gt;To see the SQL actually executed by Django, you can &lt;a href="all-you-need-to-know-about-prefetching-in-django#before-we-start"&gt;turn on SQL logging in the Django settings&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id="how-to-count-rows"&gt;&lt;a class="toclink" href="#how-to-count-rows"&gt;How to Count Rows&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Let's count how many users we have:&lt;/p&gt;
&lt;section class="side-by-side"&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;auth_user&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;/section&gt;
&lt;p&gt;Counting rows is so common that Django includes a function for it right on the QuerySet. Unlike other QuerySets we'll see next, &lt;code&gt;count&lt;/code&gt; returns a number.&lt;/p&gt;
&lt;h3 id="how-to-use-aggregate-functions"&gt;&lt;a class="toclink" href="#how-to-use-aggregate-functions"&gt;How to Use Aggregate Functions&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Django offers two more ways to count rows in a table.&lt;/p&gt;
&lt;p&gt;We'll start with &lt;a href="https://docs.djangoproject.com/en/3.0/ref/models/querysets/#aggregate" rel="noopener"&gt;&lt;code&gt;aggregate&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;section class="side-by-side"&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;id__count&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;auth_user&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Count&lt;/span&gt;

&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;aggregate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;/section&gt;
&lt;p&gt;To use &lt;code&gt;aggregate&lt;/code&gt; we imported the aggregate function &lt;code&gt;Count&lt;/code&gt;. The function accepts an expression to count. In this case, we used the name of the primary key column &lt;code&gt;id&lt;/code&gt; to count all the rows in the table.&lt;/p&gt;
&lt;div class="admonition warning"&gt;
&lt;p class="admonition-title"&gt;Aggregate NULL&lt;/p&gt;
&lt;p&gt;Aggregations ignore &lt;code&gt;NULL&lt;/code&gt; values. For more on how aggregations handle &lt;code&gt;NULL&lt;/code&gt;, see &lt;a href="sql-dos-and-donts#be-careful-when-counting-nullable-columns"&gt;12 Common Mistakes and Missed Optimization Opportunities in SQL&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;The result of &lt;code&gt;aggregate&lt;/code&gt; is a dict:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Count&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;aggregate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="go"&gt;{&amp;quot;id__count&amp;quot;: 891}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The name of the key is derived from the name of the field and the name of the aggregate. In this case, it's &lt;code&gt;id__count&lt;/code&gt;. It's a good idea not to rely on this naming convention, and instead provide your own name:&lt;/p&gt;
&lt;section class="side-by-side"&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;auth_user&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Count&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;aggregate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="go"&gt;{&amp;quot;total&amp;quot;: 891}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;/section&gt;
&lt;p&gt;The name of the argument to &lt;code&gt;aggregate&lt;/code&gt; is also the name of the key in the resulting dictionary.&lt;/p&gt;
&lt;h3 id="how-to-group-by"&gt;&lt;a class="toclink" href="#how-to-group-by"&gt;How to Group By&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Using &lt;code&gt;aggregate&lt;/code&gt; we got the result of applying the aggregate function on the entire table. This is useful, but usually we want to apply the aggregation on groups of rows.&lt;/p&gt;
&lt;p&gt;Let's count users by their active status:&lt;/p&gt;
&lt;section class="side-by-side"&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;is_active&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;auth_user&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;is_active&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;
&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;is_active&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;annotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;/section&gt;
&lt;p&gt;This time we used the function &lt;a href="https://docs.djangoproject.com/en/3.0/ref/models/querysets/#django.db.models.query.QuerySet.annotate" rel="noopener"&gt;&lt;code&gt;annotate&lt;/code&gt;&lt;/a&gt;. To produce a GROUP BY we use a combination of &lt;code&gt;values&lt;/code&gt; and &lt;code&gt;annotate&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;values('is_active')&lt;/code&gt;: what to group by&lt;/li&gt;
&lt;li&gt;&lt;code&gt;annotate(total=Count('id'))&lt;/code&gt;: what to aggregate&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;The order is important&lt;/strong&gt;: failing to call &lt;code&gt;values&lt;/code&gt; before &lt;code&gt;annotate&lt;/code&gt; will not produce aggregate results.&lt;/p&gt;
&lt;p&gt;Just like &lt;code&gt;aggregate&lt;/code&gt;, the name of the argument to &lt;code&gt;annotate&lt;/code&gt; is the key in the result of the evaluated QuerySet. In this case it's &lt;code&gt;total&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="how-to-filter-a-queryset-with-group-by"&gt;&lt;a class="toclink" href="#how-to-filter-a-queryset-with-group-by"&gt;How to Filter a QuerySet With Group By&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To apply aggregation on a filtered query you can use &lt;a href="https://docs.djangoproject.com/en/3.0/ref/models/querysets/#filter" rel="noopener"&gt;&lt;code&gt;filter&lt;/code&gt;&lt;/a&gt; anywhere in the query. For example, count only staff users by their active status:&lt;/p&gt;
&lt;section class="side-by-side"&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;is_active&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;auth_user&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;is_staff&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;True&lt;/span&gt;
&lt;/span&gt;&lt;span class="k"&gt;GROUP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;is_active&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;
&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;is_active&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;is_staff&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;annotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;/section&gt;
&lt;h3 id="how-to-sort-a-queryset-with-group-by"&gt;&lt;a class="toclink" href="#how-to-sort-a-queryset-with-group-by"&gt;How to Sort a QuerySet With Group By&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Like filter, to sort a queryset use &lt;a href="https://docs.djangoproject.com/en/3.0/ref/models/querysets/#order-by" rel="noopener"&gt;&lt;code&gt;order_by&lt;/code&gt;&lt;/a&gt; anywhere in the query:&lt;/p&gt;
&lt;section class="side-by-side"&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;is_active&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;auth_user&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;is_active&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="k"&gt;ORDER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;is_active&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;
&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;is_active&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;annotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;order_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;is_active&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;total&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;/section&gt;
&lt;p&gt;Notice that you can sort by both the GROUP BY key and the aggregate field.&lt;/p&gt;
&lt;h3 id="how-to-combine-multiple-aggregations"&gt;&lt;a class="toclink" href="#how-to-combine-multiple-aggregations"&gt;How to Combine Multiple Aggregations&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To produce multiple aggregations of the same group, add multiple annotations:&lt;/p&gt;
&lt;section class="side-by-side"&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;is_active&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;date_joined&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;last_joined&lt;/span&gt;
&lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;auth_user&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;is_active&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Max&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;
&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;is_active&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;annotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="hll"&gt;    &lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;    &lt;span class="n"&gt;last_joined&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;date_joined&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;/section&gt;
&lt;p&gt;The query will produce the number of active and inactive users, and the last date a user joined in each group.&lt;/p&gt;
&lt;h3 id="how-to-group-by-multiple-fields"&gt;&lt;a class="toclink" href="#how-to-group-by-multiple-fields"&gt;How to Group by Multiple Fields&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Just like performing multiple aggregations, we might also want to group by multiple fields. For example, group by active status and staff status:&lt;/p&gt;
&lt;section class="side-by-side"&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;is_active&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;is_staff&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;auth_user&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;is_active&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;is_staff&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;is_active&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;is_staff&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;annotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;/section&gt;
&lt;p&gt;The result of this query includes &lt;code&gt;is_active&lt;/code&gt;, &lt;code&gt;is_staff&lt;/code&gt; and the number of users in each group.&lt;/p&gt;
&lt;h3 id="how-to-group-by-an-expression"&gt;&lt;a class="toclink" href="#how-to-group-by-an-expression"&gt;How to Group by an Expression&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Another common use case for GROUP BY is to group by an expression. For example, count the number of users that joined each year:&lt;/p&gt;
&lt;section class="side-by-side"&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;EXTRACT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;year&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;date_joined&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;auth_user&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;EXTRACT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;year&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;date_joined&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;date_joined__year&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;annotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;/section&gt;
&lt;p&gt;Notice that to get the year from the date we used the special expression &lt;code&gt;&amp;lt;field&amp;gt;__year&lt;/code&gt; in the first call to &lt;code&gt;values()&lt;/code&gt;. The result of the query is a dict, and the name of the key will be &lt;code&gt;date_joined__year&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Sometimes, the built-in expressions are not enough, and you need to aggregate on a more complicated expression. For example, group by users that have logged in since they signed-up:&lt;/p&gt;
&lt;section class="side-by-side"&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;last_login&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;date_joined&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;logged_since_joined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;auth_user&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;last_login&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;date_joined&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;ExpressionWrapper&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Q&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;F&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;BooleanField&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;
&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;annotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="hll"&gt;    &lt;span class="n"&gt;logged_since_joined&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ExpressionWrapper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;        &lt;span class="n"&gt;Q&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;last_login__gt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;F&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;date_joined&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;        &lt;span class="n"&gt;output_field&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;BooleanField&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;logged_since_joined&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;annotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;logged_since_joined&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;total&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;/section&gt;
&lt;p&gt;The expression here is fairly complicated. We first use &lt;code&gt;annotate&lt;/code&gt; to built the expression, and we mark it as a GROUP BY key by referencing the expression in the following call to &lt;code&gt;values()&lt;/code&gt;. From here on, it's exactly the same.&lt;/p&gt;
&lt;h3 id="how-to-use-conditional-aggregation"&gt;&lt;a class="toclink" href="#how-to-use-conditional-aggregation"&gt;How to Use Conditional Aggregation&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Using conditional aggregation, you can aggregate only a part of the group. Conditions come in handy when you have multiple aggregates. For example, count the number of staff and non-staff users by the year they signed-up:&lt;/p&gt;
&lt;section class="side-by-side"&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;EXTRACT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;year&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;date_joined&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;FILTER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;is_staff&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;True&lt;/span&gt;
&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;staff_users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;FILTER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;is_staff&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;False&lt;/span&gt;
&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;non_staff_users&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;auth_user&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;EXTRACT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;year&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;date_joined&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;F&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Q&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;
&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;date_joined__year&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;annotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;staff_users&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="hll"&gt;        &lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;filter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Q&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;is_staff&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;non_staff_users&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="hll"&gt;        &lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;filter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Q&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;is_staff&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;    &lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;/section&gt;
&lt;p&gt;The SQL above is from PostgreSQL, which along with SQLite is currently the only database backend that supports the &lt;code&gt;FILTER&lt;/code&gt; syntax shortcut (formally called &lt;a href="https://modern-sql.com/feature/filter" rel="noopener"&gt;"selective aggregates"&lt;/a&gt;). For other database backends, the ORM will use &lt;code&gt;CASE ... WHEN&lt;/code&gt; instead.&lt;/p&gt;
&lt;div class="admonition tip"&gt;
&lt;p class="admonition-title"&gt;tip&lt;/p&gt;
&lt;p&gt;I previously wrote about aggregations with filters. Check out my &lt;a href="9-django-tips-for-working-with-databases#aggregation-with-filter"&gt;9 Django tips for working with databases&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id="how-to-use-having"&gt;&lt;a class="toclink" href="#how-to-use-having"&gt;How to Use Having&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;HAVING&lt;/code&gt; clause is used to filter on the result of an aggregate function. For example, find the years in which more than a 100 users joined:&lt;/p&gt;
&lt;section class="side-by-side"&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;is_active&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;auth_user&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;is_active&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="k"&gt;HAVING&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;annotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;year_joined&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;F&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;date_joined__year&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;is_active&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;annotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total__gt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;/section&gt;
&lt;p&gt;The filter on the annotated field &lt;code&gt;total&lt;/code&gt; added an HAVING clause in the generated SQL.&lt;/p&gt;
&lt;h3 id="how-to-group-by-distinct"&gt;&lt;a class="toclink" href="#how-to-group-by-distinct"&gt;How to Group by Distinct&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;For some aggregate functions such as &lt;code&gt;COUNT&lt;/code&gt;, it is sometimes desirable to only count distinct occurrences. For example, how many different last names are there per user active status:&lt;/p&gt;
&lt;section class="side-by-side"&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;is_active&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;last_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;unique_names&lt;/span&gt;
&lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;auth_user&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;is_active&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;
&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;is_active&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;annotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="hll"&gt;    &lt;span class="n"&gt;unique_names&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;last_name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;distinct&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;/section&gt;
&lt;p&gt;Notice the use of &lt;code&gt;distinct=True&lt;/code&gt; in the call to &lt;code&gt;Count&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="how-to-create-expressions-using-aggregate-fields"&gt;&lt;a class="toclink" href="#how-to-create-expressions-using-aggregate-fields"&gt;How to Create Expressions Using Aggregate Fields&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Aggregate fields are often just the first step to a greater question. For example, what is the &lt;em&gt;percent&lt;/em&gt; of unique last names by user active status:&lt;/p&gt;
&lt;section class="side-by-side"&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;is_active&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;last_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;unique_names&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;last_name&lt;/span&gt;&lt;span class="p"&gt;)::&lt;/span&gt;&lt;span class="nb"&gt;float&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)::&lt;/span&gt;&lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;pct_unique_names&lt;/span&gt;
&lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;auth_user&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;is_active&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FloatField&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db.models.functions&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Cast&lt;/span&gt;

&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;
&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;is_active&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;annotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;unique_names&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;last_name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;distinct&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;annotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pct_unique_names&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;    &lt;span class="n"&gt;Cast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;unique_names&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FloatField&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;    &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;Cast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;total&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FloatField&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;/section&gt;
&lt;p&gt;The first &lt;code&gt;annotate()&lt;/code&gt; defines the aggregate fields. The second &lt;code&gt;annotate()&lt;/code&gt; uses the aggregate function to construct an expression.&lt;/p&gt;
&lt;h3 id="how-to-group-by-across-relations"&gt;&lt;a class="toclink" href="#how-to-group-by-across-relations"&gt;How to Group By Across Relations&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;So far we've used only data in a single model, but aggregates are often used across relations. The simpler scenario is of a one-to-one or a foreign key relation. For example, say we have a &lt;code&gt;UserProfile&lt;/code&gt; with a one-to-one relationship to the User, and we want to count users by the type of profile:&lt;/p&gt;
&lt;section class="side-by-side"&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;auth_user&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;JOIN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;user_profile&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;
&lt;/span&gt;&lt;span class="k"&gt;GROUP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;type&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;
&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;user_profile__type&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;annotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;/section&gt;
&lt;p&gt;Just like GROUP BY expressions, using relations in &lt;code&gt;values&lt;/code&gt; will group by that field. Note that the name of the user profile type in the result will be 'user_profile__type'.&lt;/p&gt;
&lt;h3 id="how-to-group-by-a-many-to-many-relationship"&gt;&lt;a class="toclink" href="#how-to-group-by-a-many-to-many-relationship"&gt;How to Group By a Many to Many Relationship&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;A more complicated type of relation is the many to many relationship. For example, count in how many groups each user is a member:&lt;/p&gt;
&lt;section class="side-by-side"&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;group_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;memberships&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;auth_user&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;LEFT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;OUTER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;JOIN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;auth_user_groups&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ug&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;span class="k"&gt;GROUP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;
&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;annotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;memberships&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;groups&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;memberships&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;/section&gt;
&lt;p&gt;A user can be a member of more than one group. To count the number of groups the user is member of we used the related name "groups" in the &lt;code&gt;User&lt;/code&gt; model. If the related name is not explicitly set (and not explicitly disabled), Django will automatically generate a name in the format &lt;code&gt;{related model model}_set&lt;/code&gt;. For example, &lt;code&gt;group_set&lt;/code&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="going-further"&gt;&lt;a class="toclink" href="#going-further"&gt;Going Further&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To dig deeper into the ORM and GROUP BY in particular, check out these links:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="/how-to-use-grouping-sets-in-django"&gt;How to use grouping sets in Django&lt;/a&gt;: An article about advanced group by techniques such as group by cube, group by rollup and group by grouping sets.&lt;/li&gt;
&lt;li&gt;&lt;a href="/sql-group-by-first-last-value"&gt;How to Get the First or Last Value in a Group Using Group By in SQL&lt;/a&gt;: A neat little trick using arrays in PostgreSQL.&lt;/li&gt;
&lt;li&gt;&lt;a href="/sql-dos-and-donts"&gt;12 Common Mistakes and Missed Optimization Opportunities in SQL&lt;/a&gt;: Some SQL do's and dont's you need to know if you are working with data and writing SQL.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.djangoproject.com/en/3.0/topics/db/aggregation/#cheat-sheet" rel="noopener"&gt;Django Aggregation cheat-sheet page&lt;/a&gt;: How to do common aggregate queries.&lt;/li&gt;
&lt;/ul&gt;</content><category term="articles"></category><category term="SQL"></category><category term="Django"></category><category term="ORM"></category></entry><entry><title>How "Export to Excel" Almost Killed Our System</title><link href="https://hakibenita.com/python-django-optimizing-excel-export" rel="alternate"></link><published>2019-09-17T00:00:00+03:00</published><updated>2019-09-17T00:00:00+03:00</updated><author><name>Haki Benita</name></author><id>tag:hakibenita.com,2019-09-17:/python-django-optimizing-excel-export</id><summary type="html">&lt;p&gt;Inspired by an actual incident we had in one of our systems caused by an "Export to Excel" functionality implemented in Python, we go through the process of identifying the problem, experimenting and benchmarking different solutions.&lt;/p&gt;</summary><content type="html">&lt;hr&gt;
&lt;p&gt;A few weeks ago we had some trouble with an "Export to Excel" functionality in one of our systems. In the process of resolving this issue, we made some interesting discoveries and came up with original solutions.&lt;/p&gt;
&lt;p&gt;This article is inspired by the actual issue we used to track this incident over a period of two days. We go through the process of identifying the problem, experimenting and benchmarking different solutions until eventually deploying to production.&lt;/p&gt;
&lt;p&gt;These are the main takeaways described in this article:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Generating xlsx files can consume significant amount of resources.&lt;/li&gt;
&lt;li&gt;Under some circumstances better performance can be gained by not using &lt;code&gt;prefetch_related&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pyexcelerate&lt;/code&gt; is a fast package for creating simple Excel files.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tablib&lt;/code&gt; (and &lt;code&gt;django-import-export&lt;/code&gt;) can be patched to use &lt;code&gt;pyexcelerate&lt;/code&gt; and produce excel files faster.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;details class="toc-container"&gt;
    &lt;summary&gt;Table of Contents&lt;/summary&gt;&lt;/p&gt;
&lt;div class="toc"&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#exporting-a-queryset-to-excel"&gt;Exporting a QuerySet to Excel&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#using-django-import-export"&gt;Using django-import-export&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#finding-the-best-file-format"&gt;Finding the Best File Format&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#improving-the-query"&gt;Improving the Query&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#replacing-prefetch_related-with-subquery-and-outerref"&gt;Replacing prefetch_related with Subquery and OuterRef&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#using-an-iterator"&gt;Using an Iterator&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#simplifying-the-query"&gt;Simplifying the Query&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#manual-prefetch"&gt;Manual Prefetch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#trouble-in-paradise"&gt;Trouble in Paradise&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#using-a-different-excel-writer"&gt;Using a Different Excel Writer&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#a-faster-excel-writer-in-python"&gt;A Faster Excel Writer in Python&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#patching-tablib"&gt;Patching tablib&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#results-summary"&gt;Results Summary&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#seifa"&gt;Seifa&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#comments-from-readers"&gt;Comments From Readers&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;p&gt;&lt;/details&gt;&lt;/p&gt;
&lt;figure&gt;&lt;img alt="How a server must feel when asked to produce an Excel file" src="https://hakibenita.com/images/00-python-django-optimizing-excel-export.jpg"&gt;&lt;figcaption&gt;How a server must feel when asked to produce an Excel file&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;hr&gt;
&lt;p&gt;A few weeks ago we started getting complaints from users about slow response time from one of our systems. A quick glance at the server metrics showed higher than normal CPU usage. This system is mostly IO intensive, so high CPU usage is not something we experience regularly.&lt;/p&gt;
&lt;p&gt;The first thing we did was to identify the worker process that is consuming high CPU using &lt;code&gt;htop&lt;/code&gt;. After getting the process identifier (PID) of the process, we used &lt;a href="https://github.com/benfred/py-spy" rel="noopener"&gt;py-spy&lt;/a&gt; to get a glance at what it's doing:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;py-spy&lt;span class="w"&gt; &lt;/span&gt;-p&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;8187&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-r&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1000&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This command samples the process a 1000 times per second and provides a &lt;code&gt;top&lt;/code&gt;-like view of the results:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="go"&gt;Total Samples 17974&lt;/span&gt;
&lt;span class="go"&gt;GIL: 0.00%, Active: 0.00%, Threads: 1&lt;/span&gt;

&lt;span class="go"&gt;OwnTime  TotalTime  Function (filename:line)&lt;/span&gt;
&lt;span class="go"&gt;0.000s    173.7s   get_response (django/core/handlers/base.py:75)&lt;/span&gt;
&lt;span class="go"&gt;0.000s    173.7s   inner (django/core/handlers/exception.py:34)&lt;/span&gt;
&lt;span class="go"&gt;0.000s    173.7s   __call__ (django/utils/deprecation.py:94)&lt;/span&gt;
&lt;span class="go"&gt;0.000s    173.7s   __call__ (django/core/handlers/wsgi.py:141)&lt;/span&gt;
&lt;span class="go"&gt;0.000s    173.6s   view (django/views/generic/base.py:71)&lt;/span&gt;
&lt;span class="go"&gt;0.000s    173.6s   _get_response (django/core/handlers/base.py:113)&lt;/span&gt;
&lt;span class="go"&gt;0.000s    173.6s   dispatch (django/contrib/auth/mixins.py:52)&lt;/span&gt;
&lt;span class="go"&gt;0.000s    173.6s   dispatch (django/contrib/auth/mixins.py:109)&lt;/span&gt;
&lt;span class="go"&gt;0.000s    173.6s   dispatch (django/views/generic/base.py:97)&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="go"&gt;0.050s    173.6s   get (dashboard/views/list_views.py:100)&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="go"&gt;0.000s    94.69s   get_resource_to_export (dashboard/views/list_views.py:70)&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="go"&gt;0.000s    94.69s   export (dashboard/views/list_views.py:73)&lt;/span&gt;
&lt;/span&gt;&lt;span class="go"&gt;0.000s    94.68s   export (dashboard/resources.py:215)&lt;/span&gt;
&lt;span class="go"&gt;0.000s    83.81s   __iter__ (django/db/models/query.py:274)&lt;/span&gt;
&lt;span class="go"&gt;0.040s    82.73s   _fetch_all (django/db/models/query.py:1242)&lt;/span&gt;
&lt;span class="go"&gt;0.000s    78.84s   export (dashboard/views/list_views.py:74)&lt;/span&gt;
&lt;span class="go"&gt;0.000s    70.58s   __iter__ (django/db/models/query.py:55)&lt;/span&gt;
&lt;span class="go"&gt;0.000s    68.98s   execute_sql (django/db/models/sql/compiler.py:1100)&lt;/span&gt;
&lt;span class="go"&gt;68.81s    68.81s   _execute (django/db/backends/utils.py:84)&lt;/span&gt;
&lt;span class="go"&gt;0.000s    68.81s   _execute_with_wrappers (django/db/backends/utils.py:76)&lt;/span&gt;
&lt;span class="go"&gt;0.000s    68.81s   execute (django/db/backends/utils.py:67)&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="go"&gt;0.000s    50.11s   save (tablib/packages/openpyxl3/workbook.py:186)&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="go"&gt;0.000s    50.11s   export_set (tablib/formats/_xlsx.py:46)&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="go"&gt;0.000s    46.41s   save (tablib/packages/openpyxl3/writer/excel.py:124)&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="go"&gt;0.000s    46.41s   save_workbook (tablib/packages/openpyxl3/writer/excel.py:141)&lt;/span&gt;
&lt;/span&gt;&lt;span class="go"&gt;0.000s    42.40s   _fetch_all (django/db/models/query.py:1244)&lt;/span&gt;
&lt;span class="go"&gt;0.000s    42.40s   _prefetch_related_objects (django/db/models/query.py:771)&lt;/span&gt;
&lt;span class="go"&gt;0.000s    42.38s   prefetch_related_objects (django/db/models/query.py:1625)&lt;/span&gt;
&lt;span class="go"&gt;0.000s    41.94s   prefetch_one_level (django/db/models/query.py:1738)&lt;/span&gt;
&lt;span class="go"&gt;0.000s    41.25s   get_prefetch_queryset (django/db/models/fields/related_descriptors.py:627)&lt;/span&gt;
&lt;span class="go"&gt;0.000s    32.30s   _write_worksheets (tablib/packages/openpyxl3/writer/excel.py:91)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;After monitoring this view for a minute or two, we had a few insights:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A lot of time is spent fetching data.&lt;/li&gt;
&lt;li&gt;A lot of time is spent on &lt;em&gt;some&lt;/em&gt; call to &lt;code&gt;prefetch_related&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The problem is in the dashboard, and more specifically in the view that exports data.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;With these insights, we wanted to moved on to identify the exact view. We then turned to the &lt;a href="https://www.nginx.com/" rel="noopener"&gt;nginx&lt;/a&gt; access log:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;journalctl&lt;span class="w"&gt; &lt;/span&gt;-u&lt;span class="w"&gt; &lt;/span&gt;nginx&lt;span class="w"&gt; &lt;/span&gt;-r&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;grep&lt;span class="w"&gt; &lt;/span&gt;dashboard
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We managed to identify several endpoints that were taking very long to execute. Some of them finished in just under 60 seconds, others were killed by PostgreSQL after hitting the &lt;a href="https://www.postgresql.org/docs/current/runtime-config-client.html#GUC-STATEMENT-TIMEOUT" rel="noopener"&gt;&lt;code&gt;statement_timeout&lt;/code&gt; limit&lt;/a&gt; and returned a 500 status code.&lt;/p&gt;
&lt;p&gt;At this point we had a pretty good idea where the problem is, but we were still clueless as to why. The next step was to inspect the problematic code, and try to reproduce.&lt;/p&gt;
&lt;h2 id="exporting-a-queryset-to-excel"&gt;&lt;a class="toclink" href="#exporting-a-queryset-to-excel"&gt;Exporting a QuerySet to Excel&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The system is used to report and track violations in public transportation. During an inspection, the inspector documents different types of violations such as dirty bus, bus running late etc. The models for this system look roughly like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ViolationType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&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;name&lt;/span&gt; &lt;span class="o"&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;TextField&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;Inspection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AutoField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;primary_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&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;Violation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&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;inspection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Inspection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;on_delete&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PROTECT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;violation_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ViolationType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;on_delete&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PROTECT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;comments&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TextField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Every once in a while, a back office user would download the inspection information to Excel for further analysis.&lt;/p&gt;
&lt;figure&gt;&lt;img alt="Users + Excel = Love" src="https://hakibenita.com/images/01-python-django-optimizing-excel-export.jpg"&gt;&lt;figcaption&gt;Users + Excel = Love&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The report includes a lot of information about the inspection, but most importantly, it includes a list of the violation types for each inspection:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;inspection, violations
1, dirty floors | full trash can
2, full trash can | no light | missing signs
&lt;/pre&gt;&lt;/div&gt;


&lt;h3 id="using-django-import-export"&gt;&lt;a class="toclink" href="#using-django-import-export"&gt;Using &lt;code&gt;django-import-export&lt;/code&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To produce the Excel report we use a package called &lt;a href="https://github.com/django-import-export/django-import-export" rel="noopener"&gt;&lt;code&gt;django-import-export&lt;/code&gt;&lt;/a&gt;. Using the package, we define a &lt;a href="https://django-import-export.readthedocs.io/en/latest/api_resources.html#modelresource" rel="noopener"&gt;&lt;code&gt;ModelResource&lt;/code&gt;&lt;/a&gt; that can produce an Excel file from a queryset:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;import_export&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;widgets&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Inspection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Violation&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;InspectionResource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resources&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ModelResource&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;violations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;widget&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;widgets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ManyToManyWidget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Violation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;violation_type&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&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;Inspection&lt;/span&gt;
        &lt;span class="n"&gt;fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;violations&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The query produced by this &lt;code&gt;ModelResource&lt;/code&gt; causes an &lt;a href="/things-you-must-know-about-django-admin-as-your-app-gets-bigger#the-n1-problem"&gt;N+1 queries issue&lt;/a&gt;, so before we ever deployed it to production we patched it and added &lt;a href="https://docs.djangoproject.com/en/2.2/ref/models/querysets/#prefetch-related" rel="noopener"&gt;&lt;code&gt;prefetch_related&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Prefetch&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;import_export&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;widgets&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Inspection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Violation&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;InspectionResource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resources&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ModelResource&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;violations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;widget&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;widgets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ManyToManyWidget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Violation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;violation_type&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&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;Inspection&lt;/span&gt;
        &lt;span class="n"&gt;fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;violations&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;export&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;queryset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&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="p"&gt;(&lt;/span&gt;
&lt;span class="hll"&gt;            &lt;span class="n"&gt;queryset&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prefetch_related&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Prefetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;                &lt;span class="s1"&gt;&amp;#39;violations&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;                &lt;span class="n"&gt;queryset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Violation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;select_related&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;violation_type&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;                &lt;span class="n"&gt;to_attr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;prefetched_violations&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;            &lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;export&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;queryset&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;dehydrate_violations&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;inspection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Inspection&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;violation_type&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;inspection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prefetched_violations&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;To use &lt;code&gt;prefetch_related&lt;/code&gt; in a &lt;code&gt;ModelResource&lt;/code&gt; we had to make the following changes:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Override &lt;code&gt;export&lt;/code&gt; and adjust the query to prefetch the violations using &lt;code&gt;prefetch_related&lt;/code&gt;. We use the &lt;a href="https://docs.djangoproject.com/en/2.2/ref/models/querysets/#django.db.models.Prefetch" rel="noopener"&gt;&lt;code&gt;Prefetch&lt;/code&gt; object&lt;/a&gt; because we needed to customize the prefetch query, and add the violation type name from a related table.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Evaluate the query and have the export function return a list instead of a queryset. &lt;code&gt;django-import-export&lt;/code&gt; uses &lt;code&gt;iterator&lt;/code&gt; to speed up the query. Using &lt;a href="https://docs.djangoproject.com/en/2.2/ref/models/querysets/#iterator" rel="noopener"&gt;&lt;code&gt;iterator()&lt;/code&gt;&lt;/a&gt;, the ORM uses a cursor to iterate over the data in chunks and reduce memory. While this is usually useful, Django is unable to use &lt;code&gt;iterator()&lt;/code&gt; with &lt;code&gt;prefetch_related&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add a custom &lt;code&gt;dehydrate_&lt;/code&gt; function for the violations field that will render a comma-delimited list of violation type names.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="admonition tip"&gt;
&lt;p class="admonition-title"&gt;Prefetch Related&lt;/p&gt;
&lt;p&gt;This is &lt;a href="all-you-need-to-know-about-prefetching-in-django"&gt;all you need to know about prefetching in Django&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;The resource was used by the view to produce the Excel report:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.http&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;HttpRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HttpResponse&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Inspection&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;.resources&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;InspectionResource&lt;/span&gt;

&lt;span class="n"&gt;LIMIT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10000&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;export_to_excel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;HttpRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;HttpResponse&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;inspections&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Inspection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="c1"&gt;# Apply some filter on the queryset based on request&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;InspectionResource&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;export&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inspections&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="n"&gt;LIMIT&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;xlsx&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;HttpResponse&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="n"&gt;content_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;text/xlsx&amp;#39;&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="s1"&gt;&amp;#39;Content-Disposition&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;attachment; filename=export.xlsx&amp;#39;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The view takes a request, apply some filter on the inspections and produces the xlsx file using the &lt;code&gt;ModelResource&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="finding-the-best-file-format"&gt;&lt;a class="toclink" href="#finding-the-best-file-format"&gt;Finding the Best File Format&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Before we can start improving the export process, we need to establish a baseline. To get the timings and identify the hot spots in the call stack we used &lt;a href="https://docs.python.org/3.7/library/profile.html" rel="noopener"&gt;&lt;code&gt;cProfile&lt;/code&gt;&lt;/a&gt;. To identify and time query execution we turned SQL logging on in the Django settings:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# settings.py&lt;/span&gt;

&lt;span class="n"&gt;LOGGING&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;# ...&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;loggers&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;django.db.backends&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;level&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;DEBUG&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="c1"&gt;# ...&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The benchmark looked like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;cProfile&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Inspection&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;.resources&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;InspectionResource&lt;/span&gt;

&lt;span class="n"&gt;qs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;VehicleInspection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()[:&lt;/span&gt;&lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;cProfile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;resources.VehicleInspectionResource().export(qs).xlsx&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;These were the results of exporting 10,000 rows in xlsx format using &lt;code&gt;prefetch_related&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="go"&gt;56830808 function calls (47142920 primitive calls) in 41.574 seconds&lt;/span&gt;
&lt;span class="go"&gt;select    5.009&lt;/span&gt;
&lt;span class="go"&gt;prefetch  8.009&lt;/span&gt;

&lt;span class="go"&gt;56660555 function calls (47149065 primitive calls) in 39.927 seconds&lt;/span&gt;
&lt;span class="go"&gt;select    2.356&lt;/span&gt;
&lt;span class="go"&gt;prefetch  7.991&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We ran the benchmark twice to make sure the results were not effected by caches. The function took 40s to complete, and only 10s of it (25%) were spent in the database.&lt;/p&gt;
&lt;p&gt;At this point, we suspected that &lt;strong&gt;the problem might be in the file format&lt;/strong&gt;. This assumption was supported by the application server's high CPU usage.&lt;/p&gt;
&lt;p&gt;Next, we wanted to try the same benchmark, only instead of xlsx we produced a csv:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;cProfile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;resources.VehicleInspectionResource().export(qs).csv&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;These were the results of exporting 10,000 rows in csv format using &lt;code&gt;prefetch_related&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="go"&gt;9179705 function calls (9107672 primitive calls) in 17.429 seconds&lt;/span&gt;
&lt;span class="go"&gt;select    1.970&lt;/span&gt;
&lt;span class="go"&gt;prefetch  8.343&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Wow! That's a big improvement. This confirmed our suspicion that the actual production of the xlsx was the problem.&lt;/p&gt;
&lt;p&gt;Before we moved on, we wanted to check another file format that might be more useful to our users, the old xls format:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;cProfile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;resources.VehicleInspectionResource().export(qs).xls&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;These were the results of exporting 10,000 rows in xls format using &lt;code&gt;prefetch_related&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="go"&gt;16317592 function calls (15745704 primitive calls) in 20.694 seconds&lt;/span&gt;
&lt;span class="go"&gt;select    1.922&lt;/span&gt;
&lt;span class="go"&gt;prefetch  7.976&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;OK, so that's surprising. I'm not familiar with the internals of the Microsoft Office file formats, but it seems like the old format is only a little bit slower than the csv format, and much faster than the new xlsx format.&lt;/p&gt;
&lt;p&gt;This benchmark results brought up an old dilemma. In the past we used to serve users with only csv files, but they complained a lot about troubles opening the files, and encoding and formatting issues. For this reason we decided to produce xlsx in the first place, so at that time, producing xls files seemed like the best solution.&lt;/p&gt;
&lt;p&gt;I should already tell you, using the old xls format was a bad decision, but we didn't know that yet.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="improving-the-query"&gt;&lt;a class="toclink" href="#improving-the-query"&gt;Improving the Query&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;After reducing the overall execution time by half, our next targets were the queries. Two queries are executed to produce the dataset for the export. Before any change is made, it took the "main" query ~2s and the prefetch ~8s to complete.&lt;/p&gt;
&lt;p&gt;The "main" query looked like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;inspection&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;-- around 50 more fields from joined tables&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;inspection&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;INNER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;JOIN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;auth_user&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;inspection&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;user_id&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;auth_user&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;-- around 11 more joined tables&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The resource used a lot of data from related tables, and the query joined ~12 tables and had many fields listed in the SELECT clause. The table is one of the main tables in the database so it is heavily indexed, and the lookup tables were relatively small so the query didn't take long to complete.&lt;/p&gt;
&lt;p&gt;The prefetch query looked like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;violation&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;violation&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;inspection_id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;violation&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;violation_type_id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;violation_type&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;violation_type&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;name&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;violation&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;INNER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;JOIN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;violation_type&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;violation&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;violation_type_id&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;violation_type&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;id&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;violation&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;inspection_id&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ANY&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="mi"&gt;2814&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;9330&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8848&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8971&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;9372&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;9084&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;78&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3896&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2609&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5177&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2866&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;-- another 10,000 inspection IDs&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="mi"&gt;1399&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;9348&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;914&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8884&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;9082&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3356&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2896&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;742&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;9432&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8926&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;9153&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This query seems innocent, but in fact, it took ~8s to complete. The execution plan of this query looked like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;Nested&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Loop&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;cost&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1000.28..2040346.39&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;26741&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;181&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Gather&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;cost&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1000.00..2032378.29&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;26741&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;115&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;Workers&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Planned&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;Parallel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Seq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Scan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;violation&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;cost&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.00..2028704.19&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;11142&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;115&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="k"&gt;Filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vehicle_inspection_id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ANY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;{2814,9330,....,8926,9153}&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;integer&lt;/span&gt;&lt;span class="p"&gt;[]))&lt;/span&gt;
&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="k"&gt;Index&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Scan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;using&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;violationtype_pkey&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;violationtype&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;cost&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.28..0.30&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;66&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;Index&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Cond&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;violation&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;violation_type_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;I trimmed the execution plan for brevity, but the &lt;code&gt;Filter&lt;/code&gt; line was three or four pages long, filled with IDs. This got us thinking, is it possible that this huge &lt;code&gt;ANY&lt;/code&gt; filter is what's causing us trouble?&lt;/p&gt;
&lt;h3 id="replacing-prefetch_related-with-subquery-and-outerref"&gt;&lt;a class="toclink" href="#replacing-prefetch_related-with-subquery-and-outerref"&gt;Replacing &lt;code&gt;prefetch_related&lt;/code&gt; with &lt;code&gt;Subquery&lt;/code&gt; and &lt;code&gt;OuterRef&lt;/code&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To answer this question we decided to try and implement the query without &lt;code&gt;prefetch_related&lt;/code&gt;. Instead, we decided to use the new &lt;a href="https://docs.djangoproject.com/en/2.2/ref/models/expressions/#subquery-expressions" rel="noopener"&gt;&lt;code&gt;Subquery&lt;/code&gt; expression&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Using &lt;code&gt;Subquery&lt;/code&gt; the query using the ORM looked like that:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;OuterRef&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Subquery&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Value&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.contrib.postgres.aggregates&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ArrayAgg&lt;/span&gt;

&lt;span class="n"&gt;inspections&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;inspections&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;annotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;violations_csv&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Subquery&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;Violation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;
    &lt;span class="c1"&gt;# Reference the inspection ID of the outer table, inspection.&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inspection_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;OuterRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="c1"&gt;# Prevent Django from adding a group by column.&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;annotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dummy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;1&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;dummy&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# Construct an array of violation names.&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;annotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;violations&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ArrayAgg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;violation_type__name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;distinct&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;violations&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;If you never experimented with &lt;code&gt;Subquery&lt;/code&gt; there is a lot to take in here. Before we break it down, this is what the query looks like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;inspection&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;ARRAY_AGG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;U2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;name&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;violations&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;violation&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;U0&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;INNER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;JOIN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;violationtype&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;U2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;U0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;violation_type_id&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;U2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;U0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;inspection_id&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;inspection&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;violations_csv&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;-- around 50 more fields from joined tables&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;inspection&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;INNER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;JOIN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;auth_user&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;inspection&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;user_id&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;auth_user&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;-- around 11 more joined tables&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Now, let's break it down:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Subquery&lt;/code&gt; is a query expression that can only exist inside another query. In this case, the outer query is &lt;code&gt;inspection&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Subquery&lt;/code&gt; in used in &lt;code&gt;annotate&lt;/code&gt; so the result of the subquery is stored in a another column for each row.&lt;/li&gt;
&lt;li&gt;We added a dummy annotation to prevent Django from grouping the results. The subquery is executed for each inspection, this is what the filter using &lt;code&gt;OuterRef&lt;/code&gt; does. For this reason, we don't need to group by any other column.&lt;/li&gt;
&lt;li&gt;The subquery must return at most one row, so we group the names into an array using &lt;code&gt;ARRAY_AGG&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;After all this hard work, we were keen to see if this is the silver bullet we were waiting for, but in fact, when we executed this on 10,000 rows it choked. To see it through, we executed the export function with only 1,000 rows.&lt;/p&gt;
&lt;p&gt;These were the results of exporting 1,000 rows in xls format using subquery:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;1571053 function calls (1514505 primitive calls) in 60.962 seconds
select 59.917
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The query is now crazy slow. I won't paste the execution plan because there were so many other tables, but PostgreSQL used a nested loop join on the top level of the query to produce the value for this field. Surprisingly, the database did a significantly worse job than the ORM did in this case.&lt;/p&gt;
&lt;h3 id="using-an-iterator"&gt;&lt;a class="toclink" href="#using-an-iterator"&gt;Using an Iterator&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Before we completely abandoned this solution, we wanted to check one last thing. We previously mentioned that &lt;code&gt;django-import-export&lt;/code&gt; is using &lt;code&gt;iterator()&lt;/code&gt; to create a cursor over the results. We also mentioned that using &lt;code&gt;prefetch_related&lt;/code&gt; prevents us from using &lt;code&gt;iterator()&lt;/code&gt;. Well, we no longer use &lt;code&gt;prefetch_related&lt;/code&gt; so we might as well check if using &lt;code&gt;iterator()&lt;/code&gt; makes any difference.&lt;/p&gt;
&lt;p&gt;These were the results of exporting 1,000 rows in xls format using subquery and iterator:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;1571580 function calls (1514788 primitive calls) in 62.130 seconds
select 60.618
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The iterator made no difference.&lt;/p&gt;
&lt;h3 id="simplifying-the-query"&gt;&lt;a class="toclink" href="#simplifying-the-query"&gt;Simplifying the Query&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;In a final attempt to get something out of this expedition, we wanted to see if the complexity of the query prevented PostgreSQL from finding an optimal execution plan. To do that, we could have adjusted the database parameters &lt;a href="https://www.postgresql.org/docs/current/runtime-config-query.html#GUC-FROM-COLLAPSE-LIMIT" rel="noopener"&gt;&lt;code&gt;from_collapse_limit&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://www.postgresql.org/docs/current/runtime-config-query.html#GUC-JOIN-COLLAPSE-LIMIT" rel="noopener"&gt;&lt;code&gt;join_collapse_limit&lt;/code&gt;&lt;/a&gt; and let PostgreSQL take all the time and resources it needs to find an optimal execution plan, but instead, we decided to strip all other fields from the resources besides &lt;code&gt;id&lt;/code&gt; and &lt;code&gt;violations&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;These were the results of exporting 1,000 rows containing only the id and violations fields in xls format using subquery and iterator:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;6937 function calls (6350 primitive calls) in 57.280 seconds
select  57.255
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;No change, this is officially a dead end!&lt;/p&gt;
&lt;h3 id="manual-prefetch"&gt;&lt;a class="toclink" href="#manual-prefetch"&gt;Manual Prefetch&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;After a quick lunch break we decided it's time pull out the big guns. If Django's prefetch implementation wasn't working for us, and PostgreSQL was unable to produce a decent execution plan, we will just have to do it ourselves.&lt;/p&gt;
&lt;p&gt;To implement our own "prefetch" we needed to adjust some of the other functions in the resource:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;import_export&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Inspection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Violation&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;InspectionResource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resources&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ModelResource&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;violations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Field&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;Inspection&lt;/span&gt;
        &lt;span class="n"&gt;fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;violations&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;export&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;queryset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# Manually prefetch the violations.&lt;/span&gt;
&lt;span class="hll"&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prefetched_violations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;            &lt;span class="n"&gt;Violation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inspection_id__in&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;queryset&lt;/span&gt;
                &lt;span class="c1"&gt;# Clean all joins.&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;select_related&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;values_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;pk&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;annotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;violations_csv&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ArrayAgg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;violation_type__name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;values_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s1"&gt;&amp;#39;vehicle_inspection_id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;&amp;#39;violations_csv&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;export&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;queryset&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="hll"&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;dehydrate_violations&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;inspection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Inspection&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prefetched_violations&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;inspection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This looks like a lot, but it's actually not:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;We create our own "prefetch related" dict &lt;code&gt;prefetched_violations&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The key is the violation ID, and the value is an array containing the violation names (&lt;code&gt;violations_csv&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;To fetch only relevant violations, we use filter using &lt;code&gt;queryset&lt;/code&gt; to filter only the necessary inspections.&lt;/li&gt;
&lt;li&gt;We executed &lt;code&gt;select_related(None)&lt;/code&gt; to remove all previously set &lt;code&gt;select_related&lt;/code&gt; tables, and make the ORM remove any unnecessary joins.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;We return the original queryset to the &lt;code&gt;export&lt;/code&gt; function which produces the Excel file.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;To construct the value for the &lt;code&gt;violations&lt;/code&gt; field, we use the &lt;code&gt;prefetched_violations&lt;/code&gt; we populated during &lt;code&gt;export&lt;/code&gt;. This is the "lookup" part of the prefetch. While using Django's &lt;code&gt;prefetch_related&lt;/code&gt; we have access to this value on the instance, when we do it manually we have to look it up ourselves.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Once again, since we no longer use Django's &lt;code&gt;prefetch_related&lt;/code&gt; we were able to use an iterator. So, instead of evaluating the query we return a queryset.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;We already got disappointed after putting in a lot of effort the last time, let's see if this time the hard work paid off.&lt;/p&gt;
&lt;p&gt;These were the results of exporting 10,000 rows in xls format using manual prefetch and iterator:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;15281887 function calls (14721333 primitive calls) in 11.411 seconds
select  0.833
manual prefetch 0.107
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Compared to the 40 seconds we started with, this is an overall 75% improvement. 20s were reduced by switching to xls format, another 10s were from manually doing the prefetch.&lt;/p&gt;
&lt;p&gt;We are ready for production!&lt;/p&gt;
&lt;h3 id="trouble-in-paradise"&gt;&lt;a class="toclink" href="#trouble-in-paradise"&gt;Trouble in Paradise&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Quickly after rolling out the new version to production we started getting complaints from users not being able to open the file.&lt;/p&gt;
&lt;p&gt;Remember I told you using xls was a bad idea? Well, when users started downloading the xls files they got a nasty message saying the file is corrupt, and excel, thank god, managed to salvage some of the data (which is way worse!).&lt;/p&gt;
&lt;p&gt;One might ask, &lt;em&gt;"but how come you didn't catch this in QA?"&lt;/em&gt;. Well, that's just another reason we hate working with Excel. When we tested it locally on our Linux desktops using LibreOffice, it worked just fine.&lt;/p&gt;
&lt;figure&gt;&lt;img alt="&amp;quot;But it works on my machine!&amp;quot;" src="https://hakibenita.com/images/02-python-django-optimizing-excel-export.jpg"&gt;&lt;figcaption&gt;"But it works on my machine!"&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;So let's recap:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;xlsx is slow and consumes a lot of CPU.&lt;/li&gt;
&lt;li&gt;xls is not supported by the excel version used by our users.&lt;/li&gt;
&lt;li&gt;csv has many encoding and formatting issues, and proved to be unusable in the past.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="using-a-different-excel-writer"&gt;&lt;a class="toclink" href="#using-a-different-excel-writer"&gt;Using a Different Excel Writer&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;As always, when all options suck and the future is looking bleak, we turned to Google.&lt;/p&gt;
&lt;p&gt;A quick search of &lt;em&gt;"python excel performance"&lt;/em&gt; brought up &lt;a href="https://gist.github.com/jmcnamara/ba25c2bf4ba0777065eb" rel="noopener"&gt;this gist&lt;/a&gt; which compares 4 different Excel writers in Python (gotta love the internet!).&lt;/p&gt;
&lt;p&gt;These are the benchmark results:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;# Source: https://gist.github.com/jmcnamara/ba25c2bf4ba0777065eb

Versions:
    python      : 2.7.2
    openpyxl    : 2.2.1
    pyexcelerate: 0.6.6
    xlsxwriter  : 0.7.2
    xlwt        : 1.0.0

Dimensions:
    Rows = 10000
    Cols = 50

Times:
&lt;span class="hll"&gt;    pyexcelerate          :  10.63
&lt;/span&gt;    xlwt                  :  16.93
    xlsxwriter (optimised):  20.37
    xlsxwriter            :  24.24
    openpyxl   (optimised):  26.63
&lt;span class="hll"&gt;    openpyxl              :  35.75
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;According to the results, there is a big difference between the xlsx libraries.&lt;/p&gt;
&lt;p&gt;As mentioned before, we use &lt;code&gt;django-import-export&lt;/code&gt; to produce excel files from Django models and querysets. Under the hood, &lt;code&gt;django-import-export&lt;/code&gt; is using the popular &lt;a href="https://docs.python-tablib.org" rel="noopener"&gt;&lt;code&gt;tablib&lt;/code&gt; package&lt;/a&gt; to do the actual export.&lt;/p&gt;
&lt;p&gt;Tablib offers export and import capabilities to and from many formats, but it doesn't do any of the heavy lifting itself. To &lt;a href="https://github.com/vinayak-mehta/tablib/blob/master/tablib/formats/_xlsx.py" rel="noopener"&gt;produce xlsx files&lt;/a&gt;, tablib is using the package &lt;a href="https://openpyxl.readthedocs.io/en/stable/" rel="noopener"&gt;&lt;code&gt;openpyxl&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="a-faster-excel-writer-in-python"&gt;&lt;a class="toclink" href="#a-faster-excel-writer-in-python"&gt;A Faster Excel Writer in Python&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Looking back at the benchmark results, &lt;code&gt;openpyxl&lt;/code&gt; is the slowest among all packages. It looks like by switching to the fastest implementation, &lt;code&gt;pyexcelerate&lt;/code&gt; we might be able to gain some significant improvement for this export process.&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://github.com/kz26/PyExcelerate" rel="noopener"&gt;package &lt;code&gt;pyexcelerate&lt;/code&gt;&lt;/a&gt; looked great from start. The tag line is just what we needed:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;PyExcelerate is a Python for writing Excel-compatible XLSX spreadsheet files, with an emphasis on speed.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Even the snarky subtitles on the &lt;a href="https://github.com/kz26/PyExcelerate#usage" rel="noopener"&gt;"Usage" section&lt;/a&gt; in the README were just what we wanted: &lt;em&gt;fast, faster and fastest!&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;With such promising benchmarks and README, we had to try it out!&lt;/p&gt;
&lt;h3 id="patching-tablib"&gt;&lt;a class="toclink" href="#patching-tablib"&gt;Patching &lt;code&gt;tablib&lt;/code&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;We already have an entire system built on top of &lt;code&gt;django-import-export&lt;/code&gt; and &lt;code&gt;tablib&lt;/code&gt;, and we didn't want to start making changes everywhere. So instead, we looked for a way to patch tablib, and make it use &lt;code&gt;pyexcelerate&lt;/code&gt; instead of &lt;code&gt;openpyxl&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;After some digging, we found that tablib uses an internal function called &lt;a href="https://github.com/vinayak-mehta/tablib/blob/d25d24a9bb1ef0479e800aacbe6ae7dd9e6e35c2/tablib/core.py#L253" rel="noopener"&gt;&lt;code&gt;_register_formats&lt;/code&gt;&lt;/a&gt; to add export and import formats such as csv, xls and xlsx. To get a list of available formats, tablib imports a collection called &lt;code&gt;available&lt;/code&gt; from the module &lt;code&gt;formats&lt;/code&gt;. The contents of the file &lt;a href="https://github.com/vinayak-mehta/tablib/blob/d25d24a9bb1ef0479e800aacbe6ae7dd9e6e35c2/tablib/formats/__init__.py" rel="noopener"&gt;&lt;code&gt;formats/__init__.py&lt;/code&gt;&lt;/a&gt; where the collection is defined, look like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# -*- coding: utf-8 -*-&lt;/span&gt;

&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot; Tablib - formats&lt;/span&gt;
&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;.&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;_csv&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;.&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;_json&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;.&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;_xls&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;xls&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;.&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;_yaml&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;yaml&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;.&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;_tsv&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;tsv&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;.&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;_html&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;html&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;.&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;_xlsx&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;xlsx&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;.&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;_ods&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;ods&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;.&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;_dbf&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;dbf&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;.&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;_latex&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;latex&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;.&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;_df&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;.&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;_rst&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;rst&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;.&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;_jira&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;jira&lt;/span&gt;

&lt;span class="n"&gt;available&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;xls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;yaml&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;csv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dbf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tsv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;jira&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;latex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;xlsx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ods&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rst&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The interesting part is the contents of the file &lt;a href="https://github.com/vinayak-mehta/tablib/blob/d25d24a9bb1ef0479e800aacbe6ae7dd9e6e35c2/tablib/formats/_xlsx.py" rel="noopener"&gt;_xlsx.py&lt;/a&gt;. The file defines some functions to export and import from Excel using &lt;code&gt;openpyxl&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;To patch &lt;code&gt;tablib&lt;/code&gt;, we first need to implement a similar interface to the one in &lt;code&gt;_xlsx.py&lt;/code&gt; using &lt;code&gt;pyexcelerate&lt;/code&gt;, and then register it in &lt;code&gt;tablib&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Let's start with implementing &lt;code&gt;_xlsx.py&lt;/code&gt; using &lt;code&gt;pyexcelerate&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# fast_xlsx.py&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;itertools&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;io&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BytesIO&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;tablib.formats._xlsx&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;  &lt;span class="c1"&gt;# noqa&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;pyexcelerate&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Workbook&lt;/span&gt;


&lt;span class="c1"&gt;# Override the default xlsx implementation&lt;/span&gt;
&lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;xlsx&amp;#39;&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;export_set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;freeze_panes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Returns XLSX representation of Dataset.&amp;quot;&amp;quot;&amp;quot;&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;dataset&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Sheet1&amp;#39;&lt;/span&gt;
    &lt;span class="n"&gt;wb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Workbook&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;wb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new_sheet&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="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;itertools&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;dataset&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;stream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;BytesIO&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;wb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stream&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;stream&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getvalue&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;export_book&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;databook&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;freeze_panes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Returns XLSX representation of DataBook.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;databook&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_datasets&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;export_set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;databook&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_datasets&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;freeze_panes&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;dset_sheet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;How did you get here?&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This is a simple implementation of the main functions. It lacks some functionalities such a multiple sheets, but it was fine for our needs.&lt;/p&gt;
&lt;p&gt;Next, we need to make &lt;code&gt;tablib&lt;/code&gt; register this file instead of the existing xlsx format. To do that, we created a new file called &lt;code&gt;monkeypatches.py&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# monkeypatches.py&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;tablib&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;.&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;fast_xlsx&lt;/span&gt;

&lt;span class="c1"&gt;# Override default xlsx format with a faster implementation&lt;/span&gt;
&lt;span class="c1"&gt;# using `pyexcelerate` (export only).&lt;/span&gt;
&lt;span class="n"&gt;tablib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;formats&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;available&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fast_xlsx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;To apply the patch to &lt;code&gt;tablib&lt;/code&gt;, we import our implementation and add it to the available formats list. We then import this file in the module's &lt;code&gt;__init__.py&lt;/code&gt; so every time the system starts up, &lt;code&gt;tablib&lt;/code&gt; is patched.&lt;/p&gt;
&lt;p&gt;Now for the moment of truth, &lt;em&gt;did all this hard work finally paid off?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;These were the results of exporting 10,000 rows in xlsx format with &lt;code&gt;pyexcelerate&lt;/code&gt; using manual prefetch and iterator:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;13627507 function calls (13566956 primitive calls) in 10.944 seconds
select 0.137
manual prefetch 2.219
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The hard work definitely paid off! Just so we have an honest comparison, these are the results of exporting 10,000 rows in xlsx format without patching &lt;code&gt;tablib&lt;/code&gt; using manual prefetch and iterator:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;55982358 function calls (46155371 primitive calls) in 29.965 seconds
select 0.137
manual prefetch 1.724
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;That's a 64% improvement compared to the default implementation provided by &lt;code&gt;tablib&lt;/code&gt;, and a 75% improvements compared to the 40s we started with.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="results-summary"&gt;&lt;a class="toclink" href="#results-summary"&gt;Results Summary&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This a summary of all the results mentioned in the article:&lt;/p&gt;
&lt;div class="table-container"&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;th&gt;Rows&lt;/th&gt;
&lt;th&gt;Format&lt;/th&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;39.927s&lt;/td&gt;
&lt;td&gt;10,000&lt;/td&gt;
&lt;td&gt;xlsx&lt;/td&gt;
&lt;td&gt;&lt;code&gt;prefetch_related&lt;/code&gt; (Django)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;17.429s&lt;/td&gt;
&lt;td&gt;10,000&lt;/td&gt;
&lt;td&gt;csv&lt;/td&gt;
&lt;td&gt;&lt;code&gt;prefetch_related&lt;/code&gt; (Django)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20.694s&lt;/td&gt;
&lt;td&gt;10,000&lt;/td&gt;
&lt;td&gt;xls&lt;/td&gt;
&lt;td&gt;&lt;code&gt;prefetch_related&lt;/code&gt; (Django)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;60.962&lt;/td&gt;
&lt;td&gt;1,000&lt;/td&gt;
&lt;td&gt;xls&lt;/td&gt;
&lt;td&gt;subquery&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;62.130&lt;/td&gt;
&lt;td&gt;1,000&lt;/td&gt;
&lt;td&gt;xls&lt;/td&gt;
&lt;td&gt;subquery and iterator&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;57.280s&lt;/td&gt;
&lt;td&gt;1,000&lt;/td&gt;
&lt;td&gt;xls&lt;/td&gt;
&lt;td&gt;simplified query, subquery and iterator&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;29.965s&lt;/td&gt;
&lt;td&gt;10,000&lt;/td&gt;
&lt;td&gt;xlsx&lt;/td&gt;
&lt;td&gt;default &lt;code&gt;tablib&lt;/code&gt; implementation, manual prefetch and iterator&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11.411s&lt;/td&gt;
&lt;td&gt;10,000&lt;/td&gt;
&lt;td&gt;xls&lt;/td&gt;
&lt;td&gt;using manual prefetch and iterator&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10.944s&lt;/td&gt;
&lt;td&gt;10,000&lt;/td&gt;
&lt;td&gt;xlsx&lt;/td&gt;
&lt;td&gt;using &lt;code&gt;pyexcelerate&lt;/code&gt;, manual prefetch and iterator&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;hr&gt;
&lt;h2 id="seifa"&gt;&lt;a class="toclink" href="#seifa"&gt;Seifa&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We try to study every incident and take actions to prevent similar incidents from happening in the future. During this incident, some of our users did experience slowness for a short period of time, however, the "Export to Excel" functionality did not &lt;em&gt;really&lt;/em&gt; killed our app.&lt;/p&gt;
&lt;p&gt;Following this incident, there are a few open questions we haven't had the chance to fully explore yet:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Why was the prefetch query so slow?&lt;/strong&gt; The difference boils down to executing &lt;code&gt;Model.objects.filter(fk__in = [1,2,3,4....9,999, 10,000])&lt;/code&gt; vs executing &lt;code&gt;Model.objects.filter(fk__in = OtherModel.objects.filter( ... ).values_list('pk'))&lt;/code&gt;. When we tried to compare the two in the database, we found no difference, but the built-in &lt;code&gt;prefetch_related&lt;/code&gt; was significantly slower. Is it possible that time is being spent generating the query in Python?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Can &lt;code&gt;openpyxl3&lt;/code&gt; performance be improved?&lt;/strong&gt; When I talked to John, the author of the Excel writers benchmark, he mentioned that &lt;a href="https://openpyxl.readthedocs.io/en/stable/#installation" rel="noopener"&gt;&lt;code&gt;openpyxl3&lt;/code&gt; can be faster if &lt;code&gt;lxml&lt;/code&gt; is installed&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Is xlsx really the best format?&lt;/strong&gt; Can we eliminate some of the problems we had with csv by switching to &lt;a href="https://ebay.github.io/tsv-utils/docs/comparing-tsv-and-csv.html" rel="noopener"&gt;a different textual format such as tsv&lt;/a&gt;?&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you have the answer to any of these questions feel free to share them with me and i'll be happy to post the response.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;UPDATED: Aug 19, 2019&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="comments-from-readers"&gt;&lt;a class="toclink" href="#comments-from-readers"&gt;Comments From Readers&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A &lt;a href="https://lobste.rs/s/ujaizr/how_export_excel_almost_killed_our_system#c_3jvn6v" rel="noopener"&gt;reader from lobste.rs&lt;/a&gt; ran a quick benchmark to check how faster &lt;code&gt;openpyxl&lt;/code&gt; can get using &lt;code&gt;lxml&lt;/code&gt;. These were his results:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;Versions:
python: 3.6.8
Dimensions:
    Cols = 50
    Sheets = 1
    Proportion text = 0.10
optimised = True

Rows = 10000
Times:
openpyxl: 2.6.3 using LXML True:   3.70
openpyxl: 2.6.3 using LXML False:   6.03

Rows = 1000
Times:
openpyxl: 2.6.3 using LXML True:   0.37
openpyxl: 2.6.3 using LXML False:   0.57
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This benchmark shows that &lt;code&gt;openpyxl&lt;/code&gt; can be made almost twice as fast just by installing &lt;code&gt;lxml&lt;/code&gt;. However, &lt;code&gt;pyexcelerate&lt;/code&gt; improved the speed by a factor of 3.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Many reader on &lt;a href="https://www.reddit.com/r/django/comments/d5i9p4/how_export_to_excel_almost_killed_our_system/" rel="noopener"&gt;Reddit&lt;/a&gt; and &lt;a href="https://lobste.rs/s/ujaizr/how_export_excel_almost_killed_our_system" rel="noopener"&gt;Lobsters&lt;/a&gt; suggested that a better approach would be to generate the Excel file on the client side using Javascript. This is definitely something worth considering when designing a new system, even thought I think this approach might be problematic for very large files.&lt;/p&gt;</content><category term="articles"></category><category term="Python"></category><category term="Django"></category><category term="ORM"></category><category term="Performance"></category></entry><entry><title>What You Need to Know to Manage Users in Django Admin</title><link href="https://hakibenita.com/what-you-need-to-know-to-manage-users-in-django-admin" rel="alternate"></link><published>2019-08-05T00:00:00+03:00</published><updated>2019-08-05T00:00:00+03:00</updated><author><name>Haki Benita</name></author><id>tag:hakibenita.com,2019-08-05:/what-you-need-to-know-to-manage-users-in-django-admin</id><summary type="html">&lt;p&gt;Have you ever stopped to think what staff user can do in your Django admin site? Did you know staff users with misconfigured permissions on the user model can make themselves superusers? Permissive permissions to staff users can cause disastrous human errors at best, and lead to major data leaks and at worst.&lt;/p&gt;</summary><content type="html">&lt;hr&gt;
&lt;p&gt;Have you ever stopped to think what your staff users can do in Django admin? Did you know staff users with misconfigured permissions on the user model can make themselves superusers?&lt;/p&gt;
&lt;p&gt;Permissive permissions to staff users can cause disastrous human errors at best, and lead to major data leaks at worst. With the great staff of &lt;a href="https://realpython.com" rel="noopener"&gt;RealPython&lt;/a&gt;, I wrote about ways to protect your Django admin and make it safer for users, and staff users.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://realpython.com/manage-users-in-django-admin/" rel="noopener"&gt;&lt;strong&gt;Read "What You Need to Know to Manage Users in Django Admin" on RealPython ≫&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;figure&gt;&lt;img alt="What You Need to Know to Manage Users in Django Admin" src="https://hakibenita.com/images/01-what-you-need-to-know-to-manage-users-in-django-admin.jpeg"&gt;&lt;figcaption&gt;What You Need to Know to Manage Users in Django Admin&lt;/figcaption&gt;
&lt;/figure&gt;</content><category term="articles"></category><category term="Django"></category><category term="Django Admin"></category></entry><entry><title>Improve Serialization Performance in Django Rest Framework</title><link href="https://hakibenita.com/django-rest-framework-slow" rel="alternate"></link><published>2019-06-08T00:00:00+03:00</published><updated>2019-06-08T00:00:00+03:00</updated><author><name>Haki Benita</name></author><id>tag:hakibenita.com,2019-06-08:/django-rest-framework-slow</id><summary type="html">&lt;p&gt;When a developer chooses Python, Django, or Django Rest Framework, it's usually not because of its blazing fast performance. All of this doesn't mean performance is not important. As this story taught us, major performance boosts can be gained with just a little attention, and a few small changes.&lt;/p&gt;</summary><content type="html">&lt;hr&gt;
&lt;p&gt;When a developer chooses Python, Django, or Django Rest Framework, it's usually not because of its blazing fast performance. Python has always been the "comfortable" choice, the language you choose when you care more about ergonomics than skimming a few microseconds of some process.&lt;/p&gt;
&lt;p&gt;There is nothing wrong with ergonomics. Most projects don't really need that micro second performance boost, but they do need to ship quality code fast.&lt;/p&gt;
&lt;p&gt;All of this doesn't mean performance is not important. As this story taught us, major performance boosts can be gained with just a little attention, and a few small changes.&lt;/p&gt;
&lt;figure&gt;&lt;img alt="&amp;quot;mip mip&amp;quot;" src="https://hakibenita.com/images/01-django-rest-framework-slow.jpg"&gt;&lt;figcaption&gt;"mip mip"&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;details class="toc-container" open&gt;
    &lt;summary&gt;Table of Contents&lt;/summary&gt;&lt;/p&gt;
&lt;div class="toc"&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#model-serializer-performance"&gt;Model Serializer Performance&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#simple-function"&gt;Simple Function&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#modelserializer"&gt;ModelSerializer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#read-only-modelserializer"&gt;Read Only ModelSerializer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#regular-serializer"&gt;"Regular" Serializer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#read-only-regular-serializer"&gt;Read Only "regular" Serializer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#results-summary"&gt;Results Summary&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#why-is-this-happening"&gt;Why is This Happening?&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#prior-work"&gt;Prior Work&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#fixing-djangos-lazy"&gt;Fixing Django's lazy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#fixing-django-rest-framework"&gt;Fixing Django Rest Framework&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#takeaway"&gt;Takeaway&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#bonus-forcing-good-habits"&gt;Bonus: Forcing Good Habits&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;p&gt;&lt;/details&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="model-serializer-performance"&gt;&lt;a class="toclink" href="#model-serializer-performance"&gt;Model Serializer Performance&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A while back we noticed very poor performance from one of our main API endpoints. The endpoint fetched  data from a very large table, so we naturally assumed that the problem must be in the database.&lt;/p&gt;
&lt;p&gt;When we noticed that even small data sets get poor performance, we started looking into other parts of the app. This journey eventually led us to Django Rest Framework (DRF) serializers.&lt;/p&gt;
&lt;div class="admonition info"&gt;
&lt;p class="admonition-title"&gt;versions&lt;/p&gt;
&lt;p&gt;In the benchmark we use Python 3.7, Django 2.1.1 and Django Rest Framework 3.9.4.&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id="simple-function"&gt;&lt;a class="toclink" href="#simple-function"&gt;Simple Function&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Serializers are used for transforming data into objects, and objects into data. This is a simple function, so let's write one that accepts a &lt;code&gt;User&lt;/code&gt; instance, and returns a dict:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.contrib.auth.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;serialize_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;last_login&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;last_login&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isoformat&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;last_login&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;is_superuser&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_superuser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;username&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;first_name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;first_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;last_name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;last_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;email&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;is_staff&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_staff&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;is_active&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_active&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;date_joined&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;date_joined&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isoformat&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Create a user to use in the benchmark:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.contrib.auth.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;    &lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;hakib&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;    &lt;span class="n"&gt;first_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;haki&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;    &lt;span class="n"&gt;last_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;benita&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;    &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;me@hakibenita.com&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;For our benchmark we are using &lt;a href="https://docs.python.org/3.7/library/profile.html" rel="noopener"&gt;&lt;code&gt;cProfile&lt;/code&gt;&lt;/a&gt;. To eliminate external influences such as the database, we fetch a user in advance and serialize it 5,000 times:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;cProfile&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;cProfile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;for i in range(5000): serialize_user(u)&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sort&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;tottime&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="go"&gt;15003 function calls in 0.034 seconds&lt;/span&gt;
&lt;/span&gt;
&lt;span class="go"&gt;Ordered by: internal time&lt;/span&gt;
&lt;span class="go"&gt;ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;/span&gt;
&lt;span class="go"&gt;  5000    0.020    0.000    0.021    0.000 {method &amp;#39;isoformat&amp;#39; of &amp;#39;datetime.datetime&amp;#39; objects}&lt;/span&gt;
&lt;span class="go"&gt;  5000    0.010    0.000    0.030    0.000 drf_test.py:150(serialize_user)&lt;/span&gt;
&lt;span class="go"&gt;     1    0.003    0.003    0.034    0.034 &amp;lt;string&amp;gt;:1(&amp;lt;module&amp;gt;)&lt;/span&gt;
&lt;span class="go"&gt;  5000    0.001    0.000    0.001    0.000 __init__.py:208(utcoffset)&lt;/span&gt;
&lt;span class="go"&gt;     1    0.000    0.000    0.034    0.034 {built-in method builtins.exec}&lt;/span&gt;
&lt;span class="go"&gt;     1    0.000    0.000    0.000    0.000 {method &amp;#39;disable&amp;#39; of &amp;#39;_lsprof.Profiler&amp;#39; objects}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The simple function took 0.034 seconds to serialize a &lt;code&gt;User&lt;/code&gt; object 5,000 times.&lt;/p&gt;
&lt;h3 id="modelserializer"&gt;&lt;a class="toclink" href="#modelserializer"&gt;&lt;code&gt;ModelSerializer&lt;/code&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Django Rest Framework (DRF) comes with a few utility classes, namely the &lt;a href="https://www.django-rest-framework.org/api-guide/serializers/#modelserializer" rel="noopener"&gt;&lt;code&gt;ModelSerializer&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;A &lt;code&gt;ModelSerializer&lt;/code&gt; for the built-in &lt;code&gt;User&lt;/code&gt; model might look like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;rest_framework&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;serializers&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserModelSerializer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;serializers&lt;/span&gt;&lt;span class="o"&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;User&lt;/span&gt;
        &lt;span class="n"&gt;fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;last_login&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;is_superuser&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;username&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;first_name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;last_name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;email&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;is_staff&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;is_active&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;date_joined&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Running the same benchmark as before:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;cProfile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;for i in range(5000): UserModelSerializer(u).data&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sort&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;tottime&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="go"&gt;18845053 function calls (18735053 primitive calls) in 12.818 seconds&lt;/span&gt;
&lt;/span&gt;
&lt;span class="go"&gt;Ordered by: internal time&lt;/span&gt;
&lt;span class="go"&gt;  ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;/span&gt;
&lt;span class="go"&gt;   85000    2.162    0.000    4.706    0.000 functional.py:82(__prepare_class__)&lt;/span&gt;
&lt;span class="go"&gt; 7955000    1.565    0.000    1.565    0.000 {built-in method builtins.hasattr}&lt;/span&gt;
&lt;span class="go"&gt; 1080000    0.701    0.000    0.701    0.000 functional.py:102(__promise__)&lt;/span&gt;
&lt;span class="go"&gt;   50000    0.594    0.000    4.886    0.000 field_mapping.py:66(get_field_kwargs)&lt;/span&gt;
&lt;span class="go"&gt; 1140000    0.563    0.000    0.581    0.000 {built-in method builtins.getattr}&lt;/span&gt;
&lt;span class="go"&gt;   55000    0.489    0.000    0.634    0.000 fields.py:319(__init__)&lt;/span&gt;
&lt;span class="go"&gt; 1240000    0.389    0.000    0.389    0.000 {built-in method builtins.setattr}&lt;/span&gt;
&lt;span class="go"&gt;    5000    0.342    0.000   11.773    0.002 serializers.py:992(get_fields)&lt;/span&gt;
&lt;span class="go"&gt;   20000    0.338    0.000    0.446    0.000 {built-in method builtins.__build_class__}&lt;/span&gt;
&lt;span class="go"&gt;  210000    0.333    0.000    0.792    0.000 trans_real.py:275(gettext)&lt;/span&gt;
&lt;span class="go"&gt;   75000    0.312    0.000    2.285    0.000 functional.py:191(wrapper)&lt;/span&gt;
&lt;span class="go"&gt;   20000    0.248    0.000    4.817    0.000 fields.py:762(__init__)&lt;/span&gt;
&lt;span class="go"&gt; 1300000    0.230    0.000    0.264    0.000 {built-in method builtins.isinstance}&lt;/span&gt;
&lt;span class="go"&gt;   50000    0.224    0.000    5.311    0.000 serializers.py:1197(build_standard_field)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;It took DRF 12.8 seconds to serialize a user 5,000 times, or 2.56ms to serialize just a single user. That is &lt;strong&gt;377 times slower than the plain function&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;We can see that a significant amount of time is spent in &lt;code&gt;functional.py&lt;/code&gt;. &lt;code&gt;ModelSerializer&lt;/code&gt; uses the &lt;code&gt;lazy&lt;/code&gt; function from &lt;code&gt;django.utils.functional&lt;/code&gt; to evaluate validations. It is also used by Django verbose names and so on, which are also being evaluated by DRF. This function seem to be weighing down the serializer.&lt;/p&gt;
&lt;h3 id="read-only-modelserializer"&gt;&lt;a class="toclink" href="#read-only-modelserializer"&gt;Read Only &lt;code&gt;ModelSerializer&lt;/code&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Field validations are added by &lt;code&gt;ModelSerializer&lt;/code&gt; only for writable fields. To measure the effect of validation, we create a &lt;code&gt;ModelSerializer&lt;/code&gt; and mark all fields as read only:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;rest_framework&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;serializers&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserReadOnlyModelSerializer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;serializers&lt;/span&gt;&lt;span class="o"&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;User&lt;/span&gt;
        &lt;span class="n"&gt;fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;last_login&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;is_superuser&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;username&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;first_name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;last_name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;email&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;is_staff&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;is_active&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;date_joined&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="hll"&gt;        &lt;span class="n"&gt;read_only_fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;When all fields are read only, the serializer cannot be used to create new instances.&lt;/p&gt;
&lt;p&gt;Let's run our benchmark with the read only serializer:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;cProfile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;for i in range(5000): UserReadOnlyModelSerializer(u).data&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sort&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;tottime&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="go"&gt;14540060 function calls (14450060 primitive calls) in 7.407 seconds&lt;/span&gt;
&lt;/span&gt;
&lt;span class="go"&gt; Ordered by: internal time&lt;/span&gt;
&lt;span class="go"&gt; ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;/span&gt;
&lt;span class="go"&gt;6090000    0.809    0.000    0.809    0.000 {built-in method builtins.hasattr}&lt;/span&gt;
&lt;span class="go"&gt;  65000    0.725    0.000    1.516    0.000 functional.py:82(__prepare_class__)&lt;/span&gt;
&lt;span class="go"&gt;  50000    0.561    0.000    4.182    0.000 field_mapping.py:66(get_field_kwargs)&lt;/span&gt;
&lt;span class="go"&gt;  55000    0.435    0.000    0.558    0.000 fields.py:319(__init__)&lt;/span&gt;
&lt;span class="go"&gt; 840000    0.330    0.000    0.346    0.000 {built-in method builtins.getattr}&lt;/span&gt;
&lt;span class="go"&gt; 210000    0.294    0.000    0.688    0.000 trans_real.py:275(gettext)&lt;/span&gt;
&lt;span class="go"&gt;   5000    0.282    0.000    6.510    0.001 serializers.py:992(get_fields)&lt;/span&gt;
&lt;span class="go"&gt;  75000    0.220    0.000    1.989    0.000 functional.py:191(wrapper)&lt;/span&gt;
&lt;span class="go"&gt;1305000    0.200    0.000    0.228    0.000 {built-in method builtins.isinstance}&lt;/span&gt;
&lt;span class="go"&gt;  50000    0.182    0.000    4.531    0.000 serializers.py:1197(build_standard_field)&lt;/span&gt;
&lt;span class="go"&gt;  50000    0.145    0.000    0.259    0.000 serializers.py:1310(include_extra_kwargs)&lt;/span&gt;
&lt;span class="go"&gt;  55000    0.133    0.000    0.696    0.000 text.py:14(capfirst)&lt;/span&gt;
&lt;span class="go"&gt;  50000    0.127    0.000    2.377    0.000 field_mapping.py:46(needs_label)&lt;/span&gt;
&lt;span class="go"&gt; 210000    0.119    0.000    0.145    0.000 gettext.py:451(gettext)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Only 7.4 seconds. A 40% improvement compared to the writable &lt;code&gt;ModelSerializer&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;In the benchmark's output we can see a lot of time is being spent in &lt;code&gt;field_mapping.py&lt;/code&gt; and &lt;code&gt;fields.py&lt;/code&gt;. These are related to the inner workings of the &lt;code&gt;ModelSerializer&lt;/code&gt;. In the serialization and initialization process the &lt;code&gt;ModelSerializer&lt;/code&gt; is using a lot of metadata to construct and validate the serializer fields, and it comes at a cost.&lt;/p&gt;
&lt;h3 id="regular-serializer"&gt;&lt;a class="toclink" href="#regular-serializer"&gt;"Regular" &lt;code&gt;Serializer&lt;/code&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;In the next benchmark, we wanted to measure exactly how much the &lt;code&gt;ModelSerializer&lt;/code&gt; "costs" us. Let's create a "regular" &lt;a href="https://www.django-rest-framework.org/api-guide/serializers/#declaring-serializers" rel="noopener"&gt;&lt;code&gt;Serializer&lt;/code&gt;&lt;/a&gt; for the &lt;code&gt;User&lt;/code&gt; model:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;rest_framework&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;serializers&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserSerializer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;serializers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Serializer&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;serializers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IntegerField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;last_login&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;serializers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DateTimeField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;is_superuser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;serializers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BooleanField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;serializers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;first_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;serializers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;last_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;serializers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;serializers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EmailField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;is_staff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;serializers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BooleanField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;is_active&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;serializers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BooleanField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;date_joined&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;serializers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DateTimeField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Running the same benchmark using the "regular" serializer:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;cProfile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;for i in range(5000): UserSerializer(u).data&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sort&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;tottime&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="go"&gt;3110007 function calls (3010007 primitive calls) in 2.101 seconds&lt;/span&gt;
&lt;/span&gt;
&lt;span class="go"&gt;Ordered by: internal time&lt;/span&gt;
&lt;span class="go"&gt;   ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;/span&gt;
&lt;span class="go"&gt;    55000    0.329    0.000    0.430    0.000 fields.py:319(__init__)&lt;/span&gt;
&lt;span class="go"&gt;105000/5000    0.188    0.000    1.247    0.000 copy.py:132(deepcopy)&lt;/span&gt;
&lt;span class="go"&gt;    50000    0.145    0.000    0.863    0.000 fields.py:626(__deepcopy__)&lt;/span&gt;
&lt;span class="go"&gt;    20000    0.093    0.000    0.320    0.000 fields.py:762(__init__)&lt;/span&gt;
&lt;span class="go"&gt;   310000    0.092    0.000    0.092    0.000 {built-in method builtins.getattr}&lt;/span&gt;
&lt;span class="go"&gt;    50000    0.087    0.000    0.125    0.000 fields.py:365(bind)&lt;/span&gt;
&lt;span class="go"&gt;     5000    0.072    0.000    1.934    0.000 serializers.py:508(to_representation)&lt;/span&gt;
&lt;span class="go"&gt;    55000    0.055    0.000    0.066    0.000 fields.py:616(__new__)&lt;/span&gt;
&lt;span class="go"&gt;     5000    0.053    0.000    1.204    0.000 copy.py:268(_reconstruct)&lt;/span&gt;
&lt;span class="go"&gt;   235000    0.052    0.000    0.052    0.000 {method &amp;#39;update&amp;#39; of &amp;#39;dict&amp;#39; objects}&lt;/span&gt;
&lt;span class="go"&gt;    50000    0.048    0.000    0.097    0.000 fields.py:55(is_simple_callable)&lt;/span&gt;
&lt;span class="go"&gt;   260000    0.048    0.000    0.075    0.000 {built-in method builtins.isinstance}&lt;/span&gt;
&lt;span class="go"&gt;    25000    0.047    0.000    0.051    0.000 deconstruct.py:14(__new__)&lt;/span&gt;
&lt;span class="go"&gt;    55000    0.042    0.000    0.057    0.000 copy.py:252(_keep_alive)&lt;/span&gt;
&lt;span class="go"&gt;    50000    0.041    0.000    0.197    0.000 fields.py:89(get_attribute)&lt;/span&gt;
&lt;span class="go"&gt;     5000    0.037    0.000    1.459    0.000 serializers.py:353(fields)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Here is the leap we were waiting for!&lt;/p&gt;
&lt;p&gt;The "regular" serializer took only 2.1 seconds. That's 60% faster than the read only &lt;code&gt;ModelSerializer&lt;/code&gt;, and a whooping 85% faster than the writable &lt;code&gt;ModelSerializer&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;At this point it become obvious that the &lt;code&gt;ModelSerializer&lt;/code&gt; does not come cheap!&lt;/p&gt;
&lt;h3 id="read-only-regular-serializer"&gt;&lt;a class="toclink" href="#read-only-regular-serializer"&gt;Read Only "regular" &lt;code&gt;Serializer&lt;/code&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;In the writable &lt;code&gt;ModelSerializer&lt;/code&gt; a lot of time was spent on validations. We were able to make it faster by marking all fields as read only. The "regular" serializer does not define any validation, so marking fields as read only is not expected to be faster. Let's make sure:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;rest_framework&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;serializers&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserReadOnlySerializer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;serializers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Serializer&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;serializers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IntegerField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;read_only&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;last_login&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;serializers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DateTimeField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;read_only&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;is_superuser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;serializers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BooleanField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;read_only&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;serializers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;read_only&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;first_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;serializers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;read_only&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;last_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;serializers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;read_only&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;serializers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EmailField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;read_only&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;is_staff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;serializers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BooleanField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;read_only&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;is_active&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;serializers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BooleanField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;read_only&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;date_joined&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;serializers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DateTimeField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;read_only&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;And running the benchmark for a user instance:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;cProfile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;for i in range(5000): UserReadOnlySerializer(u).data&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sort&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;tottime&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="go"&gt;3360009 function calls (3210009 primitive calls) in 2.254 seconds&lt;/span&gt;
&lt;/span&gt;
&lt;span class="go"&gt;Ordered by: internal time&lt;/span&gt;
&lt;span class="go"&gt;   ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;/span&gt;
&lt;span class="go"&gt;    55000    0.329    0.000    0.433    0.000 fields.py:319(__init__)&lt;/span&gt;
&lt;span class="go"&gt;155000/5000    0.241    0.000    1.385    0.000 copy.py:132(deepcopy)&lt;/span&gt;
&lt;span class="go"&gt;    50000    0.161    0.000    1.000    0.000 fields.py:626(__deepcopy__)&lt;/span&gt;
&lt;span class="go"&gt;   310000    0.095    0.000    0.095    0.000 {built-in method builtins.getattr}&lt;/span&gt;
&lt;span class="go"&gt;    20000    0.088    0.000    0.319    0.000 fields.py:762(__init__)&lt;/span&gt;
&lt;span class="go"&gt;    50000    0.087    0.000    0.129    0.000 fields.py:365(bind)&lt;/span&gt;
&lt;span class="go"&gt;     5000    0.073    0.000    2.086    0.000 serializers.py:508(to_representation)&lt;/span&gt;
&lt;span class="go"&gt;    55000    0.055    0.000    0.067    0.000 fields.py:616(__new__)&lt;/span&gt;
&lt;span class="go"&gt;     5000    0.054    0.000    1.342    0.000 copy.py:268(_reconstruct)&lt;/span&gt;
&lt;span class="go"&gt;   235000    0.053    0.000    0.053    0.000 {method &amp;#39;update&amp;#39; of &amp;#39;dict&amp;#39; objects}&lt;/span&gt;
&lt;span class="go"&gt;    25000    0.052    0.000    0.057    0.000 deconstruct.py:14(__new__)&lt;/span&gt;
&lt;span class="go"&gt;   260000    0.049    0.000    0.076    0.000 {built-in method builtins.isinstance}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;As expected, marking the fields as readonly didn't make a significant difference compared to the "regular" serializer. This reaffirms that the time was spent on validations derived from the model's field definitions.&lt;/p&gt;
&lt;h3 id="results-summary"&gt;&lt;a class="toclink" href="#results-summary"&gt;Results Summary&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Here is a summary of the results so far:&lt;/p&gt;
&lt;div class="table-container"&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;serializer&lt;/th&gt;
&lt;th&gt;seconds&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;UserModelSerializer&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;12.818&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;UserReadOnlyModelSerializer&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;7.407&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;UserSerializer&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;2.101&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;UserReadOnlySerializer&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;2.254&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;serialize_user&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0.034&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;hr&gt;
&lt;h2 id="why-is-this-happening"&gt;&lt;a class="toclink" href="#why-is-this-happening"&gt;Why is This Happening?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A lot of articles were written about serialization performance in Python. As expected, most articles focus on improving DB access using techniques like &lt;code&gt;select_related&lt;/code&gt; and &lt;code&gt;prefetch_related&lt;/code&gt;. While both are valid ways to improve the &lt;em&gt;overall&lt;/em&gt; response time of an API request, they don't address the serialization itself. I suspect this is because nobody expects serialization to be slow.&lt;/p&gt;
&lt;h3 id="prior-work"&gt;&lt;a class="toclink" href="#prior-work"&gt;Prior Work&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Other articles that do focus solely on serialization usually avoid fixing DRF, and instead motivate &lt;a href="https://engineering.betterworks.com/2015/09/04/ditching-django-rest-framework-serializers-for-serpy/" rel="noopener"&gt;new serialization frameworks&lt;/a&gt; such as &lt;a href="https://marshmallow.readthedocs.io" rel="noopener"&gt;marshmallow&lt;/a&gt; and &lt;a href="https://serpy.readthedocs.io/en/latest/" rel="noopener"&gt;serpy&lt;/a&gt;. There is even a site devoted to &lt;a href="https://voidfiles.github.io/python-serialization-benchmark/" rel="noopener"&gt;comparing serialization formats in Python&lt;/a&gt;. To save you a click, DRF always comes last.&lt;/p&gt;
&lt;p&gt;In late 2013, Tom Christie, the creator of Django Rest Framework, wrote &lt;a href="https://www.dabapps.com/blog/api-performance-profiling-django-rest-framework/" rel="noopener"&gt;an article&lt;/a&gt; discussing some of DRF's drawbacks. In his benchmarks, serialization accounted for 12% of the total time spend on processing a single request. In the summary, Tom recommends to not always resort to serialization:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;4. You don't always need to use serializers.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;For performance critical views you might consider dropping the serializers entirely and simply use .values() in your database queries.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;As we see in a bit, this is solid advice.&lt;/p&gt;
&lt;h3 id="fixing-djangos-lazy"&gt;&lt;a class="toclink" href="#fixing-djangos-lazy"&gt;Fixing Django's &lt;code&gt;lazy&lt;/code&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;In the first benchmark using &lt;code&gt;ModelSerializer&lt;/code&gt; we saw a significant amount of time being spent in &lt;code&gt;functional.py&lt;/code&gt;, and more specifically in the function &lt;code&gt;lazy&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The function &lt;code&gt;lazy&lt;/code&gt; is used internally by Django for many things such as verbose names, templates etc. &lt;a href="https://github.com/django/django/blob/2.2.1/django/utils/functional.py#L92-L207" rel="noopener"&gt;The source&lt;/a&gt; describes &lt;code&gt;lazy&lt;/code&gt; as follows:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Encapsulate a function call and act as a proxy for methods that are called on the result of that function. The function is not evaluated until one of the methods on the result is called.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The &lt;code&gt;lazy&lt;/code&gt; function does its magic by creating a proxy of the result class. To create the proxy, &lt;code&gt;lazy&lt;/code&gt; iterates over all attributes and functions of the result class (and its super-classes), and creates a wrapper class which evaluates the function only when its result is actually used.&lt;/p&gt;
&lt;p&gt;For large result classes, it can take some time to create the proxy. So, to speed things up, &lt;code&gt;lazy&lt;/code&gt; caches the proxy. But as it turns out, &lt;strong&gt;a small oversight in the code completely broke the cache mechanism, making the &lt;code&gt;lazy&lt;/code&gt; function &lt;em&gt;very very&lt;/em&gt; slow.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;To get a sense of just how slow &lt;code&gt;lazy&lt;/code&gt; is without proper caching, let's use a simple function which returns an &lt;code&gt;str&lt;/code&gt; (the result class), such as &lt;code&gt;upper&lt;/code&gt;. We choose &lt;code&gt;str&lt;/code&gt; because it has a lot of methods, so it should take a while to set up a proxy for it.&lt;/p&gt;
&lt;p&gt;To establish a baseline, we benchmark using &lt;code&gt;str.upper&lt;/code&gt; directly, without &lt;code&gt;lazy&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;cProfile&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.utils.functional&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;lazy&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;upper&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;upper&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;cProfile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&amp;#39;for i in range(50000): upper(&amp;#39;hello&amp;#39;) + &amp;quot;&amp;quot;&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sort&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;cumtime&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="hll"&gt;&lt;span class="go"&gt; 50003 function calls in 0.034 seconds&lt;/span&gt;
&lt;/span&gt;
&lt;span class="go"&gt; Ordered by: cumulative time&lt;/span&gt;

&lt;span class="go"&gt; ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;/span&gt;
&lt;span class="go"&gt;     1    0.000    0.000    0.034    0.034 {built-in method builtins.exec}&lt;/span&gt;
&lt;span class="go"&gt;     1    0.024    0.024    0.034    0.034 &amp;lt;string&amp;gt;:1(&amp;lt;module&amp;gt;)&lt;/span&gt;
&lt;span class="go"&gt; 50000    0.011    0.000    0.011    0.000 {method &amp;#39;upper&amp;#39; of &amp;#39;str&amp;#39; objects}&lt;/span&gt;
&lt;span class="go"&gt;     1    0.000    0.000    0.000    0.000 {method &amp;#39;disable&amp;#39; of &amp;#39;_lsprof.Profiler&amp;#39; objects}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Now for the scary part, the exact same function but this time wrapped with &lt;code&gt;lazy&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;lazy_upper&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;lazy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;cProfile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&amp;#39;for i in range(50000): lazy_upper(&amp;#39;hello&amp;#39;) + &amp;quot;&amp;quot;&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sort&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;cumtime&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="hll"&gt;&lt;span class="go"&gt; 4900111 function calls in 1.139 seconds&lt;/span&gt;
&lt;/span&gt;
&lt;span class="go"&gt; Ordered by: cumulative time&lt;/span&gt;

&lt;span class="go"&gt; ncalls  tottime  percall  cumtime  percall filename:lineno(function)&lt;/span&gt;
&lt;span class="go"&gt;      1    0.000    0.000    1.139    1.139 {built-in method builtins.exec}&lt;/span&gt;
&lt;span class="go"&gt;      1    0.037    0.037    1.139    1.139 &amp;lt;string&amp;gt;:1(&amp;lt;module&amp;gt;)&lt;/span&gt;
&lt;span class="go"&gt;  50000    0.018    0.000    1.071    0.000 functional.py:160(__wrapper__)&lt;/span&gt;
&lt;span class="go"&gt;  50000    0.028    0.000    1.053    0.000 functional.py:66(__init__)&lt;/span&gt;
&lt;span class="go"&gt;  50000    0.500    0.000    1.025    0.000 functional.py:83(__prepare_class__)&lt;/span&gt;
&lt;span class="go"&gt;4600000    0.519    0.000    0.519    0.000 {built-in method builtins.hasattr}&lt;/span&gt;
&lt;span class="go"&gt;  50000    0.024    0.000    0.031    0.000 functional.py:106(__wrapper__)&lt;/span&gt;
&lt;span class="go"&gt;  50000    0.006    0.000    0.006    0.000 {method &amp;#39;mro&amp;#39; of &amp;#39;type&amp;#39; objects}&lt;/span&gt;
&lt;span class="go"&gt;  50000    0.006    0.000    0.006    0.000 {built-in method builtins.getattr}&lt;/span&gt;
&lt;span class="go"&gt;     54    0.000    0.000    0.000    0.000 {built-in method builtins.setattr}&lt;/span&gt;
&lt;span class="go"&gt;     54    0.000    0.000    0.000    0.000 functional.py:103(__promise__)&lt;/span&gt;
&lt;span class="go"&gt;      1    0.000    0.000    0.000    0.000 {method &amp;#39;disable&amp;#39; of &amp;#39;_lsprof.Profiler&amp;#39; objects}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;No mistake! Using &lt;code&gt;lazy&lt;/code&gt; it took 1.139 seconds to turn 5,000 strings uppercase. The same exact function used directly took only 0.034 seconds. That is 33.5 faster.&lt;/p&gt;
&lt;p&gt;This was obviously an oversight. The developers were clearly aware of the importance of caching the proxy. A PR was issued, and merged shortly after (diff &lt;a href="https://github.com/django/django/commit/a2c31e12da272acc76f3a3a0157fae9a7f6477ac" rel="noopener"&gt;here&lt;/a&gt;). Once released, this patch is supposed to make Django overall performance a bit better.&lt;/p&gt;
&lt;h3 id="fixing-django-rest-framework"&gt;&lt;a class="toclink" href="#fixing-django-rest-framework"&gt;Fixing Django Rest Framework&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;DRF uses &lt;code&gt;lazy&lt;/code&gt; for validations and fields verbose names. When all of these lazy evaluations are put together, you get a noticeable slowdown.&lt;/p&gt;
&lt;p&gt;The fix to &lt;code&gt;lazy&lt;/code&gt; in Django would have solved this issue for DRF as well after a minor fix, but nonetheless, &lt;a href="https://github.com/encode/django-rest-framework/commit/c2293e9f251b1f215825186a7bcbf5a006df0cb0" rel="noopener"&gt;a separate fix to DRF&lt;/a&gt; was made to replace &lt;code&gt;lazy&lt;/code&gt; with something more efficient.&lt;/p&gt;
&lt;p&gt;To see the effect of the changes, install the latest of both Django and DRF:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp gp-VirtualEnv"&gt;(venv)&lt;/span&gt; &lt;span class="gp"&gt;$ &lt;/span&gt;pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;git+https://github.com/encode/django-rest-framework
&lt;span class="gp gp-VirtualEnv"&gt;(venv)&lt;/span&gt; &lt;span class="gp"&gt;$ &lt;/span&gt;pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;git+https://github.com/django/django
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;After applying both patches, we ran the same benchmark again. These are the results side by side:&lt;/p&gt;
&lt;div class="table-container"&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;serializer&lt;/th&gt;
&lt;th&gt;before&lt;/th&gt;
&lt;th&gt;after&lt;/th&gt;
&lt;th&gt;% change&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;UserModelSerializer&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;12.818&lt;/td&gt;
&lt;td&gt;5.674&lt;/td&gt;
&lt;td&gt;-55%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;UserReadOnlyModelSerializer&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;7.407&lt;/td&gt;
&lt;td&gt;5.323&lt;/td&gt;
&lt;td&gt;-28%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;UserSerializer&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;2.101&lt;/td&gt;
&lt;td&gt;2.146&lt;/td&gt;
&lt;td&gt;+2%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;UserReadOnlySerializer&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;2.254&lt;/td&gt;
&lt;td&gt;2.125&lt;/td&gt;
&lt;td&gt;-5%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;serialize_user&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;0.034&lt;/td&gt;
&lt;td&gt;0.034&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;p&gt;To sum up the results of the changes to both Django and DRF:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Serialization time for writable &lt;code&gt;ModelSerializer&lt;/code&gt; was cut by half.&lt;/li&gt;
&lt;li&gt;Serialization time for a read only &lt;code&gt;ModelSerializer&lt;/code&gt; was cut by almost a third.&lt;/li&gt;
&lt;li&gt;As expected, there is no noticeable difference in the other serialization methods.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="takeaway"&gt;&lt;a class="toclink" href="#takeaway"&gt;Takeaway&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Our takeaways from this experiment were:&lt;/p&gt;
&lt;div class="admonition tip"&gt;
&lt;p class="admonition-title"&gt;Take away&lt;/p&gt;
&lt;p&gt;Upgrade DRF and Django once these patches make their way into a formal release.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Both PR's were merged but not yet released.&lt;/p&gt;
&lt;div class="admonition tip"&gt;
&lt;p class="admonition-title"&gt;Take away&lt;/p&gt;
&lt;p&gt;In performance critical endpoints, use a "regular" serializer, or none at all.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;We had several places where clients were fetching large amounts or data using an API. The API was used only for reading data from the server, so we decided to not use a &lt;code&gt;Serializer&lt;/code&gt; at all, and inline the serialization instead.&lt;/p&gt;
&lt;div class="admonition tip"&gt;
&lt;p class="admonition-title"&gt;Take away&lt;/p&gt;
&lt;p&gt;Serializer fields that are not used for writing or validation, should be read only.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;As we've seen in the benchmarks, the way validations are implemented makes them expensive. Marking fields as read only eliminate unnecessary additional cost.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="bonus-forcing-good-habits"&gt;&lt;a class="toclink" href="#bonus-forcing-good-habits"&gt;Bonus: Forcing Good Habits&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To make sure developers don't forget to set read only fields, we added a Django check to make sure all &lt;code&gt;ModelSerializer&lt;/code&gt;s set &lt;code&gt;read_only_fields&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# common/checks.py&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;django.core.checks&lt;/span&gt;

&lt;span class="nd"&gt;@django&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;checks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;rest_framework.serializers&amp;#39;&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;check_serializers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app_configs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;inspect&lt;/span&gt;
    &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;rest_framework.serializers&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ModelSerializer&lt;/span&gt;
    &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;conf.urls&lt;/span&gt;  &lt;span class="c1"&gt;# noqa, force import of all serializers.&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;serializer&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;ModelSerializer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__subclasses__&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;

        &lt;span class="c1"&gt;# Skip third-party apps.&lt;/span&gt;
        &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;inspect&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;serializer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;site-packages&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;hasattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;serializer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Meta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;read_only_fields&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;

        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;django&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;checks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Warning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;ModelSerializer must define read_only_fields.&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;hint&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Set read_only_fields in ModelSerializer.Meta&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;serializer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;H300&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;With this check in place, when a developer adds a serializer she must also set &lt;code&gt;read_only_fields&lt;/code&gt;. If the serializer is writable, &lt;code&gt;read_only_fields&lt;/code&gt; can be set to an empty tuple. If a developer forgets to set &lt;code&gt;read_only_fields&lt;/code&gt;, she gets the following error:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;python&lt;span class="w"&gt; &lt;/span&gt;manage.py&lt;span class="w"&gt; &lt;/span&gt;check
&lt;span class="go"&gt;System check identified some issues:&lt;/span&gt;

&lt;span class="go"&gt;WARNINGS:&lt;/span&gt;
&lt;span class="go"&gt;&amp;lt;class &amp;#39;serializers.UserSerializer&amp;#39;&amp;gt;: (H300) ModelSerializer must define read_only_fields.&lt;/span&gt;
&lt;span class="go"&gt;    HINT: Set read_only_fields in ModelSerializer.Meta&lt;/span&gt;

&lt;span class="go"&gt;System check identified 1 issue (4 silenced).&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We use Django checks a lot to make sure nothing falls through the cracks. You can find many other useful checks in this article about &lt;a href="/automating-the-boring-stuff-in-django-using-the-check-framework"&gt;how we use the Django system check framework&lt;/a&gt;.&lt;/p&gt;</content><category term="articles"></category><category term="Python"></category><category term="Django"></category><category term="Performance"></category></entry><entry><title>How to Let Google Know of Other Languages in Your Django Site</title><link href="https://hakibenita.com/django-multi-language-site-hreflang" rel="alternate"></link><published>2019-05-07T00:00:00+03:00</published><updated>2019-05-07T00:00:00+03:00</updated><author><name>Haki Benita</name></author><id>tag:hakibenita.com,2019-05-07:/django-multi-language-site-hreflang</id><summary type="html">&lt;p&gt;If you have a public facing Django site in multiple languages, you probably want to let Google and other search engines know about it.&lt;/p&gt;</summary><content type="html">&lt;hr&gt;
&lt;p&gt;If you have a public facing Django site in multiple languages, you probably want to let Google and other search engines know about it.&lt;/p&gt;
&lt;figure&gt;&lt;img alt="Linguistic map of the world (&amp;lt;a href=&amp;quot;https://en.wikipedia.org/wiki/Linguistic_map&amp;quot;&amp;gt;source&amp;lt;/a&amp;gt;)" src="https://hakibenita.com/images/01-django-multi-language-site-hreflang.png"&gt;&lt;figcaption&gt;Linguistic map of the world (&lt;a href="https://en.wikipedia.org/wiki/Linguistic_map"&gt;source&lt;/a&gt;)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="multi-language-django-site"&gt;&lt;a class="toclink" href="#multi-language-django-site"&gt;Multi-Language Django Site&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Django has a &lt;a href="https://docs.djangoproject.com/en/2.2/topics/i18n/" rel="noopener"&gt;very extensive framework&lt;/a&gt; to serve sites in multiple languages.
The least amount of setup necessary to add additional languages to a Django site are these.&lt;/p&gt;
&lt;p&gt;Activate the &lt;a href="https://docs.djangoproject.com/en/2.2/ref/settings/#std:setting-USE_I18N" rel="noopener"&gt;i18n framework&lt;/a&gt; in &lt;code&gt;settings.py&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# settings.py&lt;/span&gt;

&lt;span class="n"&gt;USE_I18N&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Define the &lt;a href="https://docs.djangoproject.com/en/2.2/ref/settings/#languages" rel="noopener"&gt;supported languages&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# settings.py&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.utils.translation&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;gettext_lazy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;

&lt;span class="n"&gt;LANGUAGES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;en&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;English&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;he&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Hebrew&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Set the &lt;a href="https://docs.djangoproject.com/en/2.2/ref/settings/#language-code" rel="noopener"&gt;default language&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# settings.py&lt;/span&gt;

&lt;span class="n"&gt;LANGUAGE_CODE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;en&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Add &lt;code&gt;LocaleMiddleware&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# settings.py&lt;/span&gt;

&lt;span class="n"&gt;MIDDLEWARE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="c1"&gt;# ...&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;django.middleware.locale.LocaleMiddleware&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Use &lt;code&gt;gettext&lt;/code&gt; to mark texts for translation:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# app/views.py&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.utils.translation&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;gettext_lazy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.http&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;HttpResponse&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;about&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;HttpResponse&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;HttpResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Hello!&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Generate the translation files:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;python&lt;span class="w"&gt; &lt;/span&gt;manage.py&lt;span class="w"&gt; &lt;/span&gt;makemessages
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Translate the text:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nv"&gt;msgid&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;Hello!&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;msgstr&lt;/span&gt; &lt;span class="s"&gt;&amp;quot;שלום!&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Compile the translation files:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;python&lt;span class="w"&gt; &lt;/span&gt;manage.py&lt;span class="w"&gt; &lt;/span&gt;compilemessages
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Serve views in multiple languages using &lt;a href="https://docs.djangoproject.com/en/2.2/topics/i18n/translation/#django.conf.urls.i18n.i18n_patterns" rel="noopener"&gt;&lt;code&gt;i18n_patterns&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# urls.py&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.conf.urls.i18n&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;i18n_patterns&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.conf.urls&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;.&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;views&lt;/span&gt;


&lt;span class="n"&gt;urlpatterns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;i18n_patterns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;^about$&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;views&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;about&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;about&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Make sure that it works:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;http://localhost:8000/en/about
&lt;span class="go"&gt;Hello!&lt;/span&gt;

&lt;span class="gp"&gt;$ &lt;/span&gt;curl&lt;span class="w"&gt; &lt;/span&gt;http://localhost:8000/he/about
&lt;span class="go"&gt;שלום!&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This is it!&lt;/p&gt;
&lt;p&gt;There are a few additional steps like &lt;a href="https://docs.djangoproject.com/en/2.2/topics/i18n/translation/#set-language-redirect-view" rel="noopener"&gt;adding a view to switch the language&lt;/a&gt;, but all in all, your multi-language Django site is ready to go!&lt;/p&gt;
&lt;h2 id="link-to-other-languages-using-hreflang"&gt;&lt;a class="toclink" href="#link-to-other-languages-using-hreflang"&gt;Link to Other Languages Using &lt;code&gt;hreflang&lt;/code&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To let search engines know a page is available in a different language, you can use a special link tag:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;link&lt;/span&gt; &lt;span class="na"&gt;rel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;alternate&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;hreflang&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;en&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;https://example.com/en&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The tag has the following attributes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;hreflang&lt;/code&gt;: Language code of the linked page.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;href&lt;/code&gt;: Link to the page in the specified language.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;According to &lt;a href="https://support.google.com/webmasters/answer/189077?hl=en" rel="noopener"&gt;Google's guidelines&lt;/a&gt;, and the &lt;a href="https://en.wikipedia.org/wiki/Hreflang" rel="noopener"&gt;information in Wikipedia&lt;/a&gt;, these are the rules we need to follow:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Use absolute URLs, including the schema.&lt;/li&gt;
&lt;li&gt;Link must be valid, and the linked page should be in the specified language.&lt;/li&gt;
&lt;li&gt;List all languages, including the current one.&lt;/li&gt;
&lt;li&gt;If language X links to language Y, language Y should link back to language X.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;To implement the following in Django, start by listing the available languages in a template, and set the language code in the &lt;code&gt;hreflang&lt;/code&gt; attribute:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;load&lt;/span&gt; &lt;span class="nv"&gt;i18n&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;

&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;get_available_languages&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;LANGUAGES&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;language_code&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;language_name&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nv"&gt;LANGUAGES&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;span class="x"&gt;&amp;lt;link&lt;/span&gt;
&lt;span class="x"&gt;    rel=&amp;quot;alternate&amp;quot;&lt;/span&gt;
&lt;span class="x"&gt;    hreflang=&amp;quot;&lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;language_code&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="x"&gt;&amp;quot;&lt;/span&gt;
&lt;span class="x"&gt;    href=&amp;quot;TODO&amp;quot; /&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;endfor&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The next step is to add localized links for each language. It took some digging, but it turns out Django already has a function called &lt;code&gt;translate_url&lt;/code&gt; we can use:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;urls&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.utils&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;translation&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;translation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;activate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;en&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;reverse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;about&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;&amp;#39;/en/about&amp;#39;&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;translate_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/en/about&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;he&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;&amp;#39;/he/about&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The function &lt;code&gt;translate_url&lt;/code&gt; accepts a URL and a language, and returns the URL in that language. In the example above, we activated the English language and got the a URL prefixed with &lt;code&gt;/en&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The guidelines require absolute URLs.&lt;/p&gt;
&lt;p&gt;Let's make sure &lt;code&gt;translate_url&lt;/code&gt; can handle absolute URLs as well:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;translate_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;https://example.com/en/about&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;he&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;&amp;#39;https://example.com/he/about&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Great! &lt;code&gt;translate_url&lt;/code&gt; can "translate" absolute URLs.&lt;/p&gt;
&lt;p&gt;How about URLs with query parameters or hash?&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;translate_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;https://example.com/en/about?utm_source=search#top&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;he&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;&amp;#39;https://example.com/he/about?utm_source=search#top&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Cool, it worked too!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt;: It doesn't make much sense to have a page URL with query params and hashes in a place like a link tag (or canonical for that matter). The reason I mention it is because it might be useful for deep linking into other pages.&lt;/p&gt;
&lt;p&gt;This is basically all that we need. But, &lt;code&gt;translate_url&lt;/code&gt; has some limitations that are worth knowing.&lt;/p&gt;
&lt;p&gt;Translate a non-localized URL:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;translate_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/about&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;en&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;&amp;#39;/about&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;If you use the &lt;a href="https://docs.djangoproject.com/en/2.2/ref/middleware/#django.middleware.locale.LocaleMiddleware" rel="noopener"&gt;built-in &lt;code&gt;LocaleMiddleware&lt;/code&gt;&lt;/a&gt; and try to navigate to &lt;code&gt;/about&lt;/code&gt;, Django will redirect you to the page in the current language. &lt;code&gt;translate_url&lt;/code&gt; is unable to do the same.&lt;/p&gt;
&lt;div class="admonition info"&gt;
&lt;p class="admonition-title"&gt;good to know&lt;/p&gt;
&lt;p&gt;&lt;code&gt;translate_url&lt;/code&gt; cannot "translate" a non-localized URL (even though it might exist).&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;What about translating a URL already in a language which is not the current language?&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;translation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;activate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;en&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;translate_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/he/about&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;en&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="go"&gt;&amp;#39;/he/about&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Nope, can't do that either.&lt;/p&gt;
&lt;div class="admonition info"&gt;
&lt;p class="admonition-title"&gt;good to know&lt;/p&gt;
&lt;p&gt;&lt;code&gt;translate_url&lt;/code&gt; can only translate localized urls in the current language.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;If you look at the &lt;a href="https://github.com/django/django/blob/master/django/urls/base.py#L163" rel="noopener"&gt;implementation of &lt;code&gt;translate_url&lt;/code&gt;&lt;/a&gt; this restriction becomes clear:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# django/urls/base.py&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;translate_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lang_code&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="sd"&gt;    Given a URL (absolute or relative), try to get its translated version in&lt;/span&gt;
&lt;span class="sd"&gt;    the `lang_code` language (either by i18n_patterns or by translated regex).&lt;/span&gt;
&lt;span class="sd"&gt;    Return the original URL if no translated version is found.&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;parsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;urlsplit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;match&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parsed&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;Resolver404&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;pass&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;namespace&lt;/span&gt;
            &lt;span class="n"&gt;to_be_reversed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;
            &lt;span class="n"&gt;to_be_reversed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url_name&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;override&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lang_code&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;reverse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;to_be_reversed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;NoReverseMatch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;pass&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;urlunsplit&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;parsed&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scheme&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;parsed&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;netloc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;parsed&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;parsed&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fragment&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;url&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Django first tries to &lt;code&gt;resolve&lt;/code&gt; the URL path. This is Django's way of checking if the URL is valid. Only if the URL is valid, it is split into parts, and reversed in the desired language.&lt;/p&gt;
&lt;h3 id="translate_url-template-tag"&gt;&lt;a class="toclink" href="#translate_url-template-tag"&gt;&lt;code&gt;translate_url&lt;/code&gt; template tag&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Now that we know how to "translate" URLs to different languages, we need to be able to use it in a template. Django provides us with a way to define &lt;a href="https://docs.djangoproject.com/en/2.2/howto/custom-template-tags/" rel="noopener"&gt;custom template tags and filters&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Let's add a custom template tag for &lt;code&gt;translate_url&lt;/code&gt; :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# app/templatetags/urls.py&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;urls&lt;/span&gt;


&lt;span class="n"&gt;register&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Library&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="nd"&gt;@register&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;simple_tag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;takes_context&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&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;translate_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;language&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Get the absolute URL of the current page for the specified language.&lt;/span&gt;

&lt;span class="sd"&gt;    Usage:&lt;/span&gt;
&lt;span class="sd"&gt;        {% translate_url &amp;#39;en&amp;#39; %}&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;request&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;build_absolute_uri&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;urls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;translate_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;language&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Our &lt;code&gt;translate_url&lt;/code&gt; template tag takes context. This is necessary if we want to provide an absolute URL. We use &lt;a href="https://docs.djangoproject.com/en/2.2/ref/request-response/#django.http.HttpRequest.build_absolute_uri" rel="noopener"&gt;&lt;code&gt;build_absolute_uri&lt;/code&gt;&lt;/a&gt; to grab the absolute URL from the request.&lt;/p&gt;
&lt;p&gt;The tag also accepts the target language code to translate the URL to, and uses &lt;code&gt;translate_url&lt;/code&gt; to generate the translated URL.&lt;/p&gt;
&lt;p&gt;With our new template tag, we can fill in the blanks in the previous implementation:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;load&lt;/span&gt; &lt;span class="nv"&gt;i18n&lt;/span&gt; &lt;span class="nv"&gt;urls&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;/span&gt;
&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;get_available_languages&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;LANGUAGES&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;language_code&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;language_name&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nv"&gt;LANGUAGES&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;span class="x"&gt;&amp;lt;link&lt;/span&gt;
&lt;span class="x"&gt;    rel=&amp;quot;alternate&amp;quot;&lt;/span&gt;
&lt;span class="x"&gt;    hreflang=&amp;quot;&lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;language_code&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="x"&gt;&amp;quot;&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="x"&gt;    href=&amp;quot;&lt;/span&gt;&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;translate_url&lt;/span&gt; &lt;span class="nv"&gt;language_code&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;&lt;span class="x"&gt;&amp;quot; /&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;endfor&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;h3 id="using-x-default-for-the-default-language"&gt;&lt;a class="toclink" href="#using-x-default-for-the-default-language"&gt;Using &lt;code&gt;x-default&lt;/code&gt; for the default language&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The guidelines include another recommendation:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The reserved value hreflang="x-default" is used when no other language/region matches the user's browser setting. This value is optional, but recommended, as a way for you to control the page when no languages match. A good use is to target your site's homepage where there is a clickable map that enables the user to select their country.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So it's also a good idea to add a link to some default language. If, for example, we want to make our default language English, we can add the following to the snippet above:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;load&lt;/span&gt; &lt;span class="nv"&gt;i18n&lt;/span&gt; &lt;span class="nv"&gt;urls&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;

&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;get_available_languages&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;LANGUAGES&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;language_code&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;language_name&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nv"&gt;LANGUAGES&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;span class="x"&gt;&amp;lt;link&lt;/span&gt;
&lt;span class="x"&gt;    rel=&amp;quot;alternate&amp;quot;&lt;/span&gt;
&lt;span class="x"&gt;    hreflang=&amp;quot;&lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;language_code&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="x"&gt;&amp;quot;&lt;/span&gt;
&lt;span class="x"&gt;    href=&amp;quot;&lt;/span&gt;&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;translate_url&lt;/span&gt; &lt;span class="nv"&gt;language_code&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;&lt;span class="x"&gt;&amp;quot; /&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;endfor&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="x"&gt;&amp;lt;link&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="x"&gt;    rel=&amp;quot;alternate&amp;quot;&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="x"&gt;    hreflang=&amp;quot;x-default&amp;quot;&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="x"&gt;    href=&amp;quot;&lt;/span&gt;&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;translate_url&lt;/span&gt; &lt;span class="nv"&gt;en&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;&lt;span class="x"&gt;&amp;quot; /&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;When we setup our Django project, we already defined a default language. Instead of hard-coding English (or any other language for that matter), we want to use the &lt;code&gt;LANGUAGE_CODE&lt;/code&gt; defined in &lt;code&gt;settings.py&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;To use values from &lt;code&gt;settings.py&lt;/code&gt; in templates, we can use an old trick &lt;a href="/5-ways-to-make-django-admin-safer#visually-distinguish-environments"&gt;we used in the past to visually distinguish between environments in Django admin&lt;/a&gt;. It's a simple &lt;a href="https://docs.djangoproject.com/en/2.2/ref/templates/api/#using-requestcontext" rel="noopener"&gt;context processor&lt;/a&gt; that exposes specific values from &lt;code&gt;settings.py&lt;/code&gt; to templates through the request context:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# app/context_processor.py&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.conf&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;from_settings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;None&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;attr&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;LANGUAGE_CODE&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;To register the context processor, add the following in &lt;code&gt;settings.py&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# settings.py&lt;/span&gt;

&lt;span class="n"&gt;TEMPLATES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
    &lt;span class="c1"&gt;# ...&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;OPTIONS&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;context_processors&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="c1"&gt;#...&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;app.context_processors.from_settings&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="c1"&gt;#...&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;]}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Now that we have access to &lt;code&gt;LANGUAGE_CODE&lt;/code&gt; in the template, we can really complete our snippet:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;load&lt;/span&gt; &lt;span class="nv"&gt;i18n&lt;/span&gt; &lt;span class="nv"&gt;urls&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;

&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;get_available_languages&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;LANGUAGES&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;language_code&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;language_name&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nv"&gt;LANGUAGES&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;span class="x"&gt;&amp;lt;link&lt;/span&gt;
&lt;span class="x"&gt;    rel=&amp;quot;alternate&amp;quot;&lt;/span&gt;
&lt;span class="x"&gt;    hreflang=&amp;quot;&lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;language_code&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="x"&gt;&amp;quot;&lt;/span&gt;
&lt;span class="x"&gt;    href=&amp;quot;&lt;/span&gt;&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;translate_url&lt;/span&gt; &lt;span class="nv"&gt;language_code&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;&lt;span class="x"&gt;&amp;quot; /&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;endfor&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;span class="x"&gt;&amp;lt;link&lt;/span&gt;
&lt;span class="x"&gt;    rel=&amp;quot;alternate&amp;quot;&lt;/span&gt;
&lt;span class="x"&gt;    hreflang=&amp;quot;x-default&amp;quot;&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="x"&gt;    href=&amp;quot;&lt;/span&gt;&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;translate_url&lt;/span&gt; &lt;span class="nv"&gt;LANGUAGE_CODE&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;&lt;span class="x"&gt;&amp;quot; /&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The rendered markup for the about page looks like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;link&lt;/span&gt;
    &lt;span class="na"&gt;rel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;alternate&amp;quot;&lt;/span&gt;
    &lt;span class="na"&gt;hreflang&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;en&amp;quot;&lt;/span&gt;
    &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;https://example.com/en/about&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;link&lt;/span&gt;
    &lt;span class="na"&gt;rel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;alternate&amp;quot;&lt;/span&gt;
    &lt;span class="na"&gt;hreflang&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;he&amp;quot;&lt;/span&gt;
    &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;https://example.com/he/about&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;link&lt;/span&gt;
    &lt;span class="na"&gt;rel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;alternate&amp;quot;&lt;/span&gt;
    &lt;span class="na"&gt;hreflang&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;x-default&amp;quot;&lt;/span&gt;
    &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;https://example.com/en/about&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;h2 id="final-words"&gt;&lt;a class="toclink" href="#final-words"&gt;Final Words&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Hopefully this short article helped you gain a better understanding of how search engines can identify different languages in your Django site. In the process, you might have also picked up on some little tricks to manipulate localized URLs in Django.&lt;/p&gt;</content><category term="articles"></category><category term="Django"></category><category term="i18n"></category><category term="SEO"></category></entry><entry><title>How to Create an Index in Django Without Downtime</title><link href="https://hakibenita.com/how-to-create-django-index-without-downtime" rel="alternate"></link><published>2019-04-10T00:00:00+03:00</published><updated>2019-04-10T00:00:00+03:00</updated><author><name>Haki Benita</name></author><id>tag:hakibenita.com,2019-04-10:/how-to-create-django-index-without-downtime</id><summary type="html">&lt;p&gt;If you ever had to maintain a traffic heavy Django site, you probably had to deal with graceful migrations. In the article I explain what atomic and reversible migrations are, how to execute "raw" SQL in migrations the right way, and how using a little known migration command we can completely alter the Django migrations built-in behavior.&lt;/p&gt;</summary><content type="html">&lt;hr&gt;
&lt;p&gt;If you maintain a Django site with a decent traffic, you probably need to deal with graceful migrations. With the help of the &lt;a href="https://realpython.com" rel="noopener"&gt;RealPython&lt;/a&gt; team, I wrote an article about one the most common problems in Django migrations: &lt;strong&gt;How to create an index without causing downtime&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;In the article I explain what atomic and reversible migrations are, how to execute "raw" SQL in migrations the right way, and how using a little known migration command called &lt;code&gt;SeparateDatabaseAndState&lt;/code&gt;, you can completely alter the Django migrations built-in behavior.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://realpython.com/create-django-index-without-downtime/" rel="noopener"&gt;&lt;strong&gt;Read "How to Create an Index in Django Without Downtime" on RealPython ≫&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;figure&gt;&lt;img alt="How to Create an Index in Django Without Downtime" src="https://hakibenita.com/images/01-how-to-create-django-index-without-downtime.png"&gt;&lt;figcaption&gt;How to Create an Index in Django Without Downtime&lt;/figcaption&gt;
&lt;/figure&gt;</content><category term="articles"></category><category term="Django"></category><category term="ORM"></category><category term="PostgreSQL"></category><category term="SQL"></category></entry><entry><title>How to Use Grouping Sets in Django</title><link href="https://hakibenita.com/how-to-use-grouping-sets-in-django" rel="alternate"></link><published>2019-03-10T00:00:00+02:00</published><updated>2019-03-10T00:00:00+02:00</updated><author><name>Haki Benita</name></author><id>tag:hakibenita.com,2019-03-10:/how-to-use-grouping-sets-in-django</id><summary type="html">&lt;p&gt;How we cut a heavy admin dashboard response time in half with advanced SQL and some Django hackery. I recently had the pleasure of optimizing an old dashboard. The solution we came up with required some advanced SQL that Django does not support out of the box. In this article I present the solution, how we got to it, and a word of caution.&lt;/p&gt;</summary><content type="html">&lt;hr&gt;
&lt;p&gt;I recently had the pleasure of optimizing an &lt;a href="/how-to-turn-django-admin-into-a-lightweight-dashboard"&gt;old dashboard&lt;/a&gt;. The solution we came up with required some advanced SQL that Django does not support out of the box. In this article I present the solution, how we got to it, and a word of caution.&lt;/p&gt;
&lt;div class="admonition info"&gt;
&lt;p class="admonition-title"&gt;advanced SQL&lt;/p&gt;
&lt;p&gt;This article covers advanced topics in SQL aggregation. If you need to perform GROUP BY in Django ORM check out &lt;a href="django-group-by-sql"&gt;Understand Group by in Django with SQL&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;h2 id="the-dashboard"&gt;&lt;a class="toclink" href="#the-dashboard"&gt;The Dashboard&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The dashboard is of a sales model. It includes a simple table with metrics grouped by merchants and their devices, and a summary line.&lt;/p&gt;
&lt;p&gt;The code to produce the table looks roughly like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Sum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Avg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Q&lt;/span&gt;

&lt;span class="n"&gt;metrics&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;total&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;avg_charged_amount&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Avg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;charged_amount&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;unique_users&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;user&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;distinct&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;Sale&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;merchant&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;device&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;annotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The code to produce the summary line uses the same metrics, and looks likes this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;summary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;  &lt;span class="n"&gt;Sale&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;annotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Our admin gets a nice dashboard that looks roughly like this:&lt;/p&gt;
&lt;figure&gt;&lt;img alt="A summary line in Django Admin page" src="https://hakibenita.com/images/01-how-to-use-grouping-sets-in-django.png"&gt;&lt;figcaption&gt;A summary line in Django Admin page&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;div class="admonition info"&gt;
&lt;p class="admonition-title"&gt;see also&lt;/p&gt;
&lt;p&gt;On how to create the dashboard above see
&lt;a href="/how-to-turn-django-admin-into-a-lightweight-dashboard"&gt;How to Turn Django Admin Into a Lightweight Dashboard&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;h2 id="the-problem"&gt;&lt;a class="toclink" href="#the-problem"&gt;The Problem&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The dashboard was working well for about three years. We got good response times and accurate information. However, as data piled up, performance has degraded to a point where the page became unusable.&lt;/p&gt;
&lt;p&gt;To analyze the problem, we inspected the SQL, and timed it. The query to produce the table looks like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;merchant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;charged_amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;avg_charged_amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;unique_users&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;sale&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;merchant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;At worst, this query took about 30s to complete.&lt;/p&gt;
&lt;p&gt;The next query executed by the dashboard was used to produce the summary line:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;charged_amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;avg_charged_amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;unique_users&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;sale&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This query took roughly the same time, ~30s. Together, at their worst, &lt;strong&gt;the two queries took more than a minute to complete.&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id="aggregate-in-memory"&gt;&lt;a class="toclink" href="#aggregate-in-memory"&gt;Aggregate in Memory&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Both queries process the exact same data, the only difference is the &lt;code&gt;GROUP BY&lt;/code&gt; key. The first query produces results at the merchant and device level, the second produces the same aggregates for the entire dataset.&lt;/p&gt;
&lt;p&gt;The first thought that came to mind was to &lt;strong&gt;calculate the summary by aggregating the results in-memory.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The first metric, &lt;code&gt;total&lt;/code&gt;, is easy to calculate:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;summary_total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;total&amp;#39;&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;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The second metric is the average charged amount. We can't just sum the average for each device and merchant, we need additional information.&lt;/p&gt;
&lt;p&gt;To calculate the average charged amount for all merchants and devices, we need to divide the total charged amount by the number of sales. We already have the number of sales, so we need to add a metric for the total charged amount:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;metrics&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;total&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;avg_charged_amount&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Avg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;charged_amount&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="hll"&gt;    &lt;span class="s1"&gt;&amp;#39;total_charged_amount&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;charged_amount&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;    &lt;span class="s1"&gt;&amp;#39;unique_users&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;user&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;distinct&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Now that we have both &lt;code&gt;total&lt;/code&gt; and &lt;code&gt;total_charged_amount&lt;/code&gt;, we can compute &lt;code&gt;avg_charged_amount&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;summary_total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;total&amp;#39;&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;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;summary_total_charged_amount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;total_charged_amount&amp;#39;&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;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="n"&gt;summary_avg_charged_amount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;summary_total_charged_amount&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;summary_total&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We have one metric left, &lt;code&gt;unique_users&lt;/code&gt;. This metric counts the unique number of users that visited each device at each merchant. The same user can visit several devices at different merchants. If we sum &lt;code&gt;unique_users&lt;/code&gt; we won't get the correct metric for the entire set.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It's impossible to accurately compute distinct values from aggregated results, so the solution must be in the database.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="aggregating-in-the-database"&gt;&lt;a class="toclink" href="#aggregating-in-the-database"&gt;Aggregating in the Database&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Most SQL implementations provide several &lt;a href="https://www.postgresql.org/docs/devel/queries-table-expressions.html#QUERIES-GROUPING-SETS" rel="noopener"&gt;useful functions to aggregate data&lt;/a&gt; at different levels.&lt;/p&gt;
&lt;div class="admonition info"&gt;
&lt;p class="admonition-title"&gt;Database Support&lt;/p&gt;
&lt;p&gt;Throughout this article I use PostgreSQL. Similar functions are available in &lt;a href="https://docs.oracle.com/cd/B19306_01/server.102/b14223/aggreg.htm" rel="noopener"&gt;Oracle&lt;/a&gt;, &lt;a href="https://dev.mysql.com/doc/refman/8.0/en/group-by-modifiers.html" rel="noopener"&gt;MySQL&lt;/a&gt; and &lt;a href="https://docs.microsoft.com/en-us/sql/t-sql/queries/select-group-by-transact-sql?view=sql-server-2017" rel="noopener"&gt;MSSQL&lt;/a&gt;. As far as I know, SQLite has no support for the functions I'm about to use.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Let's start with some data:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;TABLE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sale&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt;              &lt;/span&gt;&lt;span class="nb"&gt;integer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;merchant&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;varchar&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="nb"&gt;varchar&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="nb"&gt;integer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;charged_amount&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nb"&gt;integer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;sold_at&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="k"&gt;timestamp&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;INSERT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;INTO&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sale&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;merchant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;charged_amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sold_at&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;VALUES&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Walmart&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;D1&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="mi"&gt;54&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;INTERVAL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;1 days&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Walmart&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;D1&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;INTERVAL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;2 days&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Walmart&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;D2&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;INTERVAL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;1 days&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Walmart&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;D2&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;INTERVAL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;3 days&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Walmart&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;D2&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;INTERVAL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;4 days&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Walmart&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;D3&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="mi"&gt;29&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;INTERVAL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;1 days&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Walmart&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;D3&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="mi"&gt;45&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;INTERVAL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;3 days&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Costco&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;D1&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="mi"&gt;47&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;INTERVAL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;5 days&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Costco&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;D1&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="mi"&gt;223&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;INTERVAL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;1 days&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Costco&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;D1&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="mi"&gt;67&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;INTERVAL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;2 days&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Costco&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;D4&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;INTERVAL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;5 days&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Costco&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;D5&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="mi"&gt;125&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;INTERVAL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;4 days&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Costco&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;D5&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;INTERVAL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;1 days&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The query we used in our dashboard produces the following results:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;db=#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;merchant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;charged_amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;avg_charged_amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;unique_users&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;sale&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;merchant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="go"&gt; merchant | device | total |  avg_charged_amount  | unique_users&lt;/span&gt;
&lt;span class="go"&gt;----------+--------+-------+----------------------+--------------&lt;/span&gt;
&lt;span class="go"&gt; Costco   | D1     |     3 | 112.3333333333333333 |            3&lt;/span&gt;
&lt;span class="go"&gt; Costco   | D4     |     1 |  25.0000000000000000 |            1&lt;/span&gt;
&lt;span class="go"&gt; Costco   | D5     |     2 | 137.5000000000000000 |            2&lt;/span&gt;
&lt;span class="go"&gt; Walmart  | D1     |     2 |  77.0000000000000000 |            1&lt;/span&gt;
&lt;span class="go"&gt; Walmart  | D2     |     3 |  17.0000000000000000 |            3&lt;/span&gt;
&lt;span class="go"&gt; Walmart  | D3     |     2 |  37.0000000000000000 |            1&lt;/span&gt;
&lt;span class="go"&gt;(6 rows)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The query to produce the summary line:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;db=#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;charged_amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;avg_charged_amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;unique_users&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;sale&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="go"&gt; total | avg_charged_amount  | unique_users&lt;/span&gt;
&lt;span class="go"&gt;-------+---------------------+--------------&lt;/span&gt;
&lt;span class="go"&gt;    13 | 70.4615384615384615 |            5&lt;/span&gt;
&lt;span class="go"&gt;(1 row)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;h3 id="rollup"&gt;&lt;a class="toclink" href="#rollup"&gt;Rollup&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The first special &lt;code&gt;GROUP BY&lt;/code&gt; expression is &lt;code&gt;ROLLUP&lt;/code&gt;. As the name suggest, &lt;code&gt;ROLLUP&lt;/code&gt; aggregate at the lowest level and up:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;db=#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;merchant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;sale&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;ROLLUP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;merchant&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;
&lt;span class="go"&gt; device | merchant | count&lt;/span&gt;
&lt;span class="go"&gt;--------+----------+-------&lt;/span&gt;
&lt;span class="go"&gt;        |          |    13&lt;/span&gt;
&lt;span class="go"&gt; D3     | Walmart  |     2&lt;/span&gt;
&lt;span class="go"&gt; D5     | Costco   |     2&lt;/span&gt;
&lt;span class="go"&gt; D1     | Walmart  |     2&lt;/span&gt;
&lt;span class="go"&gt; D4     | Costco   |     1&lt;/span&gt;
&lt;span class="go"&gt; D1     | Costco   |     3&lt;/span&gt;
&lt;span class="go"&gt; D2     | Walmart  |     3&lt;/span&gt;
&lt;span class="go"&gt; D2     |          |     3&lt;/span&gt;
&lt;span class="go"&gt; D4     |          |     1&lt;/span&gt;
&lt;span class="go"&gt; D1     |          |     5&lt;/span&gt;
&lt;span class="go"&gt; D5     |          |     2&lt;/span&gt;
&lt;span class="go"&gt; D3     |          |     2&lt;/span&gt;
&lt;span class="go"&gt;(12 rows)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We grouped by two fields, &lt;code&gt;device&lt;/code&gt; and &lt;code&gt;merchant&lt;/code&gt;, and we got three groups of aggregation:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;()&lt;/code&gt; &lt;em&gt;all&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;(device, merchant)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;(device)&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;code&gt;ROLLUP&lt;/code&gt; aggregates "up", so the order of the fields is significant. Let's flip the order of the fields:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;db=#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;merchant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;sale&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;ROLLUP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;merchant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;
&lt;span class="go"&gt; device | merchant | count&lt;/span&gt;
&lt;span class="go"&gt;--------+----------+-------&lt;/span&gt;
&lt;span class="go"&gt;        |          |    13&lt;/span&gt;
&lt;span class="go"&gt; D4     | Costco   |     1&lt;/span&gt;
&lt;span class="go"&gt; D3     | Walmart  |     2&lt;/span&gt;
&lt;span class="go"&gt; D1     | Walmart  |     2&lt;/span&gt;
&lt;span class="go"&gt; D1     | Costco   |     3&lt;/span&gt;
&lt;span class="go"&gt; D5     | Costco   |     2&lt;/span&gt;
&lt;span class="go"&gt; D2     | Walmart  |     3&lt;/span&gt;
&lt;span class="go"&gt;        | Costco   |     6&lt;/span&gt;
&lt;span class="go"&gt;        | Walmart  |     7&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This time we got the following groups:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;()&lt;/code&gt; &lt;em&gt;all&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;(merchant, device)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;(merchant)&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="cube"&gt;&lt;a class="toclink" href="#cube"&gt;Cube&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The next group by expression is most likely borrowed from &lt;a href="https://en.wikipedia.org/wiki/Online_analytical_processing" rel="noopener"&gt;OLAP&lt;/a&gt;, which often mention cubes. The &lt;code&gt;CUBE&lt;/code&gt; expression aggregates all possible combinations:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;db=#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;merchant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;sale&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;CUBE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;merchant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;
&lt;span class="go"&gt; device | merchant | count&lt;/span&gt;
&lt;span class="go"&gt;--------+----------+-------&lt;/span&gt;
&lt;span class="go"&gt;        |          |    13&lt;/span&gt;
&lt;span class="go"&gt; D4     | Costco   |     1&lt;/span&gt;
&lt;span class="go"&gt; D3     | Walmart  |     2&lt;/span&gt;
&lt;span class="go"&gt; D1     | Walmart  |     2&lt;/span&gt;
&lt;span class="go"&gt; D1     | Costco   |     3&lt;/span&gt;
&lt;span class="go"&gt; D5     | Costco   |     2&lt;/span&gt;
&lt;span class="go"&gt; D2     | Walmart  |     3&lt;/span&gt;
&lt;span class="go"&gt;        | Costco   |     6&lt;/span&gt;
&lt;span class="go"&gt;        | Walmart  |     7&lt;/span&gt;
&lt;span class="go"&gt; D2     |          |     3&lt;/span&gt;
&lt;span class="go"&gt; D4     |          |     1&lt;/span&gt;
&lt;span class="go"&gt; D1     |          |     5&lt;/span&gt;
&lt;span class="go"&gt; D5     |          |     2&lt;/span&gt;
&lt;span class="go"&gt; D3     |          |     2&lt;/span&gt;
&lt;span class="go"&gt;(14 rows)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The results contain the following groups:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;()&lt;/code&gt; &lt;em&gt;all&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;(device, merchant)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;(merchant)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;(device)&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="grouping-sets"&gt;&lt;a class="toclink" href="#grouping-sets"&gt;Grouping Sets&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Grouping sets allows us to provide the exact groups of aggregation we want. For example, to recreate the results of the &lt;code&gt;ROLLUP&lt;/code&gt; above, we can provide the following grouping sets:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;db=#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;merchant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;sale&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;GROUPING&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;SETS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;merchant&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;merchant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;merchant&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;
&lt;span class="c1"&gt;--------+----------+-------&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="mf"&gt;13&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;D4&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Costco&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;D3&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Walmart&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;D1&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Walmart&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;D1&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Costco&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="mf"&gt;3&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;D5&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Costco&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;D2&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Walmart&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="mf"&gt;3&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Costco&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="mf"&gt;6&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Walmart&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="mf"&gt;7&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;9&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Each list of fields inside parentheses in the &lt;code&gt;GROUPING SETS&lt;/code&gt; is a group in the result.&lt;/p&gt;
&lt;p&gt;Both &lt;code&gt;CUBE&lt;/code&gt; and &lt;code&gt;ROLLUP&lt;/code&gt; can be implemented using &lt;code&gt;GROUPING SETS&lt;/code&gt;. The following table shows the equivalent &lt;code&gt;GROUPING SETS&lt;/code&gt; expression for both &lt;code&gt;ROLLUP&lt;/code&gt; and &lt;code&gt;CUBE&lt;/code&gt;, on two fields &lt;code&gt;a&lt;/code&gt; and &lt;code&gt;b&lt;/code&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;expression&lt;/th&gt;
&lt;th&gt;equivalent grouping sets&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ROLLUP(a, b)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;GROUPING SETS ((a, b), (a), ())&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CUBE(a, b)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;GROUPING SETS ((a, b), (a), (b) ())&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;In our original query we had metrics at the merchant and device level, and we wanted to get a summary line. Using &lt;code&gt;GROUPING SETS&lt;/code&gt;, this query will look like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;db=#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;merchant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;charged_amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;avg_charged_amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;unique_users&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;sale&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;GROUPING&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;SETS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;merchant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;merchant&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;avg_charged_amount&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;unique_users&lt;/span&gt;
&lt;span class="c1"&gt;----------+--------+-------+----------------------+--------------&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Costco&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;D1&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="mf"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;112.3333333333333333&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="mf"&gt;3&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Costco&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;D4&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mf"&gt;25.0000000000000000&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Costco&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;D5&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;137.5000000000000000&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Walmart&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;D1&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mf"&gt;77.0000000000000000&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Walmart&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;D2&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="mf"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mf"&gt;17.0000000000000000&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="mf"&gt;3&lt;/span&gt;
&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Walmart&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;D3&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mf"&gt;37.0000000000000000&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="mf"&gt;13&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="mf"&gt;70.4615384615384615&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="mf"&gt;5&lt;/span&gt;
&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;7&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The first 6 lines are similar to the original query. The last line is similar to the results from the summary query we used.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Using &lt;code&gt;GROUPING SETS&lt;/code&gt; we get the results we need in just one query instead of two.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="using-grouping-sets-in-django"&gt;&lt;a class="toclink" href="#using-grouping-sets-in-django"&gt;Using Grouping Sets in Django&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Now that we have the query, we need to find a way to use it with Django. Unfortunately, Django still has no support for grouping sets. On top of that, the query is generated by Django Admin, and it includes predicates from list filters and date hierarchy. We couldn't just use raw SQL.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;We need to find a way to modify a given Django QuerySet, and add grouping sets to it.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Since Django has no built-in support for grouping sets, we are forced to manipulate the query. The base query we need to manipulate is the query that Django generates, along with any predicates and annotations added by Django Admin. Eventually, we want to execute the query in the database the same way Django does.&lt;/p&gt;
&lt;h3 id="getting-the-query"&gt;&lt;a class="toclink" href="#getting-the-query"&gt;Getting the Query&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;A nice feature of &lt;a href="https://docs.djangoproject.com/en/2.1/ref/models/querysets/#queryset-api-reference" rel="noopener"&gt;Django QuerySet&lt;/a&gt; is that it provides the generated SQL:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;qs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="n"&gt;Sale&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;merchant&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;device&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;annotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;qs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;span class="go"&gt;SELECT &amp;quot;sale&amp;quot;.&amp;quot;merchant&amp;quot;, &amp;quot;sale&amp;quot;.&amp;quot;device&amp;quot;, COUNT(&amp;quot;sale&amp;quot;.&amp;quot;id&amp;quot;) AS &amp;quot;total&amp;quot;,&lt;/span&gt;
&lt;span class="go"&gt;AVG(&amp;quot;sale&amp;quot;.&amp;quot;charged_amount&amp;quot;) AS &amp;quot;avg_charged_amount&amp;quot;,&lt;/span&gt;
&lt;span class="go"&gt;COUNT(DISTINCT &amp;quot;sale&amp;quot;.&amp;quot;user_id&amp;quot;) AS &amp;quot;unique_users&amp;quot; FROM &amp;quot;sale&amp;quot;&lt;/span&gt;
&lt;span class="go"&gt;GROUP BY &amp;quot;sale&amp;quot;.&amp;quot;merchant&amp;quot;, &amp;quot;sale&amp;quot;.&amp;quot;device&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This is a simple query, can we execute it &lt;a href="https://docs.djangoproject.com/en/2.1/topics/db/sql/#executing-custom-sql-directly" rel="noopener"&gt;directly in the database&lt;/a&gt;?&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;qs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fetchall&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;
&lt;span class="go"&gt;[(&amp;#39;Costco&amp;#39;, &amp;#39;D1&amp;#39;, 3, 112.3333333333333333, 3),&lt;/span&gt;
&lt;span class="go"&gt;(&amp;#39;Costco&amp;#39;, &amp;#39;D4&amp;#39;, 1, 25.0000000000000000, 1),&lt;/span&gt;
&lt;span class="go"&gt;...&lt;/span&gt;
&lt;span class="go"&gt;(&amp;#39;Walmart&amp;#39;, &amp;#39;D3&amp;#39;, 2, 37.0000000000000000, 1)]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This looks like something we can work with, let's dig deeper...&lt;/p&gt;
&lt;p&gt;As mentioned before, the QuerySet is generated by Django Admin, and it might include predicates for list filters and date hierarchy. Let's try to execute a query with a predicate on the &lt;code&gt;sold_at&lt;/code&gt; date field:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;datetime&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.utils&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;timezone&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;qs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;qs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sold_at__lt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;qs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="go"&gt;*** django.db.utils.ProgrammingError: syntax error at or near &amp;quot;00&amp;quot;&lt;/span&gt;
&lt;span class="go"&gt;LINE 1: ...&amp;quot;sale&amp;quot;.&amp;quot;sold_at&amp;quot; &amp;gt;= 2019-03-02 00:00:00+0...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Looks like Django is unable to execute the query as is. The reason for that is that the text generated by &lt;code&gt;str(qs.query)&lt;/code&gt; is just a text representation of the query. Under the hood, Django uses proper bind variables (might also be known as substitution variables) to avoid SQL injection.&lt;/p&gt;
&lt;p&gt;Much of the Django ORM QuerySet logic is carried out by an internal class called &lt;code&gt;Query&lt;/code&gt;. The class is not documented. The only place to learn about it is in &lt;a href="https://github.com/django/django/blob/2.1/django/db/models/sql/query.py#L133" rel="noopener"&gt;the source&lt;/a&gt;. One promising function of &lt;code&gt;Query&lt;/code&gt; is &lt;code&gt;sql_with_params&lt;/code&gt;. Let's use it on the query above, and see what we get:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;qs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;qs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sold_at__lt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;qs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sql_with_params&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;
&lt;span class="go"&gt;SELECT &amp;quot;sale&amp;quot;.&amp;quot;merchant&amp;quot;, &amp;quot;sale&amp;quot;.&amp;quot;device&amp;quot;, COUNT(&amp;quot;sale&amp;quot;.&amp;quot;id&amp;quot;) AS &amp;quot;total&amp;quot;,&lt;/span&gt;
&lt;span class="go"&gt;AVG(&amp;quot;sale&amp;quot;.&amp;quot;charged_amount&amp;quot;) AS &amp;quot;avg_charged_amount&amp;quot;,&lt;/span&gt;
&lt;span class="go"&gt;COUNT(DISTINCT &amp;quot;sale&amp;quot;.&amp;quot;user_id&amp;quot;) AS &amp;quot;unique_users&amp;quot; FROM &amp;quot;sale&amp;quot;&lt;/span&gt;
&lt;span class="go"&gt;WHERE &amp;quot;sale&amp;quot;.&amp;quot;sold_at&amp;quot; &amp;lt; %s&lt;/span&gt;
&lt;span class="go"&gt;GROUP BY &amp;quot;sale&amp;quot;.&amp;quot;merchant&amp;quot;, &amp;quot;sale&amp;quot;.&amp;quot;device&amp;quot; ORDER BY &amp;quot;sale&amp;quot;.&amp;quot;merchant&amp;quot;, &amp;quot;sale&amp;quot;.&amp;quot;device&amp;quot;&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;
&lt;span class="go"&gt;(datetime.datetime(2019, 3, 2, 0, 0, tzinfo=&amp;lt;UTC&amp;gt;))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The function &lt;code&gt;sql_with_params&lt;/code&gt; returns a tuple. The first argument is the SQL query. The second, is a list of parameters to that query.&lt;/p&gt;
&lt;p&gt;The keen-eyed might have spotted the placeholder &lt;code&gt;%s&lt;/code&gt; in the query text:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;WHERE &amp;quot;sale&amp;quot;.&amp;quot;sold_at&amp;quot; &amp;lt; %s
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This placeholder corresponds to the parameter we got in the second argument. Let's try to execute the query with the placeholder, and the params:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;datetime&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.utils&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;timezone&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;qs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;qs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sold_at__lt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;qs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sql_with_params&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fetchall&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;
&lt;span class="go"&gt;[(&amp;#39;Costco&amp;#39;, &amp;#39;D1&amp;#39;, 3, 112.3333333333333333, 3),&lt;/span&gt;
&lt;span class="go"&gt;(&amp;#39;Costco&amp;#39;, &amp;#39;D4&amp;#39;, 1, 25.0000000000000000, 1),&lt;/span&gt;
&lt;span class="go"&gt;...&lt;/span&gt;
&lt;span class="go"&gt;(&amp;#39;Walmart&amp;#39;, &amp;#39;D3&amp;#39;, 2, 37.0000000000000000, 1)]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Great! We are now able to execute a query as Django does. We are ready to manipulate the query.&lt;/p&gt;
&lt;h3 id="manipulating-the-query"&gt;&lt;a class="toclink" href="#manipulating-the-query"&gt;Manipulating the Query&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The query generated by Django includes a simple GROUP BY clause:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;GROUP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;sale&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;merchant&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;sale&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;device&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ORDER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;sale&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;merchant&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;sale&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;device&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We want to replace that with the following group by clause:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;GROUP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;GROUPING&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;SETS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;sale&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;merchant&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;sale&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;device&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ORDER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;sale&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;merchant&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;sale&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;device&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This looks like a job for &lt;a href="https://docs.python.org/3/library/re.html" rel="noopener"&gt;&lt;code&gt;re&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We want catch the grouped by fields between &lt;code&gt;GROUP BY&lt;/code&gt; and &lt;code&gt;ORDER BY&lt;/code&gt;, and make them the first group in the &lt;code&gt;GROUPING SET&lt;/code&gt; expression. Then, we want add the group &lt;code&gt;()&lt;/code&gt; for the summary:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;re&lt;/span&gt;

&lt;span class="n"&gt;sql_with_grouping_sets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;GROUP BY (.*) ORDER&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;GROUP BY GROUPING SETS (( \1 ), ()) ORDER&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Now we can take the modified query, and execute it with the params:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sql_with_grouping_sets&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fetchall&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;
&lt;span class="go"&gt;[(&amp;#39;Costco&amp;#39;, &amp;#39;D1&amp;#39;, 3, 112.3333333333333333, 3),&lt;/span&gt;
&lt;span class="go"&gt;(&amp;#39;Costco&amp;#39;, &amp;#39;D4&amp;#39;, 1, 25.0000000000000000, 1),&lt;/span&gt;
&lt;span class="go"&gt;...&lt;/span&gt;
&lt;span class="go"&gt;(&amp;#39;Walmart&amp;#39;, &amp;#39;D3&amp;#39;, 2, 37.0000000000000000, 1)]&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="go"&gt;(None, None, 13, 70.4615384615384615, 5)]&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Lo and behold... We got both the results &lt;em&gt;and&lt;/em&gt; the summary line in a single query.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;&lt;a class="toclink" href="#conclusion"&gt;Conclusion&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Several important issues to consider about this approach:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Don't do this!:&lt;/strong&gt; This is as bad as it gets. This approach is a nice exercise, and a great opportunity to explore the ORM internals, but the implementation is too fragile. When using an internal, undocumented API, there is no guaranty it wont change unexpectedly in the future. Having said that, we decided to use this approach in one of our internal admin pages. It's a very specific scenario involving a queryset that's not used for any user facing features. It helped us cut the page response time exactly in half and we are pleased with the result.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Make the sort order deterministic:&lt;/strong&gt; When using &lt;code&gt;GROUPING SETS&lt;/code&gt; (and &lt;code&gt;ROLLUP&lt;/code&gt; or &lt;code&gt;CUBE&lt;/code&gt; for that matter), you mix more than one level of aggregation in a single query. To be able to fetch the results in a predictable way, it's important to explicitly sort the results. For example, in the query above, to make sure the summary row is the first row, add the following sort order &lt;code&gt;qs.order_by( F('merchant').desc(nulls_last=False) )&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</content><category term="articles"></category><category term="Django"></category><category term="ORM"></category><category term="SQL"></category><category term="Performance"></category></entry><entry><title>Modeling Polymorphism in Django</title><link href="https://hakibenita.com/modeling-polymorphism-in-django" rel="alternate"></link><published>2019-01-02T00:00:00+02:00</published><updated>2019-01-02T00:00:00+02:00</updated><author><name>Haki Benita</name></author><id>tag:hakibenita.com,2019-01-02:/modeling-polymorphism-in-django</id><summary type="html">&lt;p&gt;Modeling polymorphism in relational databases is a challenging task. In this article, we present several modeling techniques to represent polymorphic objects in a relational database using the Django object-relational mapping (ORM).&lt;/p&gt;</summary><content type="html">&lt;hr&gt;
&lt;p&gt;If you ever added a &lt;code&gt;type&lt;/code&gt;, &lt;code&gt;kind&lt;/code&gt; or a &lt;code&gt;mode&lt;/code&gt; field to a Django model, you probably had to deal with polymorphism at some level. With the great people over at &lt;a href="https://realpython.com" rel="noopener"&gt;RealPython&lt;/a&gt;, I wrote about 5 ways to model polymorphism in Django.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://realpython.com/modeling-polymorphism-django-python/" rel="noopener"&gt;&lt;strong&gt;Read "Modeling Polymorphism in Django With Python" on RealPython ≫&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;figure&gt;&lt;img alt="Modeling Polymorphism in Django With Python" src="https://hakibenita.com/images/01-modeling-polymorphism-in-django-with-python.jpeg"&gt;&lt;figcaption&gt;Modeling Polymorphism in Django With Python&lt;/figcaption&gt;
&lt;/figure&gt;</content><category term="articles"></category><category term="Django"></category><category term="ORM"></category></entry><entry><title>Optimizing the Django Admin Paginator</title><link href="https://hakibenita.com/optimizing-the-django-admin-paginator" rel="alternate"></link><published>2018-11-06T00:00:00+02:00</published><updated>2018-11-06T00:00:00+02:00</updated><author><name>Haki Benita</name></author><id>tag:hakibenita.com,2018-11-06:/optimizing-the-django-admin-paginator</id><summary type="html">&lt;p&gt;I often talk about making Django scale but what does it actually mean? It means getting consistent performance regardless of the amount of data. In this article we tackle The last nail in Django admin's scalability coffin - the paginator.&lt;/p&gt;</summary><content type="html">&lt;hr&gt;
&lt;p&gt;In almost every project we work on, we use Django admin for support and operations. Over time we experienced an influx of new users and the amount of data we had stored grew rapidly. With a large dataset we started to experience the real cost of some Django admin features.&lt;/p&gt;
&lt;p&gt;I often talk about making Django scale but what does it actually mean? It means getting consistent performance regardless of the amount of data. Over the past few years I've written about different approaches to scale and optimize Django admin for large tables:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="/things-you-must-know-about-django-admin-as-your-app-gets-bigger"&gt;Things You Must Know About Django admin As Your App Gets Bigger&lt;/a&gt;: Included an overview of built-in options to optimize for larger tables.&lt;/li&gt;
&lt;li&gt;&lt;a href="/how-to-add-custom-action-buttons-to-django-admin"&gt;How to Add Custom Action Buttons to Django Admin&lt;/a&gt;: Explained how to make Django admin more suitable for operations.&lt;/li&gt;
&lt;li&gt;&lt;a href="/how-to-turn-django-admin-into-a-lightweight-dashboard"&gt;How to turn Django Admin into a lightweight dashboard&lt;/a&gt;: Showed how to add custom pages to the admin with fancy graphs and chart. This was our first dashboard (and we still use it!).&lt;/li&gt;
&lt;li&gt;&lt;a href="/5-ways-to-make-django-admin-safer"&gt;5 ways to make Django Admin safer&lt;/a&gt;: Described different measures we took to make our admin more secure (this one also inspired &lt;a href="https://github.com/dizballanze/django-admin-env-notice" rel="noopener"&gt;a package&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;&lt;a href="/scaling-django-admin-date-hierarchy"&gt;Scaling Django Admin Date Hierarchy&lt;/a&gt;: Identified issues with the date hierarchy and suggested ways to alter the admin behavior to better handle large tables (we also turned it into &lt;a href="https://github.com/hakib/django-admin-lightweight-date-hierarchy" rel="noopener"&gt;a package&lt;/a&gt;!).&lt;/li&gt;
&lt;li&gt;&lt;a href="/django-admin-range-based-date-hierarchy"&gt;Django Admin Range-Based Date Hierarchy &lt;/a&gt;: Looked at the queries generated by the date hierarchy and made them much faster. I'm especially proud of this one because &lt;strong&gt;I managed to &lt;a href="https://github.com/django/django/pull/9469" rel="noopener"&gt;get it into Django 2.1&lt;/a&gt;&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="/how-to-add-a-text-filter-to-django-admin"&gt;How to add a text filter to Django Admin&lt;/a&gt;: We added a new type of filter to save some time when generating list filters.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;After all of this, only one problem remained...&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="the-paginator"&gt;&lt;a class="toclink" href="#the-paginator"&gt;The Paginator&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The last nail in Django admin's scalability coffin is the paginator:&lt;/p&gt;
&lt;figure&gt;&lt;img alt="Count is taking so much time!" src="https://hakibenita.com/images/01-optimizing-django-admin-paginator.png"&gt;&lt;figcaption&gt;Count is taking so much time!&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Django spent 781ms out of 786ms just to count the rows in the table. That's ~99.4% of the time, just for the paginator!&lt;/p&gt;
&lt;p&gt;For reference, the actual query used to fetch the data took only 2.10ms. The reason it's much quicker is that it only needs to fetch one page (notice the &lt;code&gt;LIMIT 100&lt;/code&gt;).&lt;/p&gt;
&lt;h3 id="what-can-we-do"&gt;&lt;a class="toclink" href="#what-can-we-do"&gt;What Can We Do?&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Making something scale is all about eliminating operations that work on the entire dataset. The paginator has to count the rows to determine how many pages there are. This forces every single page in the admin to scan the entire table.&lt;/p&gt;
&lt;p&gt;As the table grows in size this query takes longer and longer to execute. The size of the table has a direct impact on the load time of a single page and this is exactly what we want to avoid.&lt;/p&gt;
&lt;p&gt;Django &lt;code&gt;ModelAdmin&lt;/code&gt; &lt;a href="https://docs.djangoproject.com/en/2.1/ref/contrib/admin/#django.contrib.admin.ModelAdmin.paginator" rel="noopener"&gt;provides a way to override the paginator&lt;/a&gt;.
Our initial thought was to implement an entirely different type of paginator - one that doesn't calculate the number of pages, but only shows links to the previous and next pages.&lt;/p&gt;
&lt;p&gt;Unfortunately, the paginator is embedded deep into Django admin - Django uses partial templates to render the paginator. This makes it difficult to create a paginator that doesn't count pages.&lt;/p&gt;
&lt;p&gt;To provide a different paginator to a &lt;code&gt;ModelAdmin&lt;/code&gt;we need to implement a &lt;code&gt;Paginator&lt;/code&gt;. The interesting function in the paginator implementation is &lt;code&gt;count&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# django/core/paginator.py&lt;/span&gt;

&lt;span class="nd"&gt;@cached_property&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="hll"&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;object_list&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ne"&gt;AttributeError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ne"&gt;TypeError&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="hll"&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;object_list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This is where the horror happens. Django counts the rows in the queryset (or the object list) and caches the result.&lt;/p&gt;
&lt;p&gt;If we want to eliminate the count this is where we need to start.&lt;/p&gt;
&lt;h3 id="a-dumb-paginator"&gt;&lt;a class="toclink" href="#a-dumb-paginator"&gt;A Dumb Paginator&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Let's take the simplest approach and create a paginator that returns a very
large number without actually counting the rows:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# common/paginator.py&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.core.paginator&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Paginator&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DumbPaginator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Paginator&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="sd"&gt;    Paginator that does not count the rows in the table.&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="nd"&gt;@cached_property&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;9999999999&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Let's add it to a &lt;code&gt;ModelAdmin&lt;/code&gt; of a large table and see what happens:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# app/admin.py&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;common.paginator&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;DumbPaginator&lt;/span&gt;

&lt;span class="nd"&gt;@admin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LargeTable&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;LargeTableAdmin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ModelAdmin&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="hll"&gt;    &lt;span class="n"&gt;paginator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DumbPaginator&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;And the admin page:&lt;/p&gt;
&lt;figure&gt;&lt;img alt="Admin page did not count rows" src="https://hakibenita.com/images/02-optimizing-django-admin-paginator.png"&gt;&lt;figcaption&gt;Admin page did not count rows&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Wow! With the dumb paginator the page now loads in only 4ms. This is impressive but how does the UI looks like?&lt;/p&gt;
&lt;figure&gt;&lt;img alt="Pagination UI" src="https://hakibenita.com/images/03-optimizing-django-admin-paginator.png"&gt;&lt;figcaption&gt;Pagination UI&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;We made the paginator think there are 9999999999 results so this is what it shows. Clicking on a page that doesn't exist will open an empty list view.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="getting-creative"&gt;&lt;a class="toclink" href="#getting-creative"&gt;Getting Creative&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We saw that we can provide a custom pagination and eliminate the count. We also know that Django is very attached to this specific paginator template so we can't easily replace it.&lt;/p&gt;
&lt;p&gt;At this point it's obvious that if we don't want to work &lt;em&gt;too&lt;/em&gt; hard and alter Django's templates, we need to make a compromise. A compromise can be made either in the UI or in accuracy.&lt;/p&gt;
&lt;p&gt;I personally don't like to sacrifice accuracy so fast so I tend to make compromises in the UI. Especially in internal projects such as ones implemented with Django admin.&lt;/p&gt;
&lt;p&gt;Instead of always eliminating the pagination, what if we just limit the execution time of the count query:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# common/paginator.py&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.core.paginator&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Paginator&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;OperationalError&lt;/span&gt;

&lt;span class="hll"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TimeLimitedPaginator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Paginator&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="sd"&gt;    Paginator that enforces a timeout on the count operation.&lt;/span&gt;
&lt;span class="sd"&gt;    If the operations times out, a fake bogus value is&lt;/span&gt;
&lt;span class="sd"&gt;    returned instead.&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="nd"&gt;@cached_property&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# We set the timeout in a db transaction to prevent it from&lt;/span&gt;
        &lt;span class="c1"&gt;# affecting other transactions.&lt;/span&gt;
&lt;span class="hll"&gt;        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;atomic&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;            &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;SET LOCAL statement_timeout TO 200;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;            &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="hll"&gt;                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;
&lt;/span&gt;            &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;OperationalError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="hll"&gt;                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;9999999999&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We let the original paginator count the rows. If the count takes longer than 200ms, the database will kill the query, an &lt;code&gt;OperationError&lt;/code&gt; will be raised and we return the fake value. If the count takes less than 200ms it will work as usual.&lt;/p&gt;
&lt;p&gt;To check the new approach we set the paginator in the &lt;code&gt;ModelAdmin&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# app/admin.py&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;common.paginator&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TimeLimitedPaginator&lt;/span&gt;

&lt;span class="nd"&gt;@admin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LargeTable&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;LargeTableAdmin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ModelAdmin&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="hll"&gt;    &lt;span class="n"&gt;paginator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TimeLimitedPaginator&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;First let's try it with a big result set. For example, all the rows from year 2018:&lt;/p&gt;
&lt;figure&gt;&lt;img alt="Query took longer than 200ms and was killed" src="https://hakibenita.com/images/04-optimizing-django-admin-paginator.png"&gt;&lt;figcaption&gt;Query took longer than 200ms and was killed&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Query took longer than 200ms and was killed.&lt;/p&gt;
&lt;p&gt;Let's try a shorter period. For example, only records from a single day in 2018:&lt;/p&gt;
&lt;figure&gt;&lt;img alt="Query finished in under 200ms and was not killed." src="https://hakibenita.com/images/05-optimizing-django-admin-paginator.png"&gt;&lt;figcaption&gt;Query finished in under 200ms and was not killed.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Count took only 2.41ms. It was not killed and we got the regular "paginator" - exactly what we wanted!&lt;/p&gt;
&lt;div class="admonition info"&gt;
&lt;p class="admonition-title"&gt;source code&lt;/p&gt;
&lt;p&gt;The complete code for &lt;code&gt;TimeLimitedPaginator&lt;/code&gt; can be found in &lt;a href="https://gist.github.com/hakib/5cbda96c8121299088115a94ec634903" rel="noopener"&gt;this gist&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;hr&gt;
&lt;h2 id="other-approaches"&gt;&lt;a class="toclink" href="#other-approaches"&gt;Other Approaches&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;As discussed in the previous section, compromises can be made in different ways.
Some ideas I encountered when I researched this issue were:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Estimate the number of rows based on the database execution plan&lt;/strong&gt; - in Django 2.1 there is even a new &lt;a href="https://docs.djangoproject.com/en/2.1/ref/models/querysets/#explain" rel="noopener"&gt;explain function&lt;/a&gt; on &lt;code&gt;QuerySet&lt;/code&gt;. This approach is very inaccurate, especially with complicated predicates, and might yield unexpected results.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cache the number of rows&lt;/strong&gt; - the boring and unimaginative solution. As always with a solution that involves caching, you need to decide when to invalidate the cache which is a problem of it's own.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Completely replace Django's paginator&lt;/strong&gt; - replacing templates and possibly making adjustments to the change list itself. &lt;br&gt; I went down this road
initially but gave up when I realized it will be difficult to make this "plug and play". I do believe that a proper solution should let users replace the pagination implementation to something like &lt;a href="https://www.django-rest-framework.org/api-guide/pagination/#cursorpagination" rel="noopener"&gt;cursor pagination&lt;/a&gt; (pagination that does not need to evaluate the entire dataset).&lt;/li&gt;
&lt;/ol&gt;</content><category term="articles"></category><category term="Django"></category><category term="Django Admin"></category><category term="Performance"></category></entry><entry><title>Automating the Boring Stuff in Django Using the Check Framework</title><link href="https://hakibenita.com/automating-the-boring-stuff-in-django-using-the-check-framework" rel="alternate"></link><published>2018-06-05T00:00:00+03:00</published><updated>2018-06-05T00:00:00+03:00</updated><author><name>Haki Benita</name></author><id>tag:hakibenita.com,2018-06-05:/automating-the-boring-stuff-in-django-using-the-check-framework</id><summary type="html">&lt;p&gt;Every team has a unique development style. Some teams implement localization and require translations. Some teams are more sensitive to database issues and require more careful handling of indexes and constraints. In this article we describe how we enforce our own development style using the Django check framework, the inspect and the ast modules from the Python standard library.&lt;/p&gt;</summary><content type="html">&lt;hr&gt;
&lt;p&gt;Every team has a unique development style. Some teams implement localization and require translations. Some teams are more sensitive to database issues and require more careful handling of indexes and constraints.&lt;/p&gt;
&lt;p&gt;Existing tools can not always address these specific issues out of the box, so we came up with a way to &lt;strong&gt;enforce our own development style using the Django check framework, the inspect and the ast modules from the Python standard library.&lt;/strong&gt;&lt;/p&gt;
&lt;div class="dark--invert"&gt;
&lt;figure&gt;&lt;img alt="Image by &amp;lt;a href=&amp;quot;https://www.instagram.com/_wrightdesign/&amp;quot;&amp;gt;Wright Design&amp;lt;/a&amp;gt;" src="https://hakibenita.com/images/all_nighter.png"&gt;&lt;figcaption&gt;Image by &lt;a href="https://www.instagram.com/_wrightdesign/"&gt;Wright Design&lt;/a&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/div&gt;
&lt;p&gt;&lt;details class="toc-container" open&gt;
    &lt;summary&gt;Table of Contents&lt;/summary&gt;&lt;/p&gt;
&lt;div class="toc"&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#django-checks"&gt;Django checks&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="#our-first-check"&gt;Our first check&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#inspecting-the-code"&gt;Inspecting the code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#parsing-the-code"&gt;Parsing the code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#evaluating-a-model-field"&gt;Evaluating a Model Field&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#issuing-django-checks"&gt;Issuing Django checks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#putting-it-all-together"&gt;Putting it all together&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#custom-checks-in-the-real-world"&gt;Custom Checks in the Real World&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;p&gt;&lt;/details&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="django-checks"&gt;&lt;a class="toclink" href="#django-checks"&gt;Django checks&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Django checks are part of the &lt;a href="https://docs.djangoproject.com/en/2.0/ref/checks/#system-check-framework" rel="noopener"&gt;Django System Check
framework&lt;/a&gt;.
To quote the docs:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The system check framework is a set of static checks for validating Django
projects. It detects common problems and provides hints for how to fix them. The framework is extensible so you can easily add your own checks.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;One check you might be familiar with is this one from Django admin:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="go"&gt;SystemCheckError: System check identified some issues:&lt;/span&gt;

&lt;span class="go"&gt;ERRORS:&lt;/span&gt;
&lt;span class="go"&gt;&amp;lt;class &amp;#39;app.admin.BarAdmin&amp;gt;&lt;/span&gt;
&lt;span class="go"&gt;(admin.E108) The value of &amp;#39;list_display[3]&amp;#39; refers to &amp;#39;foo&amp;#39;,&lt;/span&gt;
&lt;span class="go"&gt;which is not a callable, an attribute of &amp;#39;Bar&amp;#39;, or an attribute&lt;/span&gt;
&lt;span class="go"&gt;or method on &amp;#39;app.Bar&amp;#39;.&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The Django admin developers added a system check to warn developers about fields in the model admin that does not exist in the actual model. In this case the field 'foo' do not exist in model Bar.&lt;/p&gt;
&lt;p&gt;Checks are executed by some management commands such as &lt;code&gt;makemigrations&lt;/code&gt; and &lt;code&gt;migrate&lt;/code&gt;. It's also possible to explicitly run check using &lt;code&gt;manage.py&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;./manage.py&lt;span class="w"&gt; &lt;/span&gt;check
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;It's a good idea to incorporate &lt;code&gt;check&lt;/code&gt; in your CI. If you want to fail the CI on warnings you can do that by setting a flag:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;./manage.py&lt;span class="w"&gt; &lt;/span&gt;check&lt;span class="w"&gt; &lt;/span&gt;--fail-level&lt;span class="o"&gt;=&lt;/span&gt;WARNING
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;A simple example of how Django uses checks can be found in the source code of the &lt;a href="https://github.com/django/django/blob/c03e41712b2274f524d32bc2aef455ed82c9e3b4/django/db/models/fields/__init__.py#L211" rel="noopener"&gt;model Field checks&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="our-first-check"&gt;&lt;a class="toclink" href="#our-first-check"&gt;Our first check&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Most of our apps are not designated for English speakers so we use translations extensively. We put a lot of focus during code review to make sure everything is translated properly.&lt;/p&gt;
&lt;p&gt;One of the main issues that come up during code reviews is that &lt;strong&gt;developers often forget to set verbose_name on model fields.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Checking that a field has a verbose name is a pretty straightforward task and we wanted to automate the process of making sure it was set.&lt;/p&gt;
&lt;p&gt;To get us started we are going to define a simple customer profile model:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CustomerProfile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PositiveSmallIntegerField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;primary_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;verbose_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&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;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;created_by&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;on_delete&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PROTECT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The "name" field does not have &lt;code&gt;verbose_name&lt;/code&gt;. Let's see if we can identify that using only the model's &lt;code&gt;_meta&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;name_field&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CustomerProfile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_meta&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;name_field&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;verbose_name&lt;/span&gt;
&lt;span class="go"&gt;name&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;It looks like Django did something under to hood to set the &lt;code&gt;verbose_name&lt;/code&gt;. Looking at the Field class, there is a function called &lt;a href="https://github.com/django/django/blob/c03e41712b2274f524d32bc2aef455ed82c9e3b4/django/db/models/fields/__init__.py#L724" rel="noopener"&gt;set_attributes_from_name&lt;/a&gt; that populates &lt;code&gt;verbose_name&lt;/code&gt; by transforming the name of the field - this is where the &lt;code&gt;verbose_name&lt;/code&gt; "name" came from.&lt;/p&gt;
&lt;p&gt;Because Django is setting the &lt;code&gt;verbose_name&lt;/code&gt; on its own the string "name" &lt;strong&gt;will not be picked up by &lt;/strong&gt;&lt;code&gt;makemessages&lt;/code&gt;&lt;strong&gt; and will not be added to the po file automatically. &lt;/strong&gt;This will probably cause the string "name" to go unnoticed. We don't want that.&lt;/p&gt;
&lt;p&gt;Also, because Django is populating the field automatically &lt;strong&gt;we can't use the model _meta to check if &lt;/strong&gt;&lt;code&gt;verbose_name&lt;/code&gt;&lt;strong&gt; was originally set&lt;/strong&gt;. To do that we need to inspect the actual source code.&lt;/p&gt;
&lt;h3 id="inspecting-the-code"&gt;&lt;a class="toclink" href="#inspecting-the-code"&gt;Inspecting the code&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I didn't use the word &lt;em&gt;inspect&lt;/em&gt; for no reason - Python has a module called &lt;a href="https://docs.python.org/3/library/inspect.html" rel="noopener"&gt;inspect&lt;/a&gt; that we can use to, well, inspect code:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The inspect module provides several useful functions to help get information
about live objects such as modules, classes, methods, functions, tracebacks, frame objects, and code objects. For example, it can help you examine the contents of a class, retrieve the source code of a method, extract and format the argument list for a function, or get all the information you need to display a detailed traceback.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Let's see what we can get from inspect:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;inspect&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;inspect&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getsource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CustomerProfile&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="go"&gt;&amp;quot;class CustomerProfile(models.Model):\n id = models.PositiveSmallIntegerField(\n&lt;/span&gt;
&lt;span class="go"&gt;primary_key=True,\n verbose_name=_(&amp;#39;Name&amp;#39;),\n )\n name = models.CharField(\n&lt;/span&gt;
&lt;span class="go"&gt;max_length=100,\n )\n created_by = models.ForeignKey(\n User,\n on_delete=models.PROTECT,\n&lt;/span&gt;
&lt;span class="go"&gt;)\n\n def __str__(self):\n return self.name\n&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;That's pretty exciting. We gave inspect the class and got the source code for that class as text.&lt;/p&gt;
&lt;p&gt;Given the source code we could have used some fancy RegExp to parse the code but once again, Python already has us covered.&lt;/p&gt;
&lt;h3 id="parsing-the-code"&gt;&lt;a class="toclink" href="#parsing-the-code"&gt;Parsing the code&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Parsing code in Python is done by the &lt;a href="https://docs.python.org/3/library/ast.html" rel="noopener"&gt;ast
module&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;The ast module helps Python applications to process trees of the Python
abstract syntax grammar.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Great! A tree is much easier to work with than text.&lt;/p&gt;
&lt;p&gt;Let's use ast to parse the source code of our model:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;inspect&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;ast&lt;/span&gt;
&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;model_source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;inspect&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getsource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CustomerProfile&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;model_node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ast&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model_source&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;ast&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dump&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model_node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="go"&gt;Module([&lt;/span&gt;
&lt;span class="go"&gt;    ClassDef(&amp;#39;CustomerProfile&amp;#39;,&lt;/span&gt;
&lt;span class="go"&gt;    [Attribute(Name(&amp;#39;models&amp;#39;, Load()), &amp;#39;Model&amp;#39;, Load())],&lt;/span&gt;
&lt;span class="go"&gt;    [],&lt;/span&gt;
&lt;span class="go"&gt;    [&lt;/span&gt;

&lt;span class="go"&gt;        Assign(&lt;/span&gt;
&lt;span class="go"&gt;            [Name(&amp;#39;id&amp;#39;, Store())],&lt;/span&gt;
&lt;span class="go"&gt;            Call(&lt;/span&gt;
&lt;span class="go"&gt;                Attribute(Name(&amp;#39;models&amp;#39;, Load()), &amp;#39;PositiveSmallIntegerField&amp;#39;, Load()),&lt;/span&gt;
&lt;span class="go"&gt;                [],&lt;/span&gt;
&lt;span class="go"&gt;                [&lt;/span&gt;
&lt;span class="go"&gt;                keyword(&amp;#39;primary_key&amp;#39;, NameConstant(True)),&lt;/span&gt;
&lt;span class="go"&gt;                keyword(&amp;#39;verbose_name&amp;#39;, Call(Name(&amp;#39;_&amp;#39;, Load()), [Str(&amp;#39;Name&amp;#39;)], []))&lt;/span&gt;
&lt;span class="go"&gt;                ]&lt;/span&gt;
&lt;span class="go"&gt;            )&lt;/span&gt;
&lt;span class="go"&gt;        ),&lt;/span&gt;

&lt;span class="go"&gt;        Assign(&lt;/span&gt;
&lt;span class="go"&gt;            [Name(&amp;#39;name&amp;#39;, Store())],&lt;/span&gt;
&lt;span class="go"&gt;            Call(&lt;/span&gt;
&lt;span class="go"&gt;                Attribute(Name(&amp;#39;models&amp;#39;, Load()), &amp;#39;CharField&amp;#39;, Load()),&lt;/span&gt;
&lt;span class="go"&gt;                [],&lt;/span&gt;
&lt;span class="go"&gt;                [keyword(&amp;#39;max_length&amp;#39;, Num(100))]&lt;/span&gt;
&lt;span class="go"&gt;            )&lt;/span&gt;
&lt;span class="go"&gt;        ),&lt;/span&gt;

&lt;span class="go"&gt;        Assign(&lt;/span&gt;
&lt;span class="go"&gt;            [Name(&amp;#39;created_by&amp;#39;, Store())],&lt;/span&gt;
&lt;span class="go"&gt;            Call(&lt;/span&gt;
&lt;span class="go"&gt;                Attribute(Name(&amp;#39;models&amp;#39;, Load()), &amp;#39;ForeignKey&amp;#39;, Load()),&lt;/span&gt;
&lt;span class="go"&gt;                [Name(&amp;#39;User&amp;#39;, Load())],&lt;/span&gt;
&lt;span class="go"&gt;                [keyword(&amp;#39;on_delete&amp;#39;, Attribute(Name(&amp;#39;models&amp;#39;, Load()), &amp;#39;PROTECT&amp;#39;, Load()))]&lt;/span&gt;
&lt;span class="go"&gt;            )&lt;/span&gt;
&lt;span class="go"&gt;        ),&lt;/span&gt;

&lt;span class="go"&gt;        FunctionDef(&lt;/span&gt;
&lt;span class="go"&gt;            &amp;#39;__str__&amp;#39;,&lt;/span&gt;
&lt;span class="go"&gt;            arguments([arg(&amp;#39;self&amp;#39;, None)],None,[],[],None,[]),&lt;/span&gt;
&lt;span class="go"&gt;            [Return(Attribute(Name(&amp;#39;self&amp;#39;, Load()), &amp;#39;name&amp;#39;, Load()))], [], None&lt;/span&gt;
&lt;span class="go"&gt;        )&lt;/span&gt;
&lt;span class="go"&gt;    ],&lt;/span&gt;
&lt;span class="go"&gt;    []&lt;/span&gt;
&lt;span class="go"&gt;    )&lt;/span&gt;
&lt;span class="go"&gt;])&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;If we look closely at the dump we can identify that our &lt;strong&gt;model fields are all
Assign nodes&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Let's zoom-in on the "name" field:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;Assign(
    [Name(&amp;#39;name&amp;#39;, Store())],
    Call(
        Attribute(Name(&amp;#39;models&amp;#39;, Load()), &amp;#39;CharField&amp;#39;, Load()),
        [],
        [keyword(&amp;#39;max_length&amp;#39;, Num(100))]
    )
)
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The model field is an assignment of a Call node (CharField) to a Name node ("name"). The Call node has a list of arguments. In this case we only have one argument "max_length" with the numeric value 100.&lt;/p&gt;
&lt;p&gt;Our id field looks like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;Assign(
    [Name(&amp;#39;id&amp;#39;, Store())],
    Call(
        Attribute(Name(&amp;#39;models&amp;#39;, Load()), &amp;#39;PositiveSmallIntegerField&amp;#39;, Load()), [], [
            keyword(&amp;#39;primary_key&amp;#39;, NameConstant(True)),
            keyword(&amp;#39;verbose_name&amp;#39;, Call(
               Name(&amp;#39;_&amp;#39;, Load()), [Str(&amp;#39;Name&amp;#39;)], []
            )
        )
    ])
)
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The id field is also an Assign node with a Name node and a Call node. The id field has two keywords - &lt;code&gt;primary_key&lt;/code&gt; and &lt;code&gt;verbose_name,&lt;/code&gt; which is &lt;strong&gt;the one we are looking for&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id="evaluating-a-model-field"&gt;&lt;a class="toclink" href="#evaluating-a-model-field"&gt;Evaluating a Model Field&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To evaluate the fields we first need to identify them. We already saw that &lt;strong&gt;model fields are Assign nodes &lt;/strong&gt;but we &lt;strong&gt;can't rely on them being the only Assign nodes &lt;/strong&gt;in the class.&lt;/p&gt;
&lt;p&gt;The only thing we can rely on is that at the top level of the class the attribute names are unique. Meaning, &lt;strong&gt;if we know there is a field called "name" we can assume the attribute "name" of the class is the field.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Let's join forces with Django model &lt;code&gt;_meta&lt;/code&gt; to find the nodes of the model fields:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FieldDoesNotExist&lt;/span&gt;


&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;model_node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nb"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ast&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Assign&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;continue&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;targets&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;continue&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nb"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;targets&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;ast&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;continue&lt;/span&gt;

    &lt;span class="n"&gt;field_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;targets&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;field&lt;/span&gt; &lt;span class="o"&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;_meta&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;field_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;FieldDoesNotExist&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;continue&lt;/span&gt;

   &lt;span class="c1"&gt;# node is field!&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Let's break it down:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Model fields are defined at the top level of the class&lt;/strong&gt; - we only need to check attributes defined at the top level (no need to "visit" nodes recursively).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Model fields will have a Name target &lt;/strong&gt;- the name of the field.&lt;/li&gt;
&lt;li&gt;Finally, the field we assign will be &lt;strong&gt;registered in the Django model as a field&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now we have the field node and we can check if there is a &lt;code&gt;verbose_name&lt;/code&gt; attribute defined.&lt;/p&gt;
&lt;p&gt;Let's iterate the keywords and search for &lt;code&gt;verbose_name&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;kw&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;keywords&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="hll"&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;kw&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;arg&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;verbose_name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;       &lt;span class="n"&gt;verbose_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;kw&lt;/span&gt;
&lt;/span&gt;        &lt;span class="k"&gt;break&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;verbose_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;At this point, if &lt;code&gt;verbose_name&lt;/code&gt; is None we know that the attribute was not set and we are ready to &lt;strong&gt;issue our first warning!&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id="issuing-django-checks"&gt;&lt;a class="toclink" href="#issuing-django-checks"&gt;Issuing Django checks&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To issue checks we need to register a function with the check framework:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.core&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;check&lt;/span&gt; &lt;span class="n"&gt;checks&lt;/span&gt;

&lt;span class="hll"&gt;&lt;span class="nd"&gt;@checks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;checks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tags&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&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run_custom_checks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app_configs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# implement check logic&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Inside the function we implement the check logic and return a list of checks.&lt;/p&gt;
&lt;p&gt;We want to warn the developer that a field is missing a &lt;code&gt;verbose_name&lt;/code&gt; attribute, so once we find a field that has no &lt;code&gt;verbose_name&lt;/code&gt; we create a &lt;code&gt;CheckMessage&lt;/code&gt; of type &lt;code&gt;Warning&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.core.checks&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="ne"&gt;Warning&lt;/span&gt;

&lt;span class="nd"&gt;@checks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;checks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tags&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run_custom_checks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app_configs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="c1"&gt;# inspect and parse models...&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;
&lt;span class="hll"&gt;        &lt;span class="ne"&gt;Warning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;            &lt;span class="s1"&gt;&amp;#39;Field has no verbose name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;            &lt;span class="n"&gt;hint&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Set verbose name on field &lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s1"&gt;.&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;            &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;            &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;H001&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;I assigned the code &lt;code&gt;H00X&lt;/code&gt; to my warnings (guess why…). For each warning we can also add a hint to inform the developer on how to address the issue raised by the warning.&lt;/p&gt;
&lt;h3 id="putting-it-all-together"&gt;&lt;a class="toclink" href="#putting-it-all-together"&gt;Putting it all together&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To recap what we did so far:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Get the source code for a model using inspect.&lt;/li&gt;
&lt;li&gt;Parse the model source code using ast and identify the field nodes.&lt;/li&gt;
&lt;li&gt;Examine a field node and check if &lt;code&gt;verbose_name&lt;/code&gt; is defined.&lt;/li&gt;
&lt;li&gt;Register a function with the check framework and issue a Warning.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The skeleton of a function that checks a single model:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# common/checks.py&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_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="w"&gt;   &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Check a single model.&lt;/span&gt;

&lt;span class="sd"&gt;   Yields (django.checks.CheckMessage)&lt;/span&gt;
&lt;span class="sd"&gt;   &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
   &lt;span class="n"&gt;model_source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;inspect&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getsource&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_node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ast&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model_source&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;node&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;model_node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

       &lt;span class="c1"&gt;# Check if node is a model field.&lt;/span&gt;

       &lt;span class="c1"&gt;# Check if field has verbose name defined&lt;/span&gt;

       &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="ne"&gt;Warning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;Field has no verbose name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;hint&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Set verbose name on field &lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s1"&gt;.&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;H001&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The next step is to implement a single function to &lt;strong&gt;iterate over all models&lt;/strong&gt;, run our checks and register it with the Django check framework:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# common/checks.py&lt;/span&gt;

&lt;span class="nd"&gt;@checks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;checks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tags&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_models&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app_configs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;errors&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="hll"&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;django&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;apps&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;apps&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_app_configs&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;
        &lt;span class="c1"&gt;# Skip third party apps.&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;site-packages&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;

        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_models&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;span class="hll"&gt;            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;check_message&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;check_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&gt;&lt;span class="hll"&gt;                &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;check_message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We use a little trick to skip models from third party apps. We assume that when
installing third party apps using &lt;code&gt;pip install&lt;/code&gt; they are installed in a
directory called "site-packages".&lt;/p&gt;
&lt;p&gt;The only thing left to do it to import this file somewhere in the code and
that's it.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# app/__init__.py&lt;/span&gt;

&lt;span class="hll"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;common.checks&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;  &lt;span class="c1"&gt;# noqa&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Let's see our new check in action:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;./manage.py&lt;span class="w"&gt; &lt;/span&gt;check

SystemCheckError:&lt;span class="w"&gt; &lt;/span&gt;System&lt;span class="w"&gt; &lt;/span&gt;check&lt;span class="w"&gt; &lt;/span&gt;identified&lt;span class="w"&gt; &lt;/span&gt;some&lt;span class="w"&gt; &lt;/span&gt;issues:

&lt;span class="hll"&gt;WARNINGS:
&lt;/span&gt;&lt;span class="hll"&gt;app.CustomerProfile.name:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;H001&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Field&lt;span class="w"&gt; &lt;/span&gt;has&lt;span class="w"&gt; &lt;/span&gt;no&lt;span class="w"&gt; &lt;/span&gt;verbose&lt;span class="w"&gt; &lt;/span&gt;name
&lt;/span&gt;&lt;span class="hll"&gt;HINT:&lt;span class="w"&gt; &lt;/span&gt;Set&lt;span class="w"&gt; &lt;/span&gt;verbose&lt;span class="w"&gt; &lt;/span&gt;name&lt;span class="w"&gt; &lt;/span&gt;on&lt;span class="w"&gt; &lt;/span&gt;the&lt;span class="w"&gt; &lt;/span&gt;field&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;name&amp;quot;&lt;/span&gt;.
&lt;/span&gt;
System&lt;span class="w"&gt; &lt;/span&gt;check&lt;span class="w"&gt; &lt;/span&gt;identified&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;issues&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;silenced&lt;span class="o"&gt;)&lt;/span&gt;.
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Exactly what we wanted!&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="custom-checks-in-the-real-world"&gt;&lt;a class="toclink" href="#custom-checks-in-the-real-world"&gt;Custom Checks in the Real World&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To give a sense of what you can do with Django checks, these are the checks we
use in our code base:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;H001: Field has no verbose name.&lt;/strong&gt;
This is the example we just saw.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;H002: Verbose name should use gettext.&lt;/strong&gt;
Make sure verbose_name is always in the form of &lt;code&gt;verbose_name=_('text')&lt;/code&gt;. If the value is not using gettext it will not be translated.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;H003: Words in verbose name must be all upper case or all lower case.&lt;/strong&gt;
We decided to use only lower case in verbose names. Using lower case texts we were able to reuse more translations. One exception to the rule is acronyms such as API and ETL. The general rule we ended up with is making sure all words are either all lower or all upper case. For example, "etl run" is valid, "ETL run" is also valid, "Etl Run" is not valid.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;H004: Help text should use gettext.&lt;/strong&gt;
Help text is displayed to the user in admin forms and detail views so it should use gettext and be translated.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;H005: Model must define class Meta.&lt;/strong&gt;
The translation of the model name is defined in the model Meta class so every model must have a class Meta.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;H006: Model has no verbose name.&lt;/strong&gt;
Model verbose names are defined in the class Meta and are displayed to the user in the admin so they should be translated.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;H007: Model has no verbose name plural.&lt;/strong&gt;
Plural model names are used in the admin and are displayed to the user so they should be translated.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;H008: Must set db_index explicitly on a ForeignKey field.&lt;/strong&gt;
This must be the most useful check we defined. This check forces the developer to explicitly set db_index on every ForeignKey field. I wrote in the past about &lt;a href="/9-django-tips-for-working-with-databases#fk-indexes"&gt;how a database index is created implicitly for every foreign key field&lt;/a&gt;. By making sure the developer is aware of that and making him decide if an index is required or not, you are left with &lt;strong&gt;only the indexes you really need!&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is it, &lt;strong&gt;go piss off some colleagues!&lt;/strong&gt;&lt;/p&gt;
&lt;div class="admonition info"&gt;
&lt;p class="admonition-title"&gt;source code&lt;/p&gt;
&lt;p&gt;The complete source code for the checks above can be found in &lt;a href="https://gist.github.com/hakib/e2e50d41d19a6984dc63bd94580c8647" rel="noopener"&gt;this gist&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;</content><category term="articles"></category><category term="Django"></category><category term="Productivity"></category></entry><entry><title>9 Django Tips for Working with Databases</title><link href="https://hakibenita.com/9-django-tips-for-working-with-databases" rel="alternate"></link><published>2018-01-29T00:00:00+02:00</published><updated>2018-01-29T00:00:00+02:00</updated><author><name>Haki Benita</name></author><id>tag:hakibenita.com,2018-01-29:/9-django-tips-for-working-with-databases</id><summary type="html">&lt;p&gt;ORMs offer great utility for developers but abstracting access to the database has its costs. Developers who are willing to poke around the database and change some defaults often find that great improvements can be made.&lt;/p&gt;</summary><content type="html">&lt;hr&gt;
&lt;p&gt;ORMs offer great utility for developers but abstracting access to the database has its costs. Developers who are willing to poke around the database and change some defaults often find that great improvements can be made.&lt;/p&gt;
&lt;h3 id="aggregation-with-filter"&gt;&lt;a class="toclink" href="#aggregation-with-filter"&gt;Aggregation with Filter&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Prior to Django 2.0 if we wanted to get something like the total number of users and the total number of active users we had to resort to &lt;a href="https://docs.djangoproject.com/en/2.0/ref/models/conditional-expressions/" rel="noopener"&gt;conditional expressions&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.contrib.auth.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Sum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Case&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;When&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;IntegerField&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;aggregate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;total_users&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;total_active_users&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Case&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="hll"&gt;        &lt;span class="n"&gt;When&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;is_active&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;then&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;        &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;        &lt;span class="n"&gt;output_field&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;IntegerField&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;/span&gt;    &lt;span class="p"&gt;)),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;In Django 2.0 a &lt;code&gt;filter&lt;/code&gt; &lt;a href="https://docs.djangoproject.com/en/2.0/ref/models/querysets/#id6" rel="noopener"&gt;argument to aggregate functions&lt;/a&gt; was added to make this a lot easier:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.contrib.auth.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;F&lt;/span&gt;

&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;aggregate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;total_users&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="hll"&gt;    &lt;span class="n"&gt;total_active_users&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;filter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;F&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;is_active&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Nice, short and sweet.&lt;/p&gt;
&lt;p&gt;If you are using PostgreSQL, the two queries will look like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;total_users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CASE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;WHEN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;is_active&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;THEN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ELSE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;total_active_users&lt;/span&gt;
&lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;auth_users&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;total_users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;FILTER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;is_active&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;total_active_users&lt;/span&gt;
&lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;auth_users&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The second query uses the &lt;code&gt;FILTER (WHERE …)&lt;/code&gt; clause.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="queryset-results-as-namedtuple"&gt;&lt;a class="toclink" href="#queryset-results-as-namedtuple"&gt;QuerySet Results as &lt;code&gt;namedtuple&lt;/code&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href="/working-with-apis-the-pythonic-way"&gt;I'm a big fan of namedtuples&lt;/a&gt; and apparently starting Django 2.0 so is the ORM.&lt;/p&gt;
&lt;p&gt;In Django 2.0 a &lt;a href="https://docs.djangoproject.com/en/2.0/ref/models/querysets/#django.db.models.query.QuerySet.values_list" rel="noopener"&gt;new attribute was added to values_list called&lt;/a&gt; &lt;code&gt;named&lt;/code&gt;. Setting &lt;code&gt;named&lt;/code&gt; to true will return the queryset as a list of namedtuples:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;values_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="go"&gt;    &amp;#39;first_name&amp;#39;,&lt;/span&gt;
&lt;span class="go"&gt;    &amp;#39;last_name&amp;#39;,&lt;/span&gt;
&lt;span class="go"&gt;)[0]&lt;/span&gt;
&lt;span class="go"&gt;(&amp;#39;Haki&amp;#39;, &amp;#39;Benita&amp;#39;)&lt;/span&gt;

&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;user_names&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;values_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="go"&gt;    &amp;#39;first_name&amp;#39;,&lt;/span&gt;
&lt;span class="go"&gt;    &amp;#39;last_name&amp;#39;,&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="go"&gt;    named=True,&lt;/span&gt;
&lt;/span&gt;&lt;span class="go"&gt;)&lt;/span&gt;

&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;user_names&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="go"&gt;Row(first_name=&amp;#39;Haki&amp;#39;, last_name=&amp;#39;Benita&amp;#39;)&lt;/span&gt;
&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;user_names&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;first_name&lt;/span&gt;
&lt;/span&gt;&lt;span class="go"&gt;&amp;#39;Haki&amp;#39;&lt;/span&gt;

&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;user_names&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;last_name&lt;/span&gt;
&lt;span class="go"&gt;&amp;#39;Benita&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;hr&gt;
&lt;h3 id="custom-functions"&gt;&lt;a class="toclink" href="#custom-functions"&gt;Custom Functions&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Django ORM is very powerful and feature-rich but it can't possibly keep up with all database vendors. Luckily the ORM lets us extend it with custom functions.&lt;/p&gt;
&lt;p&gt;Say we have a Report model with a duration field. We want to find the average duration of all reports:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Avg&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;Report&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;aggregate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;avg_duration&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Avg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;duration&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="go"&gt;{&amp;#39;avg_duration&amp;#39;: datetime.timedelta(0, 0, 55432)}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;That's great, but average alone tells us very little. Let's try to fetch the standard deviation as well:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="go"&gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt; from django.db.models import Avg, StdDev&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;Report&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;aggregate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="gp"&gt;... &lt;/span&gt;    &lt;span class="n"&gt;avg_duration&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Avg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;duration&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="gp"&gt;... &lt;/span&gt;    &lt;span class="n"&gt;std_duration&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;StdDev&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;duration&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;span class="gp"&gt;... &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="go"&gt;ProgrammingError: function stddev_pop(interval) does not exist&lt;/span&gt;
&lt;span class="go"&gt;LINE 1: SELECT STDDEV_POP(&amp;quot;report&amp;quot;.&amp;quot;duration&amp;quot;) AS &amp;quot;std_dura...&lt;/span&gt;
&lt;span class="go"&gt;               ^&lt;/span&gt;
&lt;span class="go"&gt;HINT:  No function matches the given name and argument types.&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="go"&gt;You might need to add explicit type casts.&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Oops… PostgreSQL does not support stddev on an interval field - we need to convert the interval to a number before we can apply &lt;code&gt;STDDEV_POP&lt;/code&gt; to it.&lt;/p&gt;
&lt;p&gt;One option is extracting epoch from the duration:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;db=#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;AVG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;STDDEV_POP&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;EXTRACT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;EPOCH&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;report&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="go"&gt;        avg       |    stddev_pop&lt;/span&gt;
&lt;span class="go"&gt;------------------+------------------&lt;/span&gt;
&lt;span class="go"&gt;   00:00:00.55432 | 1.06310113695549&lt;/span&gt;

&lt;span class="go"&gt;(1 row)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;So how can we implement this in Django? You guessed it - a &lt;a href="https://docs.djangoproject.com/en/2.0/ref/models/expressions/#func-expressions" rel="noopener"&gt;custom
function&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# common/db.py&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Func&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Epoch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Func&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
   &lt;span class="n"&gt;function&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;EXTRACT&amp;#39;&lt;/span&gt;
   &lt;span class="n"&gt;template&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;%(function)s&lt;/span&gt;&lt;span class="s2"&gt;(&amp;#39;epoch&amp;#39; from &lt;/span&gt;&lt;span class="si"&gt;%(expressions)s&lt;/span&gt;&lt;span class="s2"&gt;)&amp;quot;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;And use our new function like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Avg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;StdDev&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;F&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;common.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Epoch&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;Report&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;aggregate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="gp"&gt;... &lt;/span&gt;    &lt;span class="n"&gt;avg_duration&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Avg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;duration&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="gp"&gt;... &lt;/span&gt;    &lt;span class="n"&gt;std_duration&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;StdDev&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Epoch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;F&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;duration&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))),&lt;/span&gt;
&lt;span class="gp"&gt;... &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="go"&gt;{&amp;#39;avg_duration&amp;#39;: datetime.timedelta(0, 0, 55432),&lt;/span&gt;
&lt;span class="go"&gt;&amp;#39;std_duration&amp;#39;: 1.06310113695549}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Notice the use of the &lt;a href="https://docs.djangoproject.com/en/2.0/ref/models/expressions/#f-expressions" rel="noopener"&gt;F expression&lt;/a&gt; in the call to Epoch.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="statement-timeout"&gt;&lt;a class="toclink" href="#statement-timeout"&gt;Statement Timeout&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;This is probably the easiest and most important tip I can give. We are all humans and we make mistakes. We can't possibly handle each and every edge case so &lt;strong&gt;we must set boundaries&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Unlike other non-blocking app servers such as Tornado, asyncio or even Node, Django usually uses synchronous worker processes. This means that &lt;strong&gt;when a user executes a long running operation, the worker process is blocked and no one else can use it until it is done&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;I'm sure no one is really running Django in production with just one worker process but we still want to make sure a single query is not hogging too much resources for too long.&lt;/p&gt;
&lt;p&gt;In most Django apps the majority of time is spent waiting for database queries. So, &lt;a href="https://www.postgresql.org/docs/9.6/static/runtime-config-client.html#GUC-STATEMENT-TIMEOUT" rel="noopener"&gt;setting a timeout on SQL queries&lt;/a&gt; &lt;strong&gt;is a good place to start.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We like setting a global timeout in our &lt;code&gt;wsgi.py&lt;/code&gt; file like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# wsgi.py&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db.backends.signals&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;connection_created&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.dispatch&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;receiver&lt;/span&gt;

&lt;span class="nd"&gt;@receiver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;connection_created&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;setup_postgres&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vendor&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;postgresql&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;

    &lt;span class="c1"&gt;# Timeout statements after 30 seconds.&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="hll"&gt;        &lt;span class="n"&gt;cursor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;SET statement_timeout TO 30000;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Why wsgi.py?&lt;/strong&gt; This way it only affects worker processes and not out-of-band analytic queries, cron tasks, etc.&lt;/p&gt;
&lt;p&gt;Hopefully, you are using &lt;a href="https://docs.djangoproject.com/en/2.0/ref/databases/#persistent-connections" rel="noopener"&gt;persistent database connections&lt;/a&gt;, so this per-connection setup should not add overhead to each request.&lt;/p&gt;
&lt;p&gt;The timeout can also be set &lt;a href="https://www.postgresql.org/docs/9.5/static/sql-alteruser.html" rel="noopener"&gt;at the user level&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;db=#&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;alter&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;user&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;app_user&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;statement_timeout&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;TO&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;30000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="go"&gt;ALTER ROLE&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;em&gt;SIDE NOTE&lt;/em&gt;: The other common place we spent a lot of time at is networking. So make sure when you call a remote service to &lt;a href="http://docs.python-requests.org/en/master/user/quickstart/#timeouts" rel="noopener"&gt;always set a timeout&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;requests&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;requests&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;https://api.slow-as-hell.com&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="hll"&gt;    &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;hr&gt;
&lt;h3 id="limit"&gt;&lt;a class="toclink" href="#limit"&gt;LIMIT&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;This is somewhat related to the last point about setting boundaries. Sometimes we want to let users produce reports and maybe export them to a spreadsheet. These types of views are usually the immediate suspects for any weird behavior in production.&lt;/p&gt;
&lt;p&gt;It's not uncommon to encounter a user that thinks it's reasonable to export all sales since the dawn of time in the middle of the work day. It's also not uncommon for this same user to open another tab and try again when the first attempt "got stuck".&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;This is where LIMIT comes in.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Let's limit a certain query to no more than 100 rows:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="go"&gt;# bad example&lt;/span&gt;

&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Sale&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;all&lt;/span&gt;&lt;span class="p"&gt;())[:&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This is the worst thing you can do. You just fetched all gazillion rows into memory just to return the first 100.&lt;/p&gt;
&lt;p&gt;Let's try again:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="go"&gt;data = Sale.objects.all()[:100]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This is better. Django will use the limit clause in the SQL to fetch only 100 rows.&lt;/p&gt;
&lt;p&gt;Now let's say we added the limit, the users are under control and all is good. We still have one problem - the user asked for all the sales and we gave them 100. The &lt;strong&gt;user now thinks there are only 100 sales - this is wrong&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Instead of blindly returning the first 100 rows, let's make sure that if there are more than 100 rows (normally after filtering) we throw an exception:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;LIMIT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;Sales&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;LIMIT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;ExceededLimit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LIMIT&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;Sale&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()[:&lt;/span&gt;&lt;span class="n"&gt;LIMIT&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This will work but we just added another query.&lt;/p&gt;
&lt;p&gt;Can we do better? I think we can:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;LIMIT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;

&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Sale&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()[:(&lt;/span&gt;&lt;span class="n"&gt;LIMIT&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&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="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;LIMIT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;ExceededLimit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LIMIT&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;data&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Instead of fetching 100 rows, we fetch 100 + 1 = 101 rows. &lt;strong&gt;If the 101 row exists it's enough for us to know there is more than 100 rows&lt;/strong&gt;. Or in other words, fetching LIMIT + 1 rows is the least we need to make sure there are no more than LIMIT rows in the query result.&lt;/p&gt;
&lt;p&gt;Remember the LIMIT + 1 trick, it can come pretty handy at times.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="select-for-update-of"&gt;&lt;a class="toclink" href="#select-for-update-of"&gt;Select for update … of&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;This one we learned the hard way. We started getting errors in the middle of the night about transactions timing out due to locks in the database.&lt;/p&gt;
&lt;p&gt;A common &lt;a href="/bullet-proofing-django-models"&gt;pattern for manipulating a transaction&lt;/a&gt; in our code would look like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;db_transaction&lt;/span&gt;

&lt;span class="c1"&gt;# ...&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;db_transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;atomic&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;transaction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;Transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;
&lt;span class="hll"&gt;        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;select_related&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;            &lt;span class="s1"&gt;&amp;#39;user&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;product&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;product__category&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="hll"&gt;        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;select_for_update&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Manipulating the transaction usually involves some properties from the user and the product so we often use select_related to force a join and save some queries.&lt;/p&gt;
&lt;p&gt;Updating the transaction also involves obtaining a lock to make sure it's not being manipulated by anyone else.&lt;/p&gt;
&lt;p&gt;Now, &lt;strong&gt;do you see the problem?&lt;/strong&gt; NO? Neither did we.&lt;/p&gt;
&lt;p&gt;We had some ETL processes running at night performing maintenance on the product and user tables. These ETLs performed updates and inserts to the tables so they also obtained locks on the tables.&lt;/p&gt;
&lt;p&gt;So what was the problem? &lt;strong&gt;When select_for_update is used along with select_related, Django will attempt to obtain a lock on all the tables in the query.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The code we used to fetch the transaction tried to obtain a lock on both the transaction table and the users, product and category tables. Once the ETL locked the last three tables in the middle of the night transactions started to fail.&lt;/p&gt;
&lt;p&gt;Once we had a better understanding of the problem we started looking for ways to lock only the necessary table - the transaction table. Luckily &lt;a href="https://docs.djangoproject.com/en/2.0/releases/2.0/#database-backend-api" rel="noopener"&gt;A new option to select_for_update just became available in Django 2.0&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;db_transaction&lt;/span&gt;

&lt;span class="c1"&gt;# ...&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;db_transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;atomic&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;transaction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;Transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;select_related&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;user&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;product&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;product__category&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="hll"&gt;        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;select_for_update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;            &lt;span class="n"&gt;of&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;self&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,)&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;        &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The &lt;code&gt;of&lt;/code&gt; option was added to &lt;a href="https://docs.djangoproject.com/en/2.0/ref/models/querysets/#django.db.models.query.QuerySet.select_for_update" rel="noopener"&gt;select_for_update&lt;/a&gt;. Using &lt;code&gt;of&lt;/code&gt; we can explicitly state which tables we want to lock. &lt;code&gt;self&lt;/code&gt; is a special keyword indicating we want to lock the model we are working on, in this case, the Transaction.&lt;/p&gt;
&lt;p&gt;Currently, this feature is only available for the PostgreSQL and Oracle backends.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="fk-indexes"&gt;&lt;a class="toclink" href="#fk-indexes"&gt;FK Indexes&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;When creating a model, Django will automatically create a B-Tree index on any foreign key. B-Tree indexes can get pretty heavy and sometimes they are not really necessary.&lt;/p&gt;
&lt;p&gt;A classic example is a through model for an M2M relation:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Membership&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;group&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Group&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;In the model above &lt;strong&gt;Django will implicitly create two indexes&lt;/strong&gt; - one for user and one for group.&lt;/p&gt;
&lt;p&gt;Another common pattern in M2M models is adding a unique constraint on the two fields. In our case it means that a user can only be a member of the same group once:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Membership&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;group&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Group&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;User&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;unique_together&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="hll"&gt;            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;group&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;user&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,)&lt;/span&gt;
&lt;/span&gt;        &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The unique_together will also create an index on both fields. So &lt;strong&gt;we get one model with two fields and three indexes&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Depending on the work we do with this model, many times we can dismiss the FK indexes and keep only the one created by the unique constraint:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Membership&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;group&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;Group&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="hll"&gt;        &lt;span class="n"&gt;db_index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="hll"&gt;        &lt;span class="n"&gt;db_index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&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;unique_together&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;group&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;user&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Removing redundant indexes will make insert and updates faster, plus, our database is now lighter which is always a good thing.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="order-of-columns-in-composite-index"&gt;&lt;a class="toclink" href="#order-of-columns-in-composite-index"&gt;Order of columns in composite index&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Indexes with more than one column are called &lt;strong&gt;composite indexes&lt;/strong&gt;. In B-Tree composite indexes the first column is indexed using a tree structure. From the leafs of the first level a new tree is created for the second level and so on.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The order of the columns in the index is significant&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;In the example above we would get a tree for groups first, and for each group another tree for all it's users.&lt;/p&gt;
&lt;p&gt;The rule of thumb for B-Tree composite indexes is to &lt;strong&gt;make the secondary indexes as small as possible&lt;/strong&gt;. In other words, columns with high cardinality (more distinct values) should come first.&lt;/p&gt;
&lt;p&gt;In our example it's reasonable to assume there are more users than groups so puting the user column first will make the secondary index on group, smaller.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Membership&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;group&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;Group&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;db_index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;db_index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&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;unique_together&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="hll"&gt;            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;user&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;group&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;        &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This is just a rule of thumb and it should be taken with a grain of salt. The final indexing should be optimized for the specific use case. The main point here is to &lt;strong&gt;be aware of implicit indexes and the significance of the column order in composite indexes.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="brin-indexes"&gt;&lt;a class="toclink" href="#brin-indexes"&gt;BRIN indexes&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;A B-Tree index is structured like a tree. The cost of looking up a single value is the height of the tree + 1 for the random access to the table. This makes B-Tree indexes ideal for unique constraints and (some) range queries.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The disadvantage of B-Tree index is its size - B-Tree indexes can get big.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;It's not uncommon to think there are no alternatives but databases offer &lt;a href="https://medium.com/@Alibaba_Cloud/principles-and-applications-of-the-index-types-supported-by-postgresql-481f59bab67d" rel="noopener"&gt;other types of indexes for specific use cases&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Starting with Django 1.11 there is a &lt;a href="https://docs.djangoproject.com/en/2.0/ref/models/indexes/" rel="noopener"&gt;new Meta option for creating indexes&lt;/a&gt; on a model. This gives us an opportunity to explore other types of indexes.&lt;/p&gt;
&lt;p&gt;PostgreSQL has a very useful type of index called BRIN (Block Range Index). &lt;strong&gt;Under some circumstances BRIN indexes can be more efficient than B-Tree indexes.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Let's see what &lt;a href="https://www.postgresql.org/docs/9.5/static/brin-intro.html" rel="noopener"&gt;the official documentation&lt;/a&gt; has
to say first:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;BRIN is designed for handling very large tables in which certain columns have
some natural correlation with their physical location within the table.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;To understand this statement it's important to understand how BRIN index works. As the name suggest, a BRIN index will create a mini index on a range of
adjacent blocks in the table. The index is very small and it can only say if a certain value is &lt;strong&gt;definitely not in the range &lt;/strong&gt;or if &lt;strong&gt;it might be in the range&lt;/strong&gt; of indexed blocks.&lt;/p&gt;
&lt;p&gt;Let's do a simplified example of how BRIN works to help us understand.&lt;/p&gt;
&lt;p&gt;Say we have these values in a column, each is one block:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;1, 2, 3, 4, 5, 6, 7, 8, 9
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Let's create a range for each 3 adjacent blocks:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;[1,2,3], [4,5,6], [7,8,9]
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;For each range we are going to &lt;strong&gt;keep the minimum and maximum&lt;/strong&gt; value in the range:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;[1–3], [4–6], [7–9]
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Using this index, let's try to search for the value 5:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;[1–3]&lt;/code&gt; - Definitely not here.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;[4–6]&lt;/code&gt; - Might be here.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;[7–9]&lt;/code&gt; - Definitely not here.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Using the index we limited our search to blocks 4–6.&lt;/p&gt;
&lt;p&gt;Let's take another example, this time the values in the column are not going to be nicely sorted:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;[2,9,5], [1,4,7], [3,8,6]
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;And this is our index with the minimum and maximum value in each range:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;[2–9], [1–7], [3–8]
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Let's try to search for the value 5:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;[2–9]&lt;/code&gt; - Might be here.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;[1–7]&lt;/code&gt; - Might be here.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;[3–8]&lt;/code&gt; - Might be here.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The index is useless - not only did it not limit the search at all, we actually had to read more because we fetched both the index and the entire table.&lt;/p&gt;
&lt;p&gt;Going back to the documentation:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;…columns have some natural correlation with their physical location within the
table.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is key for BRIN indexes. To get the most out of it, &lt;strong&gt;the values in the column must be roughly sorted or clustered on disk.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Now back to Django, &lt;strong&gt;what field do we have that is often indexed and will most likely be naturally sorted on disk?&lt;/strong&gt; That's right, I'm looking at you &lt;a href="https://docs.djangoproject.com/en/2.0/ref/models/fields/#datefield" rel="noopener"&gt;auto_now_add&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;A very common pattern in Django models is this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SomeModel&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;created&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DatetimeField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="hll"&gt;        &lt;span class="n"&gt;auto_now_add&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;When auto_now_add is used Django will automatically populate the field with the current time when the row is created. A &lt;code&gt;created&lt;/code&gt; field is usually also a great candidate for queries so it's often indexed.&lt;/p&gt;
&lt;p&gt;Let's add a BRIN index on &lt;code&gt;created&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.contrib.postgres.indexes&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BrinIndex&lt;/span&gt;
&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SomeModel&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;created&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DatetimeField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;auto_now_add&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&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;indexes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="hll"&gt;            &lt;span class="n"&gt;BrinIndex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;created&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
&lt;/span&gt;        &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;To get a sense of the difference in size I created a table with ~2M rows with a date field that is naturally sorted on disk:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;B-Tree index: 37 MB&lt;/li&gt;
&lt;li&gt;BRIN index: 49 KB&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That's right, no mistake.&lt;/p&gt;
&lt;p&gt;There are a lot more to consider when creating indexes than the size of the
index. But now, with Django 1.11 support for indexes, we can easily integrate
new types of indexes into our apps and make them lighter and faster.&lt;/p&gt;</content><category term="articles"></category><category term="Django"></category><category term="ORM"></category><category term="PostgreSQL"></category><category term="SQL"></category><category term="Performance"></category></entry><entry><title>How to Add a Text Filter to Django Admin</title><link href="https://hakibenita.com/how-to-add-a-text-filter-to-django-admin" rel="alternate"></link><published>2018-01-02T00:00:00+02:00</published><updated>2018-01-02T00:00:00+02:00</updated><author><name>Haki Benita</name></author><id>tag:hakibenita.com,2018-01-02:/how-to-add-a-text-filter-to-django-admin</id><summary type="html">&lt;p&gt;Django Admin search fields are great, throw a bunch of fields in search_fields and Django will handle the rest. The problem with search field begins when there are too many of them. This is how we replaced Django search with text filters for specific fields, and made Django admin much faster.&lt;/p&gt;</summary><content type="html">&lt;hr&gt;
&lt;p&gt;When creating a new Django Admin page a common conversation between the developer and the support personal might sound like this:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Developer&lt;/strong&gt;: Hey, I'm adding a new admin page for transactions. Can you tell
me how you want to search for transactions?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Support&lt;/strong&gt;: Sure, I usually just search by the username.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Developer&lt;/strong&gt;: Cool.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;search_fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;user__username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;blockquote&gt;
&lt;p&gt;Anything else?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Support&lt;/strong&gt;: I sometimes also want to search by the user email address.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Developer&lt;/strong&gt;: OK.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;search_fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
   &lt;span class="n"&gt;user__username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="hll"&gt;   &lt;span class="n"&gt;user__email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Support&lt;/strong&gt;: And the first and last name of course.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Developer:&lt;/strong&gt; Yeah, OK.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;search_fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;user__username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;user__email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="hll"&gt;    &lt;span class="n"&gt;user__first_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;    &lt;span class="n"&gt;user__last_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Developer:&lt;/strong&gt; Is that it?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Support&lt;/strong&gt;: Well, sometimes I need to search by the payment voucher number.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Developer&lt;/strong&gt;: OK.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;search_fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;user__username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;user__email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;user__first_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;user__last_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="hll"&gt;    &lt;span class="n"&gt;payment__voucher_number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Developer&lt;/strong&gt;: Anything else?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Support&lt;/strong&gt;: Some customers send their invoices and ask questions so I search by the invoice number as well.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Developer&lt;/strong&gt;: FINE!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;search_fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;user__username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;user__email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;user__first_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;user__last_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;payment__voucher_number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="hll"&gt;    &lt;span class="n"&gt;invoice__invoice_number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Developer&lt;/strong&gt;: OK, are you sure this is it?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Support&lt;/strong&gt;: Well, developers sometimes forward tickets to us and they use these long random strings. I'm never really sure what they are so I just search and hope for the best.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Developer:&lt;/strong&gt; These are called UUID's.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;search_fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;user__username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;user__email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;user__first_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;user__last_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;payment__voucher_number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;invoice__invoice_number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="hll"&gt;    &lt;span class="n"&gt;user__uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;    &lt;span class="n"&gt;payment__uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;    &lt;span class="n"&gt;invoice__uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Developer:&lt;/strong&gt;  So is that it?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Support&lt;/strong&gt;: Yes, for now…&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="the-problem-with-search-fields"&gt;&lt;a class="toclink" href="#the-problem-with-search-fields"&gt;The Problem With Search Fields&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://docs.djangoproject.com/en/2.0/ref/contrib/admin/#django.contrib.admin.ModelAdmin.search_fields" rel="noopener"&gt;Django Admin search fields&lt;/a&gt; are great, throw a bunch of fields in &lt;code&gt;search_fields&lt;/code&gt; and Django will handle the rest.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The problem with search field begins when there are too many of them.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;When the admin user want to search by UID or email, Django has no idea this is what the user intended so it has to search by all the fields listed in &lt;code&gt;search_fields&lt;/code&gt;. These "match any" queries have huge WHERE clauses and lots of joins and can quickly become very slow.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Using a regular ListFilter is not an option&lt;/strong&gt; -&lt;code&gt;ListFilter&lt;/code&gt; will render a list of choices from the distinct values of the field. Some fields we listed above are unique and the others have many distinct values - &lt;strong&gt;Showing choices is not an option.&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id="bridging-the-gap-between-django-and-the-user"&gt;&lt;a class="toclink" href="#bridging-the-gap-between-django-and-the-user"&gt;Bridging the gap between Django and the user&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;We started thinking of ways we can create multiple search fields - one for each field or group of fields. We thought that if the user want to search by email or UID there is no reason to search by any other field.&lt;/p&gt;
&lt;p&gt;After some thought we came up with a solution - &lt;strong&gt;a custom &lt;a href="https://docs.djangoproject.com/en/2.0/ref/contrib/admin/#django.contrib.admin.ModelAdmin.list_filter" rel="noopener"&gt;SimpleListFilter&lt;/a&gt;&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ListFilter allows for custom filtering logic.&lt;/li&gt;
&lt;li&gt;ListFilter can have a custom template.&lt;/li&gt;
&lt;li&gt;Django already has support for multiple ListFilters.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We wanted it to look like this:&lt;/p&gt;
&lt;figure&gt;&lt;img alt="A text list filter" src="https://hakibenita.com/images/01-how-to-add-a-text-filter-to-django-admin.png"&gt;&lt;figcaption&gt;A text list filter&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="implementing-inputfilter"&gt;&lt;a class="toclink" href="#implementing-inputfilter"&gt;Implementing &lt;code&gt;InputFilter&lt;/code&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;What we want to do is have a &lt;strong&gt;ListFilter with a text input instead of choices.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Before we dive into the implementation, let's start from the end. This is how we want to use our &lt;code&gt;InputFilter&lt;/code&gt; in a &lt;code&gt;ModelAdmin&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UIDFilter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;InputFilter&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="hll"&gt;    &lt;span class="n"&gt;parameter_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;uid&amp;#39;&lt;/span&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;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;UID&amp;#39;&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;queryset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;queryset&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;uid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="hll"&gt;            &lt;span class="k"&gt;return&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;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;                &lt;span class="n"&gt;Q&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;                &lt;span class="n"&gt;Q&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payment__uid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;                &lt;span class="n"&gt;Q&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user__uid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;            &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;And use it like any other list filter in a &lt;code&gt;ModelAdmin&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TransactionAdmin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ModelAdmin&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="c1"&gt;# ...&lt;/span&gt;

    &lt;span class="n"&gt;list_filter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="hll"&gt;        &lt;span class="n"&gt;UUIDFilter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;We create a custom filter for the uuid field - &lt;code&gt;UIDFilter&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;We set the &lt;code&gt;parameter_name&lt;/code&gt; in the URL to be &lt;code&gt;uid&lt;/code&gt;. A URL filtered by uid will look like this &lt;code&gt;/admin/app/transaction?uid=&amp;lt;uid&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;If the user entered a uid we search by transaction uid, payment uid or user uid.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;So far this is just like a regular custom ListFilter.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Now that we have a better idea of what we want let's implement our &lt;code&gt;InputFilter&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;InputFilter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SimpleListFilter&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;template&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;admin/input_filter.html&amp;#39;&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;lookups&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model_admin&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# Dummy, required to show the filter.&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;((),)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We inherit from &lt;code&gt;SimpleListFilter&lt;/code&gt; and override the template. We don't have any lookups and we want the template to render a text input instead of choices:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="cm"&gt;&amp;lt;!-- templates/admin/input_filter.html --&amp;gt;&lt;/span&gt;

{% load i18n %}

&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h3&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;{% blocktrans with filter_title=title %} By {{ filter_title }} {% endblocktrans %}&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h3&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="hll"&gt;    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;GET&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt;
           &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;text&amp;quot;&lt;/span&gt;
           &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;{{ spec.value|default_if_none:&amp;#39;&amp;#39; }}&amp;quot;&lt;/span&gt;
           &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;{{ spec.parameter_name }}&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We use similar markup to Django's existing list filter to make it native. The template renders a simple form with a GET action and a text field for the parameter. When this form is submitted the URL will be updated with the parameter name and the submitted value.&lt;/p&gt;
&lt;h3 id="play-nice-with-other-filters"&gt;&lt;a class="toclink" href="#play-nice-with-other-filters"&gt;Play Nice With Other Filters&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;So far our filter works but only if there are no other filters. If we want to play nice with other filters we need to consider them in our form. To do that, we need to get their values.&lt;/p&gt;
&lt;p&gt;The list filter has another function called "choices". The function accepts a &lt;code&gt;changelist&lt;/code&gt; object that contains all the information about the current view and return a list of choices.&lt;/p&gt;
&lt;p&gt;We don't have any choices, so we are going to use this function to extract all the filters that were applied to the queryset and expose them to the template:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;InputFilter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SimpleListFilter&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;template&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;admin/input_filter.html&amp;#39;&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;lookups&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model_admin&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# Dummy, required to show the filter.&lt;/span&gt;
        &lt;span class="k"&gt;return&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;choices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;changelist&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# Grab only the &amp;quot;all&amp;quot; option.&lt;/span&gt;
        &lt;span class="n"&gt;all_choice&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;changelist&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;all_choice&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;query_parts&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&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;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;changelist&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_filters_params&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parameter_name&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;all_choice&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;To include the filters we &lt;strong&gt;add a hidden input field for each parameter&lt;/strong&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="cm"&gt;&amp;lt;!-- templates/admin/input_filter.html --&amp;gt;&lt;/span&gt;

{% load i18n %}

&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;h3&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;{% blocktrans with filter_title=title %} By {{ filter_title }} {% endblocktrans %}&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;h3&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    {% with choices.0 as all_choice %}
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;GET&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="hll"&gt;        {% for k, v in all_choice.query_parts %}
&lt;/span&gt;&lt;span class="hll"&gt;        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;hidden&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;{{ k }}&amp;quot;&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;{{ v }}&amp;quot;&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;        {% endfor %}
&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt;
            &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;text&amp;quot;&lt;/span&gt;
            &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;{{ spec.value|default_if_none:&amp;#39;&amp;#39; }}&amp;quot;&lt;/span&gt;
            &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;{{ spec.parameter_name }}&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;

    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;form&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    {% endwith %}
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Now we have a filter with a text input that plays nice with other filters. The only thing left to do it to &lt;strong&gt;add a "clear" option.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;To clear the filter we need a URL that include all filters except ours:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="cm"&gt;&amp;lt;!-- templates/admin/input_filter.html --&amp;gt;&lt;/span&gt;

...

&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;input&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;text&amp;quot;&lt;/span&gt;
  &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;{{ spec.value|default_if_none:&amp;#39;&amp;#39; }}&amp;quot;&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;{{ spec.parameter_name }}&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;

&lt;span class="hll"&gt;{% if not all_choice.selected %}
&lt;/span&gt;&lt;span class="hll"&gt;  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;strong&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt; &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;{{ all_choice.query_string }}&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;⨉ {% trans &amp;#39;Remove&amp;#39; %}&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;strong&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;{% endif %}
&lt;/span&gt;
...
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Voilà!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This is what we get:&lt;/p&gt;
&lt;figure&gt;&lt;img alt="InputFilter with other filters and a remove button" src="https://hakibenita.com/images/02-how-to-add-a-text-filter-to-django-admin.png"&gt;&lt;figcaption&gt;InputFilter with other filters and a remove button&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The complete code of admin.py can be found in &lt;a href="https://gist.githubusercontent.com/hakib/1491a848e71078dae81fca48c46cc258/raw/19934611bcdd6d806aabaf00f55f582cd40fffd8/admin.py" rel="noopener"&gt;this gist&lt;/a&gt; and the complete code of the tempalte can be found in &lt;a href="https://gist.githubusercontent.com/hakib/1491a848e71078dae81fca48c46cc258/raw/19934611bcdd6d806aabaf00f55f582cd40fffd8/input_filter.html" rel="noopener"&gt;this gist&lt;/a&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id="bonus"&gt;&lt;a class="toclink" href="#bonus"&gt;Bonus&lt;/a&gt;&lt;/h4&gt;
&lt;h3 id="search-multiple-words-similar-to-django-search"&gt;&lt;a class="toclink" href="#search-multiple-words-similar-to-django-search"&gt;Search Multiple Words Similar to Django Search&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;You might have noticed that when searching multiple words &lt;a href="https://github.com/django/django/blob/master/django/contrib/admin/options.py#L972" rel="noopener"&gt;Django find results that include at least one of the words and not all&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For example, if you search for a user "John Duo" Django will find both "John Foo" and "Bar Due". This is very convenient when searching for things like full name, product names and so on.&lt;/p&gt;
&lt;p&gt;We can implement a similar condition using our &lt;code&gt;InputFilter&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Q&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserFilter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;InputFilter&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;parameter_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;user&amp;#39;&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;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;User&amp;#39;&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;queryset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;queryset&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;term&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;term&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;

&lt;span class="hll"&gt;        &lt;span class="n"&gt;any_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Q&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;bit&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;term&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;            &lt;span class="n"&gt;any_name&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;                &lt;span class="n"&gt;Q&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user__first_name__icontains&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;bit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;                &lt;span class="n"&gt;Q&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user__last_name__icontains&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;bit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;            &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;
        &lt;span class="k"&gt;return&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;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;any_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;This is it!&lt;/strong&gt;&lt;/p&gt;</content><category term="articles"></category><category term="Django"></category><category term="Django Admin"></category></entry><entry><title>Django Admin Range-Based Date Hierarchy</title><link href="https://hakibenita.com/django-admin-range-based-date-hierarchy" rel="alternate"></link><published>2017-12-11T00:00:00+02:00</published><updated>2017-12-11T00:00:00+02:00</updated><author><name>Haki Benita</name></author><id>tag:hakibenita.com,2017-12-11:/django-admin-range-based-date-hierarchy</id><summary type="html">&lt;p&gt;A few weeks ago we encountered a major performance regression in one of our main admin pages. The page took more than 10 seconds to load (at best) and hit the query execution timeout at worst. When we investigated the issue, we found that the date hierarchy was the cause for most of the time spent loading the admin page. In the article we describe how we significantly improved the performance of Django Admin date hierarchy&lt;/p&gt;</summary><content type="html">&lt;hr&gt;
&lt;p&gt;A few weeks ago we encountered a major performance regression in one of our main admin pages. The page took more than 10 seconds to load (at best) and hit the query execution timeout at worst.&lt;/p&gt;
&lt;p&gt;The page was an admin list view of a transactions model, one of the main models in our app. The model is used by support personal on a daily basis. It has millions of rows, and used several joins to display relevant information.&lt;/p&gt;
&lt;p&gt;The most common use for the page was to filter transactions by a certain period, most commonly the last day. We used Django admin date hierarchy to drill down on the creation date of the records. When we investigated the issue, we found that the date hierarchy was the cause for most of the time spent loading the admin page.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="identifying-the-problem"&gt;&lt;a class="toclink" href="#identifying-the-problem"&gt;Identifying the Problem&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The filtered URL looked like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;/admin/transactions/created__year=2017&amp;amp;created__month=11
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We identified the "heavy" query as the one fetching the data to populate the list. The query performed the join with all the lookup tables and applied the filters we listed in the ModelAdmin. The relevant WHERE clause was:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;created&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BETWEEN&lt;/span&gt;
&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;2017-01-01T00:00:00+00:00&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;timestamptz&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AND&lt;/span&gt;
&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;2017-12-31T23:59:59.999999+00:00&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;timestamptz&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;AND&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;EXTRACT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;month&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;created&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;TIME&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ZONE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;UTC&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;When we inspected the execution plan of this query we found this snippet at the bottom:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;Index&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Scan&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;using&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ix&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;transactions_transaction&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;cost&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.43..90663.65&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;rows&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;4561&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;471&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;Index&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Cond&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;created&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;2017-01-01 02:00:00+02&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;timestamp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;with time zone&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="w"&gt;                 &lt;/span&gt;&lt;span class="k"&gt;AND&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;created&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;2018-01-01 01:59:59.999999+02&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;timestamp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;with time zone&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;Filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;date_part&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;month&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;UTC&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;created&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;11&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;double precision&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;PostgreSQL decided to use the index on the &lt;code&gt;created&lt;/code&gt; column. The estimate was 4,561 rows and the cost estimate maxed at 90,000. This estimate was wildly inaccurate which made us drew our first conclusion:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The estimate made by the database is very low and inaccurate.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We first suspected that the low estimate was due to stale statistics on the column. We gathered stats on the column and tried again:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;haki=#&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;analyze&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;transaction_transaction&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;created&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;No change. Estimate remained the same.&lt;/p&gt;
&lt;p&gt;We decided to give the query another look. We found this snippet in the WHERE clause a bit odd:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;created&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BETWEEN&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;2017-01-01T00:00:00+00:00&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;timestamptz&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;AND&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;2017-12-31T23:59:59.999999+00:00&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;timestamptz&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;AND&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;EXTRACT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;month&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;created&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;TIME&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ZONE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;UTC&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Django applied a filter on the &lt;strong&gt;full year &lt;/strong&gt;and then used &lt;code&gt;EXTRACT&lt;/code&gt; to filter only the month.&lt;/p&gt;
&lt;p&gt;We decided to check what happens if we remove the &lt;code&gt;EXTRACT&lt;/code&gt; function and instead simplify the condition:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;created&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BETWEEN&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;2017-11-01T00:00:00+00:00&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;timestamptz&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;AND&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;2017-11-30T23:59:59.999999+00:00&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;timestamptz&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The execution plan for that query was:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;Index Scan using ix on transactions_transaction (cost=0.43..1265.75 rows=13716 width=471)
    Index Cond: ((created &amp;gt;= ‘2017–11–01 02:00:00+02&amp;#39;::timestamp with time zone)
                  AND (created &amp;lt;= ‘2017–12–01 01:59:59.999999+02&amp;#39;::timestamp with time zone))
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The estimate is still low but the cost estimate is now significantly lower. This execution plan helped us reach our second conclusion:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The way Django apply the filter makes it difficult for the database to optimize the query.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="the-problem-with-the-way-date-hierarchy-is-implemented"&gt;&lt;a class="toclink" href="#the-problem-with-the-way-date-hierarchy-is-implemented"&gt;The Problem With the Way Date Hierarchy is Implemented&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Django filters the queryset for a given level in the date hierarchy using a database function to extract the relevant date part.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A function is opaque to the database optimizer&lt;/strong&gt;. If you have a range-based (BTREE) index on the field, using &lt;code&gt;EXTRACT&lt;/code&gt; does not limit the range at all. The &lt;strong&gt;index is not utilized&lt;/strong&gt; properly which might lead to a &lt;strong&gt;sub-optimal execution plan&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Once we had a better understanding of the problem we started discussing possible solutions.&lt;/p&gt;
&lt;h3 id="function-based-index"&gt;&lt;a class="toclink" href="#function-based-index"&gt;Function based index&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;A &lt;a href="https://www.postgresql.org/docs/9.1/static/indexes-expressional.html" rel="noopener"&gt;function based index&lt;/a&gt; is an index on an expression. In our case, an appropriate function based index might look like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;INDEX&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;transactions_transaction_created_month_brin&lt;/span&gt;
&lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;transactions_transaction&lt;/span&gt;
&lt;span class="k"&gt;USING&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;BRIN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;EXTRACT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;month&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;created&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;TIME&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ZONE&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;UTC&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The index made the query run faster but it came at a cost.&lt;/p&gt;
&lt;p&gt;The downside to this approach is having to maintain additional indexes for each level of the hierarchy (day and month). &lt;strong&gt;Additional indexes slow down insert and update operations, and take up space.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Another downside is the index size. We used a &lt;a href="https://www.postgresql.org/docs/9.5/static/brin-intro.html" rel="noopener"&gt;BRIN index&lt;/a&gt; in this case to minimize the size of the index. The table is naturally clustered by the creation date so this is an ideal use case for a BRIN index. When this is not the case, a similar BTREE index can become quite heavy.&lt;/p&gt;
&lt;div class="admonition tip"&gt;
&lt;p class="admonition-title"&gt;see also&lt;/p&gt;
&lt;p&gt;More on how BRIN indexes work in &lt;a href="/9-django-tips-for-working-with-databases#brin-indexes"&gt;9-django-tips-for-working-with-databases&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id="simplify-the-condition-used-by-django-date-hierarchy"&gt;&lt;a class="toclink" href="#simplify-the-condition-used-by-django-date-hierarchy"&gt;Simplify the Condition Used by Django Date Hierarchy&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;We decided to see if we can &lt;strong&gt;simplify the condition used by Django&lt;/strong&gt; to apply the date hierarchy.&lt;/p&gt;
&lt;p&gt;To implement the filter differently we first need to understand how Django admin applies filters on a queryset.&lt;/p&gt;
&lt;p&gt;In &lt;a href="https://github.com/django/django/blob/master/django/contrib/admin/views/main.py" rel="noopener"&gt;django/contrib/admin/views/main.py&lt;/a&gt; there is a function called
&lt;a href="https://github.com/django/django/blob/master/django/contrib/admin/views/main.py#L98" rel="noopener"&gt;get_filters&lt;/a&gt;. It might look scary at first but what it does is:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Extract all the query parameters from the URL.&lt;/li&gt;
&lt;li&gt;Get all the &lt;code&gt;ListFilter&lt;/code&gt; declared in the &lt;code&gt;ModelAdmin&lt;/code&gt; and apply them one by one to the parameter list.&lt;/li&gt;
&lt;li&gt;Each &lt;code&gt;ListFilter&lt;/code&gt; receive the parameter list, takes what it needs and remove the value from the parameter list.&lt;/li&gt;
&lt;li&gt;Any parameters left after all &lt;code&gt;ListFilter&lt;/code&gt;'s were applied are processed using the "default" Django filter. This is why, for example, it's possible to filter the queryset directly from the URL even when a &lt;code&gt;ListFilter&lt;/code&gt; is not explicitly defined.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If you look at the date hierarchy query parameters you'll see that there is nothing special about them, they are just regular URL params. This sparked an idea:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Implement a ListFilter to grab the relevant date hierarchy parameters from the parameter list and apply a custom filter on the queryset.&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id="the-implementation"&gt;&lt;a class="toclink" href="#the-implementation"&gt;The Implementation&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Let's start by grabbing the date hierarchy fields from the parameter list:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RangeBasedDateHierarchyListFilter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ListFilter&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="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&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_admin&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;date_hierarchy_field&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model_admin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;date_hierarchy&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;date_hierarchy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

        &lt;span class="n"&gt;date_hierarchy_field_re&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="hll"&gt;            &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;^&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s1"&gt;__(day|month|year)$&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;date_hierarchy_field&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&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;param&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;()):&lt;/span&gt;
            &lt;span class="n"&gt;match&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;date_hierarchy_field_re&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;param&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;period&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;group&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="hll"&gt;                &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;date_hierarchy&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;period&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;param&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt;We take the &lt;code&gt;date_hierarchy&lt;/code&gt; field name from the &lt;code&gt;model_admin.&lt;/code&gt;In our case it was &lt;code&gt;created&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;We create a Regex pattern to identify the parameters in the URL. The pattern is always the name of the date hierarchy field + the period (day, month, year).&lt;br&gt; In our case the possible parameters are &lt;code&gt;created__day&lt;/code&gt;, &lt;code&gt;created__month&lt;/code&gt; and &lt;code&gt;created__year&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;We iterate the parameter list, pop any date hierarchy parameter that match our criteria and store the period and the value in a dict.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Now, this is where all the magic happens:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RangeBasedDateHierarchyListFilter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ListFilter&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="c1"&gt;# ...&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;queryset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;queryset&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;tz&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_default_timezone&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;from_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;to_date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_date_range_for_hierarchy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;date_hierarchy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tz&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;queryset&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s1"&gt;__gte&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;date_hierarchy_field&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;from_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s1"&gt;__lt&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;date_hierarchy_field&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;to_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Django will call the &lt;code&gt;queryset&lt;/code&gt; function of our &lt;code&gt;ListFilter&lt;/code&gt; with a queryset, and the function is expected to return the filtered queryset.&lt;/p&gt;
&lt;p&gt;In the example above this is happening in a separate function (so we can test it, you know...):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_date_range_for_hierarchy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;date_hierarchy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tz&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Generate date range for date hierarchy.&lt;/span&gt;

&lt;span class="sd"&gt;    date_hierarchy &amp;lt;dict&amp;gt;:&lt;/span&gt;
&lt;span class="sd"&gt;        year (int)&lt;/span&gt;
&lt;span class="sd"&gt;        month (int or None)&lt;/span&gt;
&lt;span class="sd"&gt;        day (int or None)&lt;/span&gt;
&lt;span class="sd"&gt;    tz &amp;lt;timezone or None&amp;gt;:&lt;/span&gt;
&lt;span class="sd"&gt;        The timezone in which to generate the datetimes.&lt;/span&gt;
&lt;span class="sd"&gt;        If None, the datetimes will be naive.&lt;/span&gt;

&lt;span class="sd"&gt;    Returns (tuple):&lt;/span&gt;
&lt;span class="sd"&gt;        from_date (datetime.datetime, aware if tz is set) inclusive&lt;/span&gt;
&lt;span class="sd"&gt;        to_date (datetime.datetime, aware if tz is set) exclusive&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;from_date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;date_hierarchy&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;year&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;date_hierarchy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;month&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;date_hierarchy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;day&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;tz&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;from_date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tz&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;localize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;from_date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;day&amp;#39;&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;date_hierarchy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;to_date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;from_date&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;days&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;month&amp;#39;&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;date_hierarchy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;from_date&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;day&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="n"&gt;to_date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;from_date&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;days&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;day&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;to_date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;from_date&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;from_date&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&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;from_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;to_date&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The function receives the dict we constructed in &lt;code&gt;__init__&lt;/code&gt; and returns a date range to filter on. The &lt;code&gt;queryset&lt;/code&gt; function then applies a simpler range filter that our database can better utilize.&lt;/p&gt;
&lt;p&gt;To use the simplified range condition in our &lt;code&gt;ModelAdmin&lt;/code&gt; we need to add it as a &lt;code&gt;list_filter&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TransactionAdmin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ModelAdmin&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;date_hierarchy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;created&amp;#39;&lt;/span&gt;
    &lt;span class="n"&gt;list_filter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="c1"&gt;#...&lt;/span&gt;
        &lt;span class="n"&gt;RangeBasedDateHierarchyListFilter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;h3 id="the-result"&gt;&lt;a class="toclink" href="#the-result"&gt;The Result&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;After we deployed this change the performance of the page improved drastically. Our database was happy, the support team was happy and so were we.&lt;/p&gt;
&lt;div class="admonition info"&gt;
&lt;p class="admonition-title"&gt;source code&lt;/p&gt;
&lt;p&gt;See our package &lt;a href="https://github.com/hakib/django-admin-lightweight-date-hierarchy" rel="noopener"&gt;django-lightweight-date-hierarchy&lt;/a&gt; on github and pypi.&lt;/p&gt;
&lt;/div&gt;</content><category term="articles"></category><category term="Django"></category><category term="Django Admin"></category><category term="Performance"></category></entry><entry><title>Scaling Django Admin Date Hierarchy</title><link href="https://hakibenita.com/scaling-django-admin-date-hierarchy" rel="alternate"></link><published>2017-10-06T00:00:00+03:00</published><updated>2017-10-06T00:00:00+03:00</updated><author><name>Haki Benita</name></author><id>tag:hakibenita.com,2017-10-06:/scaling-django-admin-date-hierarchy</id><summary type="html">&lt;p&gt;The date hierarchy is a great feature but it comes at a price. On very large tables, the way date hierarchy is implemented can make an admin page nearly unusable. In this article we describe the limitations of the date hierarchy, and suggest a way to overcome them.&lt;/p&gt;</summary><content type="html">&lt;hr&gt;
&lt;div class="admonition info"&gt;
&lt;p class="admonition-title"&gt;package&lt;/p&gt;
&lt;p&gt;We published a package called &lt;a href="https://github.com/hakib/django-admin-lightweight-date-hierarchy" rel="noopener"&gt;django-admin-lightweight-date-hierarchy&lt;/a&gt; which overrides Django Admin &lt;code&gt;date_hierarchy&lt;/code&gt; template tag and eliminates all database queries from it.&lt;br&gt;For the implementation details and the shocking performance analysis read on.&lt;/p&gt;
&lt;/div&gt;
&lt;hr&gt;
&lt;p&gt;If you are not familiar with Django Admin &lt;a href="https://docs.djangoproject.com/en/1.11/ref/contrib/admin/#django.contrib.admin.ModelAdmin.date_hierarchy" rel="noopener"&gt;date_hierarchy&lt;/a&gt; you should, it's great. Set the &lt;code&gt;date_hierarchy&lt;/code&gt; attribute of a &lt;code&gt;ModelAdmin&lt;/code&gt; to a &lt;code&gt;DateField&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.contrib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Sale&lt;/span&gt;

&lt;span class="nd"&gt;@admin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Sale&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;SaleAdmin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ModelAdmin&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="hll"&gt;    &lt;span class="n"&gt;date_hierarchy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;created&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;And you get a nice drill-down menu at the top of the admin change list:&lt;/p&gt;
&lt;figure&gt;&lt;img alt="Django date_hierarchy in action" src="https://hakibenita.com/images/01-scaling-django-admin-date-hierarchy.png"&gt;&lt;figcaption&gt;Django date_hierarchy in action&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;When selecting a year, Django will filter the data to the selected year, and present a list of months for which there is data in that year.&lt;/p&gt;
&lt;p&gt;When selecting a month, Django will apply the filter and present the list of days for which there is data in that month.&lt;/p&gt;
&lt;h3 id="date_hierarchy-behind-the-scenes"&gt;&lt;a class="toclink" href="#date_hierarchy-behind-the-scenes"&gt;&lt;code&gt;date_hierarchy&lt;/code&gt; Behind the Scenes&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To produce a list of dates for which there is data, &lt;strong&gt;Django has to perform a query&lt;/strong&gt;. For example, to produce the list of years, the first level in the hierarchy, Django will execute the following query:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;django_datetime_trunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;year&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;sales_sale&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;created&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;UTC&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;datetimefield&amp;quot;&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;sales_sale&amp;quot;&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;sales_sale&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;created&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;IS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NOT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;datetimefield&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ASC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;When a year is selected, Django will execute a query to produce the next level in the hierarchy - months:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;django_datetime_trunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;month&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;sales_sale&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;created&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;UTC&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;datetimefield&amp;quot;&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;sales_sale&amp;quot;&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;sales_sale&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;created&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BETWEEN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;2017–01–01 00:00:00&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AND&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;2017–12–31 23:59:59.999999&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;AND&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;sales_sale&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;created&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BETWEEN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;2017–01–01 00:00:00&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AND&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;2017–12–31 23:59:59.999999&amp;#39;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;AND&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;sales_sale&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;created&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;IS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NOT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;datetimefield&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ASC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;h3 id="how-expensive-is-it"&gt;&lt;a class="toclink" href="#how-expensive-is-it"&gt;How Expensive is it?&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The date hierarchy is a great feature but it comes at a price.&lt;/p&gt;
&lt;p&gt;To illustrate the problem we created a simple &lt;code&gt;Sale&lt;/code&gt; model with two fields - id and created, and populated it with ~1,000,000 rows.&lt;/p&gt;
&lt;p&gt;Using &lt;a href="https://github.com/jazzband/django-debug-toolbar" rel="noopener"&gt;django-admin-toolbar&lt;/a&gt; we can see how long it takes Django to produce the date hierarchy:&lt;/p&gt;
&lt;figure&gt;&lt;img alt="Breakdown of SQL queries executed by Django Admin" src="https://hakibenita.com/images/02-scaling-django-admin-date-hierarchy.png"&gt;&lt;figcaption&gt;Breakdown of SQL queries executed by Django Admin&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;strong&gt;WOW! The page took a boggling ~8s to load, out of which 7.6 seconds are spent producing the date hierarchy!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Just for comparison, the exact same page without date_hierarchy:&lt;/p&gt;
&lt;figure&gt;&lt;img alt="Breakdown of SQL queries executed by Django Admin without date hierarchy" src="https://hakibenita.com/images/03-scaling-django-admin-date-hierarchy.png"&gt;&lt;figcaption&gt;Breakdown of SQL queries executed by Django Admin without date hierarchy&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;strong&gt;17ms. That's about 99.8% better.&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id="a-possible-solution"&gt;&lt;a class="toclink" href="#a-possible-solution"&gt;A Possible Solution&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Looking at the chart above it's clear that we have a problem. The date hierarchy is weighing our page making it nearly unusable.&lt;/p&gt;
&lt;p&gt;Django need to execute a query because it only wants to show dates for which there is data. In our case, &lt;strong&gt;we have sales every day&lt;/strong&gt;. Once we make this assumption &lt;strong&gt;we no longer have to query the data to produce a list of dates - we can just show them all!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The idea we came up with is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;if the user &lt;strong&gt;selected a month&lt;/strong&gt; we show &lt;strong&gt;all of the days in the month&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;If the user &lt;strong&gt;selected a year&lt;/strong&gt; we show &lt;strong&gt;all of the months in the year&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;If the user &lt;strong&gt;selected nothing &lt;/strong&gt;we need to make an additional assumption - in our case we decided to show &lt;strong&gt;+-3 years from the current year&lt;/strong&gt;. This is a compromise we were willing to make for the sake of performance and usability.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now that we have the general idea let's dive into the implementation.&lt;/p&gt;
&lt;h3 id="implementation"&gt;&lt;a class="toclink" href="#implementation"&gt;Implementation&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Looking at the output above we can see that the queries originate from a template tag called &lt;code&gt;{% date_hierarchy cl %}&lt;/code&gt;. The argument &lt;code&gt;cl&lt;/code&gt; is the &lt;code&gt;ChangeList&lt;/code&gt; created by the &lt;code&gt;ModelAdmin&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The implementation for the &lt;code&gt;date_hierarchy&lt;/code&gt; template tag can be found at &lt;a href="https://github.com/django/django/blob/master/django/contrib/admin/templatetags/admin_list.py#L329" rel="noopener"&gt;admin_list.py&lt;/a&gt;.The interesting part is where the queries are executed.&lt;/p&gt;
&lt;p&gt;Let's take a look at how Django produces a list of months for a given year:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;span class="n"&gt;year_field&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s1"&gt;__year&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;field_name&lt;/span&gt;

&lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;span class="n"&gt;year_lookup&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;year_field&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;link&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;cl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_query_string&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="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;field_generic&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="c1"&gt;# ...&lt;/span&gt;

&lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;year_lookup&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="hll"&gt;  &lt;span class="n"&gt;months&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cl&lt;/span&gt;&lt;span class="o"&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;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;year_field&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;year_lookup&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;  &lt;span class="n"&gt;months&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;months&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;dates&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;field_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;month&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;show&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;back&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="s1"&gt;&amp;#39;link&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;link&lt;/span&gt;&lt;span class="p"&gt;({}),&lt;/span&gt;
      &lt;span class="s1"&gt;&amp;#39;title&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;All dates&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;choices&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
      &lt;span class="s1"&gt;&amp;#39;link&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;link&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="n"&gt;year_field&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;year_lookup&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;month_field&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;month&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;month&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;}),&lt;/span&gt;
      &lt;span class="s1"&gt;&amp;#39;title&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;capfirst&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;formats&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;date_format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;month&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;YEAR_MONTH_FORMAT&amp;#39;&lt;/span&gt;&lt;span class="p"&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;month&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;months&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Our &lt;code&gt;date_hierarchy&lt;/code&gt; is set to the &lt;code&gt;created&lt;/code&gt;. If we drill-down on year 2017 we get the following URL:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;http://localhost:8000/admin/sales/sale/?created__year=2017
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;In the template tag &lt;code&gt;year_field&lt;/code&gt; is &lt;code&gt;created__year&lt;/code&gt; and &lt;code&gt;year_lookup&lt;/code&gt; is 2017. The generated query applies the filter on &lt;code&gt;created&lt;/code&gt; and fetches a list of months there is data for in year 2017 to the variable &lt;code&gt;months&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Let's replace this bit and populate &lt;code&gt;months&lt;/code&gt; with a list of all the months
in the year instead:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# months = cl.queryset.filter(**{year_field: year_lookup})&lt;/span&gt;
&lt;span class="c1"&gt;# months = getattr(months, &amp;#39;dates&amp;#39;)(field_name, &amp;#39;month&amp;#39;)&lt;/span&gt;

&lt;span class="c1"&gt;# All months of selected year.&lt;/span&gt;
&lt;span class="n"&gt;months&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;year_lookup&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;month&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&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;month&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;After the change:&lt;/p&gt;
&lt;figure&gt;&lt;img alt="Queries executed by Django Admin after the change" src="https://hakibenita.com/images/04-scaling-django-admin-date-hierarchy.png"&gt;&lt;figcaption&gt;Queries executed by Django Admin after the change&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;strong&gt;Awesome! No queries.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;And the list view:&lt;/p&gt;
&lt;figure&gt;&lt;img alt="All month of year 2017 are shown" src="https://hakibenita.com/images/05-scaling-django-admin-date-hierarchy.png"&gt;&lt;figcaption&gt;All month of year 2017 are shown&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Our little change worked! All the months are displayed and &lt;strong&gt;no queries are executed by the date hierarchy&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Let's do the same for days:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# days = cl.queryset.filter(**{year_field: year_lookup, month_field: month_lookup})&lt;/span&gt;
&lt;span class="c1"&gt;# days = getattr(days, dates_or_datetimes)(field_name, &amp;#39;day&amp;#39;)&lt;/span&gt;
&lt;span class="c1"&gt;# All days of month.&lt;/span&gt;

&lt;span class="n"&gt;days_in_month&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;calendar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;monthrange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;year_lookup&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;month_lookup&lt;/span&gt;&lt;span class="p"&gt;))[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;first_day_of_month&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;year_lookup&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;month_lookup&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;days&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;first_day_of_month&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;days&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;i&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;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;days_in_month&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We use the &lt;code&gt;calendar&lt;/code&gt; module to find out how many days there are in a given month.&lt;/p&gt;
&lt;p&gt;Let's handle the years. Remember, we fetch +-3 years from today:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# years = getattr(cl.queryset, dates_or_datetimes)(field_name, &amp;#39;year&amp;#39;)&lt;/span&gt;

&lt;span class="c1"&gt;# Three years in each direction.&lt;/span&gt;
&lt;span class="n"&gt;today&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_today&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;years&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&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;y&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;today&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;year&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="n"&gt;today&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;h3 id="integration"&gt;&lt;a class="toclink" href="#integration"&gt;Integration&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Up until now we fiddled with Django's source but we can't really do that. First we &lt;strong&gt;override the template tag to make Django use our implementation&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Let's copy the function and register a template with the same name:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# app/templatetags/admin_list.py&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.contrib.admin.templatetags.admin_list&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;register&lt;/span&gt;

&lt;span class="nd"&gt;@register&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inclusion_tag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;admin/date_hierarchy.html&amp;#39;&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;date_hierarchy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cl&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="c1"&gt;# ... (original implementation) ...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Register the library in our app:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# settings.py&lt;/span&gt;

&lt;span class="n"&gt;TEMPLATES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
  &lt;span class="c1"&gt;# ...&lt;/span&gt;
  &lt;span class="s1"&gt;&amp;#39;OPTIONS&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;# ...&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;libraries&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;# ...&lt;/span&gt;
      &lt;span class="s1"&gt;&amp;#39;admin&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;app.templatetags.admin_list&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Now Django uses our template tag instead of his.&lt;/p&gt;
&lt;p&gt;The problem with producing the date hierarchy is really an issue only for very large tables. &lt;strong&gt;We don't want to disable the existing behavior - we want to enable it only for very large tables&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Let's add an attribute on the &lt;code&gt;ModelAdmin&lt;/code&gt; to turn the default drill-down behavior on and off:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nd"&gt;@admin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Sale&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;SaleAdmin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ModelAdmin&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;date_hierarchy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;created&amp;#39;&lt;/span&gt;
&lt;span class="hll"&gt;    &lt;span class="n"&gt;date_hierarchy_drilldown&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;When &lt;code&gt;date_hierarchy_drilldown&lt;/code&gt; is set to False our new template tag will not execute queries. Otherwise, we preserve the original behavior.&lt;/p&gt;
&lt;p&gt;To implement this we add the following at the start of our implementation of the &lt;code&gt;date_hierarchy&lt;/code&gt; template tag:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;date_hierarchy_drilldown&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;cl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model_admin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;date_hierarchy_drilldown&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Now we can re-enable the default behavior when &lt;code&gt;date_hierarchy_drilldown=True&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;For example, producing a list of months:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;date_hierarchy_drilldown&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;months&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cl&lt;/span&gt;&lt;span class="o"&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;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;year_field&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;year_lookup&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;months&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;months&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dates_or_datetimes&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;field_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;month&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# All months of selected year.&lt;/span&gt;
    &lt;span class="n"&gt;months&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;year_lookup&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;month&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&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;month&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This is it! We've successfully scaled date hierarchy to handle millions of rows with a little compromise on UX.&lt;/p&gt;
&lt;h3 id="package"&gt;&lt;a class="toclink" href="#package"&gt;Package&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;We found this approach useful in several projects so we decided to publish it as a package:&lt;/p&gt;
&lt;p&gt;Using it is as simple as&lt;/p&gt;
&lt;p&gt;Install it&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;django-admin-lightweight-date-hierarchy
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Add it to your INSTALLED_APPS:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;INSTALLED_APPS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;django_admin_lightweight_date_hierarchy&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Set &lt;code&gt;date_hierarchy_drilldown&lt;/code&gt; to False on any ModelAdmin with date_hierarchy to prevent the default drill-down behavior:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nd"&gt;@admin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MyModel&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;MyModelAdmin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ModelAdmin&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="n"&gt;date_hierarchy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;created&amp;#39;&lt;/span&gt;
&lt;span class="hll"&gt;  &lt;span class="n"&gt;date_hierarchy_drilldown&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;To squeeze some more juice out of Django Admin check out this post as well:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cheers!&lt;/strong&gt;&lt;/p&gt;</content><category term="articles"></category><category term="Django"></category><category term="Django Admin"></category><category term="Performance"></category></entry><entry><title>How We Replaced Dozens of Test Fixtures With One Simple Function</title><link href="https://hakibenita.com/how-we-replaced-dozens-of-test-fixtures-with-one-simple-function" rel="alternate"></link><published>2017-08-16T00:00:00+03:00</published><updated>2017-08-16T00:00:00+03:00</updated><author><name>Haki Benita</name></author><id>tag:hakibenita.com,2017-08-16:/how-we-replaced-dozens-of-test-fixtures-with-one-simple-function</id><summary type="html">&lt;p&gt;We had a large codebase and a lot of tests. Unfortunately, a lot of our tests were a relic from when we were using fixtures extensively. In this article, we describe a different approach that reduced the number of fixtures we maintain.&lt;/p&gt;</summary><content type="html">&lt;hr&gt;
&lt;p&gt;It all started when we added feature flags to our app. After some deliberation we created a "feature set" model with boolean fields for each feature:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FeatureSet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&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;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&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;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;can_pay_with_credit_card&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BooleanField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;can_save_credit_card&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BooleanField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;can_receive_email_notifications&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BooleanField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We added a foreign key from the user account to the feature sets model, and created feature sets for "pro", "newbie" and "commercial" users.&lt;/p&gt;
&lt;p&gt;To enforce the features we added tests in appropriate places. For example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;pay_with_credit_card&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;user_account&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;feature_set&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;can_pay_with_credit_card&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;FeatureDisabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;can_pay_with_credit_card&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;h3 id="the-problem"&gt;&lt;a class="toclink" href="#the-problem"&gt;The Problem&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;At this point we had a large codebase and a lot of tests. Unfortunately, a lot of our tests were a relic from when we were using fixtures extensively.&lt;/p&gt;
&lt;p&gt;The thought of having to update and add new fixtures was unacceptable. But, we still had to test the new features so we started writing tests like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_should_charge_credit_card&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="hll"&gt;    &lt;span class="n"&gt;feature_set&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user_account&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;feature_set&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;    &lt;span class="n"&gt;feature_set&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;can_pay_with_credit_card&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;
&lt;/span&gt;    &lt;span class="n"&gt;feature_set&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;update_fields&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;can_pay_with_credit_card&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;pay_with_credit_card&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&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_should_fail_when_feature_disabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="hll"&gt;    &lt;span class="n"&gt;feature_set&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user_account&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;feature_set&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;    &lt;span class="n"&gt;feature_set&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;can_pay_with_credit_card&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;
&lt;/span&gt;    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertRaises&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FeatureDisabled&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;pay_with_credit_card&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We had a lot of tests to update and some of the features we added interrupted the flow of other tests which resulted in &lt;strong&gt;a mess!&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id="the-context-manager"&gt;&lt;a class="toclink" href="#the-context-manager"&gt;The Context Manager&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href="/how-to-test-django-signals-like-a-pro#enter-context-manager"&gt;We already used context managers to improve our tests in the past&lt;/a&gt;, and we thought we can use one here to set features on and off:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;contextlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;contextmanager&lt;/span&gt;

&lt;span class="nd"&gt;@contextmanager&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;feature_set&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;feature_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;original_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;feature_set&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;feature_name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;setattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;feature_set&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;feature_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;feature_set&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;update_fields&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;feature_name&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt;

    &lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nb"&gt;setattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;feature_set&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;feature_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;original_value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;feature_set&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;update_fields&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;feature_name&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;What does this context manager do?&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Save the original value of the feature.&lt;/li&gt;
&lt;li&gt;Set the new value for the feature.&lt;/li&gt;
&lt;li&gt;Yields - this where our test code actually executes.&lt;/li&gt;
&lt;li&gt;Set the feature back to the original value&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This made our tests much more elegant:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_should_charge_credit_card&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="hll"&gt;    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_account&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;feature_set&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;can_pay_with_credit_card&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;       &lt;span class="n"&gt;pay_with_credit_card&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&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_should_fail_when_feature_disabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="hll"&gt;    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_account&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;feature_set&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;can_pay_with_credit_card&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertRaises&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FeatureDisabled&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;pay_with_credit_card&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;h3 id="kwargs"&gt;&lt;a class="toclink" href="#kwargs"&gt;**kwargs&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;This context manager has proven to be very useful for features so we thought... &lt;strong&gt;why not use it for other things as well?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We had a lot of methods involving more than one feature:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_should_not_send_notification&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;feature_set&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user_account&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;feature_set&lt;/span&gt;
&lt;span class="hll"&gt;    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;feature_set&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;can_pay_with_credit_card&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;feature_set&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;can_receive_notifications&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;            &lt;span class="n"&gt;pay_with_credit_card&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Or more than one object:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_should_not_send_notification_to_inactive_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;feature_set&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user_account&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;feature_set&lt;/span&gt;
&lt;span class="hll"&gt;    &lt;span class="n"&gt;user_account&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_active&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;feature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;feature_set&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;can_receive_notifications&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;        &lt;span class="n"&gt;pay_with_credit_card&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;So we rewrote the context manager to accept any object and added support for multiple arguments:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nd"&gt;@contextmanager&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;temporarily&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="hll"&gt;    &lt;span class="n"&gt;original_values&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&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;k&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="nb"&gt;setattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;update_fields&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="hll"&gt;        &lt;span class="k"&gt;yield&lt;/span&gt;
&lt;/span&gt;
    &lt;span class="k"&gt;finally&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;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;original_values&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="nb"&gt;setattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="hll"&gt;        &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;update_fields&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;original_values&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The context manager can now accept multiple features, save the original values,
set the new values and restore when we are done.&lt;/p&gt;
&lt;p&gt;Testing became much easier:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_should_not_send_notification&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="hll"&gt;    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;temporarily&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;        &lt;span class="n"&gt;user_account&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;feature_set&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;        &lt;span class="n"&gt;can_pay_with_credit_card&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;        &lt;span class="n"&gt;can_receive_notifications&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;    &lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;        &lt;span class="n"&gt;pay_with_credit_card&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertEquals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;outbox&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We can now use the function on other objects as well:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_should_fail_to_login_inactive_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="hll"&gt;    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;temporarily&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;is_active&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;login&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&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="o"&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="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Profit!&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="the-hidden-performance-benefit"&gt;&lt;a class="toclink" href="#the-hidden-performance-benefit"&gt;The Hidden Performance Benefit&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;After a while getting comfortable with the new utility we noticed another performance benefit. In tests that had heavy setups we managed to move the setup from the test level to the class level.&lt;/p&gt;
&lt;p&gt;To illustrate the difference let's test a function that sends an invoice to the users. Invoices are usually sent only when the transaction is complete. To create a complete transaction we need a lot of setup (choose products, checkout, issue payment etc).&lt;/p&gt;
&lt;p&gt;This is a test that require a lot of setup:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TestSendInvoice&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="hll"&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;setUp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;transaction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;Transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;Transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;Transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;checkout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;Transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request_payment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;Transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;process_payment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_should_not_send_invoice_to_commercial_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;commercial&amp;#39;&lt;/span&gt;
        &lt;span class="n"&gt;mail&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;outbox&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
        &lt;span class="n"&gt;Transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send_invoice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mail&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;outbox&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&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_should_attach_special_offer_to_pro_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;pro&amp;#39;&lt;/span&gt;
        &lt;span class="n"&gt;mail&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;outbox&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
        &lt;span class="n"&gt;Transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send_invoice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mail&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;outbox&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;mail&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;outbox&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;Invoice and a special offer!&amp;#39;&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The &lt;code&gt;setUp&lt;/code&gt; function need to execute before each test function because the test functions change the objects and that might create a dangerous dependency between test cases.&lt;/p&gt;
&lt;p&gt;To prevent dependencies between test cases we need to make sure each test leaves the data exactly as it got it. Luckily, this is exactly what our new context manager does:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TestSendInvoice&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="nd"&gt;@classmethod&lt;/span&gt;
&lt;span class="hll"&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;setUpTestData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;        &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;transaction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;Transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;Transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;Transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;checkout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;Transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request_payment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;Transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;process_payment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_should_not_send_invoice_to_commercial_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;mail&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;outbox&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="hll"&gt;        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;temporarily&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;commercial&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;            &lt;span class="n"&gt;Transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send_invoice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mail&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;outbox&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&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_should_attach_special_offer_to_pro_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;mail&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;outbox&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="hll"&gt;        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;temporarily&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;pro&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;            &lt;span class="n"&gt;Transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send_invoice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mail&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;outbox&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mail&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;outbox&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Invoice and a special offer!&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We moved the setup code to setUpTestData. &lt;strong&gt;The setup code will execute only once for the entire test class resulting in quicker tests.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="final-words"&gt;&lt;a class="toclink" href="#final-words"&gt;Final Words&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The motivation for this context processor was our long unhealthy relationship with fixtures. As we scaled our app the fixtures became a burden. Having so many tests rely on them made it difficult to completely replace.&lt;/p&gt;
&lt;p&gt;With the addition of features we knew we did not want to rely on fixtures any more and we looked for creative, more verbose and maintainable ways, of managing test data. Having a simple way to create different variations of an object for testing was exactly what we needed.&lt;/p&gt;</content><category term="articles"></category><category term="Python"></category><category term="Django"></category><category term="Testing"></category></entry><entry><title>How to Manage Concurrency in Django Models</title><link href="https://hakibenita.com/how-to-manage-concurrency-in-django-models" rel="alternate"></link><published>2017-07-07T00:00:00+03:00</published><updated>2017-07-07T00:00:00+03:00</updated><author><name>Haki Benita</name></author><id>tag:hakibenita.com,2017-07-07:/how-to-manage-concurrency-in-django-models</id><summary type="html">&lt;p&gt;The days of desktop systems serving single users are long gone. Web applications nowadays are serving millions of users at the same time. With many users comes a wide range of new problems: concurrency problems. In this article we describe two approaches for managing concurrency in Django models.&lt;/p&gt;</summary><content type="html">&lt;hr&gt;
&lt;p&gt;The days of desktop systems serving single users are long gone. Web applications nowadays are serving millions of users at the same time. With many users comes a wide range of new problems: &lt;strong&gt;concurrency problems&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id="the-problem"&gt;&lt;a class="toclink" href="#the-problem"&gt;The Problem&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To demonstrate common concurrency issues we are going to work on a bank account
model:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Account&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AutoField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;primary_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IntegerField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;To get started we are going to implement a naive deposit and withdraw methods for an account instance:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;deposit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&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;withdraw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;balance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InsufficientFunds&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This seems innocent enough and it might even pass unit tests and integration tests on localhost. But, &lt;strong&gt;what happens when two users perform actions on the same account at the same time?&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;User A fetches the account&lt;ul&gt;
&lt;li&gt;balance is 100$&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;User B fetches the account&lt;ul&gt;
&lt;li&gt;balance is 100$&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;User B withdraws 30$&lt;ul&gt;
&lt;li&gt;balance is updated to 100$ - 30$ = 70$&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;User A deposits 50$&lt;ul&gt;
&lt;li&gt;balance is updated to 100$ + 50$ = 150$&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id="what-happened-here"&gt;&lt;a class="toclink" href="#what-happened-here"&gt;What Happened Here?&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;User B asked to withdraw 30$ and user A deposited 50$. We expect the balance to be 120$, but we ended up with 150$.&lt;/p&gt;
&lt;h4 id="why-did-it-happen"&gt;&lt;a class="toclink" href="#why-did-it-happen"&gt;Why Did it Happen?&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;At step 4, when user A updated the balance, the amount he had stored in memory was stale (user B had already withdrawn 30$). To prevent this situation from happening we need to &lt;strong&gt;make sure the resource we are working on is not altered while we are working on it.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="pessimistic-approach"&gt;&lt;a class="toclink" href="#pessimistic-approach"&gt;Pessimistic Approach&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The pessimistic approach dictates that you should &lt;strong&gt;lock the resource exclusively until you are finished with it&lt;/strong&gt;. If nobody else can acquire a lock on the object while you are working on it, you can be sure the object was not changed.&lt;/p&gt;
&lt;p&gt;To acquire a lock on a resource we use a &lt;strong&gt;database lock&lt;/strong&gt; for several reasons:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;(relational) &lt;strong&gt;databases are very good at managing locks&lt;/strong&gt; and maintaining consistency.&lt;/li&gt;
&lt;li&gt;The database is the lowest level in which data is accessed - acquiring the lock at the lowest level will &lt;strong&gt;protect the data from other processes&lt;/strong&gt; modifying the data as well. For example, direct updates in the DB, cron jobs, cleanup tasks, etc.&lt;/li&gt;
&lt;li&gt;A Django app &lt;strong&gt;can run on multiple processes&lt;/strong&gt; (e.g workers). Maintaining locks at the app level will require a lot of (unnecessary) work.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id="implementation"&gt;&lt;a class="toclink" href="#implementation"&gt;Implementation&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Let's implement a safe deposit and withdraw actions using a pessimistic approach:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nd"&gt;@classmethod&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;deposit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;cls&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;amount&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="hll"&gt;    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;atomic&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;        &lt;span class="n"&gt;account&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;
&lt;span class="hll"&gt;            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;select_for_update&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&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="n"&gt;account&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;
        &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&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;account&lt;/span&gt;

&lt;span class="nd"&gt;@classmethod&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;withdraw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;cls&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;amount&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="hll"&gt;    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;atomic&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;        &lt;span class="n"&gt;account&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;
&lt;span class="hll"&gt;            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;select_for_update&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InsufficientFunds&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
       &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;
       &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&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;account&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt;We use &lt;code&gt;select_for_update&lt;/code&gt; on our queryset to tell the database to lock the object until the transaction is done.&lt;/li&gt;
&lt;li&gt;Locking a row in the database requires a database transaction. We use Django's decorator &lt;code&gt;transaction.atomic()&lt;/code&gt; to scope the transaction.&lt;/li&gt;
&lt;li&gt;We use a &lt;code&gt;classmethod&lt;/code&gt; instead of an instance method. To acquire the lock we need to tell the database to lock it. To achieve that we need to be the ones fetching the object from the database. When operating on &lt;em&gt;self &lt;/em&gt;the object is already fetched and we don't have any guaranty that it was locked.&lt;/li&gt;
&lt;li&gt;All the operations on the account are executed within the database transaction.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Let's see how the scenario from earlier is prevented with our new implementation:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;User A asks to withdraw 30$:&lt;ul&gt;
&lt;li&gt;User A acquires a lock on the account&lt;/li&gt;
&lt;li&gt;Balance is 100$&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;User B asks to deposit 50$:&lt;ul&gt;
&lt;li&gt;Attempt to acquire lock on account fails (locked by user A)&lt;/li&gt;
&lt;li&gt;User B waits for the lock to release&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;User A withdraw 30$:&lt;ul&gt;
&lt;li&gt;Balance is 70$&lt;/li&gt;
&lt;li&gt;Lock of user A on account is released&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;User B acquires a lock on the account&lt;ul&gt;
&lt;li&gt;Balance is 70$&lt;/li&gt;
&lt;li&gt;New balance is 70$ + 50$ = 120$&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Lock of user B on account is released, balance is 120$. Bug prevented!&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id="what-you-need-to-know-about-select_for_update"&gt;&lt;a class="toclink" href="#what-you-need-to-know-about-select_for_update"&gt;What You Need to Know About &lt;code&gt;select_for_update&lt;/code&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;You dont have to wait for the lock to release&lt;/strong&gt; - In our scenario, user B waited for user A to release the lock. Instead of waiting, we can tell Django not to wait for the lock to release and raise a &lt;code&gt;DatabaseError&lt;/code&gt; instead. To do that, we set &lt;code&gt;select_for_update(nowait=True)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Select related objects are also locked&lt;/strong&gt; - Using &lt;code&gt;select_for_update&lt;/code&gt; with &lt;code&gt;select_related&lt;/code&gt; locks the related objects as well.&lt;br&gt;For example, If we &lt;code&gt;select_related&lt;/code&gt; the user along with the account, both the user and the account are locked. If during deposit someone is trying to update the user's first name, that update will fail because the user object is locked.&lt;br&gt; If you are using PostgreSQL or Oracle this might not be a problem soon, thanks to &lt;a href="https://docs.djangoproject.com/en/dev/ref/models/querysets/#select-for-update" rel="noopener"&gt;a new feature&lt;/a&gt; in the upcoming Django 2.0. In this version, &lt;code&gt;select_for_update&lt;/code&gt; has an &lt;code&gt;of&lt;/code&gt; option to explicitly state which of the tables in the query to lock.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="admonition tip"&gt;
&lt;p class="admonition-title"&gt;See Also&lt;/p&gt;
&lt;p&gt;I used the bank account example in the past to demonstrate common patterns we use in Django models. You are welcome to follow up &lt;a href="bullet-proofing-django-models"&gt;in this article&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;hr&gt;
&lt;h3 id="optimistic-approach"&gt;&lt;a class="toclink" href="#optimistic-approach"&gt;Optimistic Approach&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Unlike the pessimistic approach, the optimistic approach does not require a lock on the object. The optimistic approach assumes collisions are not very common, and dictates that one should only make sure there were no changes made to the object at the time it is updated.&lt;/p&gt;
&lt;h4 id="implementation_1"&gt;&lt;a class="toclink" href="#implementation_1"&gt;Implementation&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;First, we add a column to keep track of changes made to the object:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IntegerField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Then, when we update an object, we make sure the version did not change:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;deposit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;updated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Account&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="hll"&gt;        &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;    &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;balance&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="hll"&gt;        &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&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;updated&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;withdraw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InsufficientFunds&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;updated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Account&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="hll"&gt;        &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;    &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;balance&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="hll"&gt;        &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&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;updated&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Let's break it down:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;We operate directly on the instance (no classmethod).&lt;/li&gt;
&lt;li&gt;We rely on the fact that the version is incremented every time the object is updated.&lt;/li&gt;
&lt;li&gt;We update only if the version did not change:&lt;ul&gt;
&lt;li&gt;If the object was not modified since we fetched it than the object is updated.&lt;/li&gt;
&lt;li&gt;If it was modified than the query will return zero records and the object will not be updated.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Django returns the number of updated rows. If &lt;code&gt;updated&lt;/code&gt; is zero it means someone else changed the object from the time we fetched it.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;How is optimistic locking work in our scenario:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;User A fetch the account:&lt;ul&gt;
&lt;li&gt;balance is 100$&lt;/li&gt;
&lt;li&gt;version is 0&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;User B fetch the account:&lt;ul&gt;
&lt;li&gt;balance is 100$&lt;/li&gt;
&lt;li&gt;version is 0&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;User B asks to withdraw 30$:&lt;ul&gt;
&lt;li&gt;Balance is updated to 100$ - 30$ = 70$&lt;/li&gt;
&lt;li&gt;Version is incremented to 1&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;User A asks to deposit 50$:&lt;ul&gt;
&lt;li&gt;The calculated balance is 100$ + 50$ = 150$&lt;/li&gt;
&lt;li&gt;The account does not exist with version 0 → nothing is updated&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id="what-you-need-to-know-about-the-optimistic-approach"&gt;&lt;a class="toclink" href="#what-you-need-to-know-about-the-optimistic-approach"&gt;What You Need to Know About the Optimistic Approach:&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Unlike the pessimistic approach, this approach requires an &lt;strong&gt;additional field and a lot of discipline&lt;/strong&gt;. One way to overcome the discipline issue is to abstract this behavior. Some packages we've taken inspiration from are:&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/kmmbvnr/django-fsm" rel="noopener"&gt;django-fsm&lt;/a&gt; implements &lt;a href="https://github.com/kmmbvnr/django-fsm/blob/master/django_fsm/__init__.py#L463" rel="noopener"&gt;optimistic locking using a version field&lt;/a&gt; as described above.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/gavinwahl/django-optimistic-lock" rel="noopener"&gt;django-optimistic-lock&lt;/a&gt; seem to do the same.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;In an environment with a lot of concurrent updates this approach might be &lt;strong&gt;wasteful&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;The optimistic approach &lt;strong&gt;does not protect from modifications made to the object outside the app&lt;/strong&gt;. If you have other tasks that modify the data directly (e.g no through the model), you need to make sure they use the version as well.&lt;/li&gt;
&lt;li&gt;Using the optimistic approach, the &lt;strong&gt;function can fail&lt;/strong&gt; and return false. In this case we will most likely want to retry the operation. Using the pessimistic approach with &lt;code&gt;nowait=False&lt;/code&gt; the operation cannot fail, it will wait for the lock to release.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id="which-one-should-i-use"&gt;&lt;a class="toclink" href="#which-one-should-i-use"&gt;Which One Should I Use?&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Like any great question, the answer is &lt;em&gt;"it depends"&lt;/em&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If your object has a lot of concurrent updates you are probably better off with the pessimistic approach.&lt;/li&gt;
&lt;li&gt;If you have updates happening outside the ORM (for example, directly in the database) the pessimistic approach is safer.&lt;/li&gt;
&lt;li&gt;If your method has side effects such as remote API calls or OS calls make sure they are safe. Some things to consider - can the remote call take a long time? Is the remote call idempotent (safe to retry)?&lt;/li&gt;
&lt;/ul&gt;</content><category term="articles"></category><category term="Django"></category><category term="ORM"></category></entry><entry><title>5 Ways to Make Django Admin Safer</title><link href="https://hakibenita.com/5-ways-to-make-django-admin-safer" rel="alternate"></link><published>2017-06-09T00:00:00+03:00</published><updated>2017-06-09T00:00:00+03:00</updated><author><name>Haki Benita</name></author><id>tag:hakibenita.com,2017-06-09:/5-ways-to-make-django-admin-safer</id><summary type="html">&lt;p&gt;With great power comes great responsibility. The more powerful your Django admin is, the safer it should be. Making a Django admin safer and more secure doesn't have to be hard - you just have to pay attention. In this article I present 5 ways to protect the Django Admin from human errors and attackers.&lt;/p&gt;</summary><content type="html">&lt;hr&gt;
&lt;p&gt;In this article I present 5 ways to protect the Django Admin from human errors and attackers.&lt;/p&gt;
&lt;p&gt;&lt;details class="toc-container" open&gt;
    &lt;summary&gt;Table of Contents&lt;/summary&gt;&lt;/p&gt;
&lt;div class="toc"&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#change-the-url"&gt;Change the URL&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#visually-distinguish-environments"&gt;Visually Distinguish Environments&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#name-your-admin-site"&gt;Name Your Admin Site&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#separate-the-django-admin-from-the-main-site"&gt;Separate the Django Admin From The Main Site&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#add-two-factor-authentication-2fa"&gt;Add Two Factor Authentication (2FA)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#final-words"&gt;Final Words&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;p&gt;&lt;/details&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="change-the-url"&gt;&lt;a class="toclink" href="#change-the-url"&gt;Change the URL&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Every framework has a fingerprint and Django is no exception. A skilled developer, an attacker or even a tech savvy user can identify a Django site by looking at things like cookies and auth URLs.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Once a site is identified as a Django site, an attacker will most likely try /admin.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;To make it harder to gain access we can change the "recommended" URL to something harder to guess.&lt;/p&gt;
&lt;p&gt;In the base url.py of the app, &lt;a href="https://docs.djangoproject.com/en/1.11/ref/contrib/admin/#hooking-adminsite-instances-into-your-urlconf" rel="noopener"&gt;register the admin site&lt;/a&gt; under a different url:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;urlpatterns&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;i18n_patterns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;^super-secret/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;site&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;admin&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Change &lt;em&gt;"super-secret"&lt;/em&gt; to something you and your team can remember.&lt;/p&gt;
&lt;p&gt;This is definitely not the only precaution you should take, but it is a good start.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="visually-distinguish-environments"&gt;&lt;a class="toclink" href="#visually-distinguish-environments"&gt;Visually Distinguish Environments&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Users and admins are not perfect and mistakes happen. When you have multiple environments such as development, QA, staging and production, it's not unlikely for an admin to perform a destructive operation in the wrong environment by accident (&lt;a href="https://about.gitlab.com/2017/02/01/gitlab-dot-com-database-incident/" rel="noopener"&gt;just ask gitlab&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;To reduce the chance of mistakes, we mark different environments clearly in the admin:&lt;/p&gt;
&lt;figure&gt;&lt;img alt="Indicator in different environments" src="https://hakibenita.com/images/01-5-ways-to-make-django-admin-safer.png"&gt;&lt;figcaption&gt;Indicator in different environments&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;First you need to have some way of knowing which environment you are on. We have a variable called &lt;code&gt;ENVIRONMENT_NAME&lt;/code&gt; we populate during deployment. We have another variable called &lt;code&gt;ENVIRONMENT_COLOR&lt;/code&gt; for the indicator color.&lt;/p&gt;
&lt;p&gt;To add the environment indicator to every page in the admin, override the base admin template:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="x"&gt;&amp;lt;!-- app/templates/admin/base_site.html --&amp;gt;&lt;/span&gt;

&lt;span class="hll"&gt;&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;extends&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;admin/base_site.html&amp;quot;&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;/span&gt;
&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;block&lt;/span&gt; &lt;span class="nv"&gt;extrastyle&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;

&lt;span class="x"&gt;&amp;lt;style type=&amp;quot;text/css&amp;quot;&amp;gt;&lt;/span&gt;
&lt;span class="x"&gt;    body:before {&lt;/span&gt;
&lt;span class="x"&gt;        display: block;&lt;/span&gt;
&lt;span class="x"&gt;        line-height: 35px;&lt;/span&gt;
&lt;span class="x"&gt;        text-align: center;&lt;/span&gt;
&lt;span class="x"&gt;        font-weight: bold;&lt;/span&gt;
&lt;span class="x"&gt;        text-transform: uppercase;&lt;/span&gt;
&lt;span class="x"&gt;        color: white;&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="x"&gt;        content: &amp;quot;&lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;ENVIRONMENT_NAME&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="x"&gt;&amp;quot;;&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="x"&gt;        background-color: &lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;ENVIRONMENT_COLOR&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="x"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;span class="x"&gt;    }&lt;/span&gt;
&lt;span class="x"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;

&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;endblock&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;To make the ENVIRONMENT variables from &lt;code&gt;settings.py&lt;/code&gt; available in the template we use a context processor:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# app/context_processors.py&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.conf&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;from_settings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;ENVIRONMENT_NAME&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ENVIRONMENT_NAME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;ENVIRONMENT_COLOR&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ENVIRONMENT_COLOR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;To register the context processor add the following in &lt;code&gt;settings.py&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;TEMPLATES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
    &lt;span class="c1"&gt;# ...&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;OPTIONS&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;context_processors&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="c1"&gt;# ...&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;app.context_processors.from_settings&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="c1"&gt;# ...&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Now when you open Django Admin you should see the indicator on top.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="name-your-admin-site"&gt;&lt;a class="toclink" href="#name-your-admin-site"&gt;Name Your Admin Site&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If you have multiple Django services that look the same, admins can easily get confused. To help admins be more aware of where they are, change the title:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# urls.py&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.contrib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt;

&lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;site&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;site_header&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Awesome Inc. Administration&amp;#39;&lt;/span&gt;
&lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;site&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;site_title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Awesome Inc. Administration&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;And you get:&lt;/p&gt;
&lt;figure&gt;&lt;img alt="Django Admin site with a name and a title" src="https://hakibenita.com/images/02-5-ways-to-make-django-admin-safer.png"&gt;&lt;figcaption&gt;Django Admin site with a name and a title&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;For more exotic options look around &lt;a href="https://docs.djangoproject.com/en/1.11/ref/contrib/admin/#adminsite-attributes" rel="noopener"&gt;in the docs&lt;/a&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="separate-the-django-admin-from-the-main-site"&gt;&lt;a class="toclink" href="#separate-the-django-admin-from-the-main-site"&gt;Separate the Django Admin From The Main Site&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Using the same codebase you can deploy two instances of the same Django app - one only for the admin and one only for the rest of the app.&lt;/p&gt;
&lt;p&gt;This is controversial and not as easy as the other tips. The implementation is dependent on the configuration (e.g. if you are using gunicorn or uwsgi) so I won't go into the details.&lt;/p&gt;
&lt;p&gt;Some reasons you might want to split the admin to its own instance are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Deploy the admin inside a VPN (virtual private network)&lt;/strong&gt; - If the admin is used only internally and you have a VPN it is good practice to have it inside the private network.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Remove unnecessary components from the main site&lt;/strong&gt; - For example, the Django admin uses the messages framework. If the main site does not, you can remove that middleware. Another example is authentication - if the main site is an API backend using token authentication, you can remove a lot of templates configuration, session middleware, etc. and trim some fat from the request-response cycle.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Stronger authentication&lt;/strong&gt; - If you want to strengthen the security of Django Admin you might want to provide a different authentication mechanism just for the admin. This is much easier on different instances with different settings.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We split the admin from the main site only in public facing sites. We don't bother with internal apps because it complicates the deployment and it has no benefit of being more secure.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="add-two-factor-authentication-2fa"&gt;&lt;a class="toclink" href="#add-two-factor-authentication-2fa"&gt;Add Two Factor Authentication (2FA)&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Two factor authentication became very popular lately as many sites started offering this option. 2FA performs authentication using two things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Something you know&lt;/strong&gt; - Usually a password.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Something you have&lt;/strong&gt; - Usually a mobile app that generates a random number every 30 seconds (such as &lt;a href="https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2&amp;amp;hl=en" rel="noopener"&gt;Authenticator by Google&lt;/a&gt;).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;On first signup the user is usually asked to scan a barcode with the authenticator app. After this initial setup the app will start generating the one-time codes.&lt;/p&gt;
&lt;p&gt;I don't usually recommend third party packages, but a couple of months ago we started using &lt;a href="https://pypi.python.org/pypi/django-otp" rel="noopener"&gt;django-otp &lt;/a&gt;to implement 2FA in our admin site and it's working great for us. It's hosted on Bitbucket so you might have missed it.&lt;/p&gt;
&lt;p&gt;The setup is pretty simple:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;django-otp
$&lt;span class="w"&gt; &lt;/span&gt;pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;qrcode
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Add django-otp to the installed apps and the middleware:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# settings.py&lt;/span&gt;

&lt;span class="n"&gt;INSTALLED_APPS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;django_otp&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;django_otp.plugins.otp_totp&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;MIDDLEWARE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;django.contrib.auth.middleware.AuthenticationMiddleware&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;django_otp.middleware.OTPMiddleware&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Name the issuer - this is the name users will see in the authenticator app, so make it distinguishable.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;# settings.py

OTP_TOTP_ISSUER = &amp;#39;Awesome Inc.&amp;#39;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Add 2FA authentication to the admin site:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;# urls.py

from django_otp.admin import OTPAdminSite

admin.site.__class__ = OTPAdminSite
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Now you have a secure admin page that looks like this:&lt;/p&gt;
&lt;figure&gt;&lt;img alt="Django Admin login with OTP token" src="https://hakibenita.com/images/03-5-ways-to-make-django-admin-safer.png"&gt;&lt;figcaption&gt;Django Admin login with OTP token&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;To set up a new user create a "TOTP Device" from the Django Admin. Once you are done click the QR link and you will get a screen like that:&lt;/p&gt;
&lt;figure&gt;&lt;img alt="QR for setting up a new user" src="https://hakibenita.com/images/04-5-ways-to-make-django-admin-safer.png"&gt;&lt;figcaption&gt;QR for setting up a new user&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Have the user scan the QR code with the authenticator app on their personal device, and they will have a fresh code generated every 30 seconds.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="final-words"&gt;&lt;a class="toclink" href="#final-words"&gt;Final Words&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Making a Django admin safer and more secure doesn't have to be hard - you just have to pay attention. Some of the tips mentioned here are very easy to set up and they go a long way.&lt;/p&gt;</content><category term="articles"></category><category term="Django"></category><category term="Django Admin"></category></entry><entry><title>All You Need To Know About Prefetching in Django</title><link href="https://hakibenita.com/all-you-need-to-know-about-prefetching-in-django" rel="alternate"></link><published>2017-04-29T00:00:00+03:00</published><updated>2017-04-29T00:00:00+03:00</updated><author><name>Haki Benita</name></author><id>tag:hakibenita.com,2017-04-29:/all-you-need-to-know-about-prefetching-in-django</id><summary type="html">&lt;p&gt;A rundown of all the ways you can use Prefetch to speed up queries in Django.&lt;/p&gt;</summary><content type="html">&lt;hr&gt;
&lt;p&gt;I have recently worked on a ticket ordering system for a conference. It was very important for the customer to see a table of orders including a column with a list of program names in each order:&lt;/p&gt;
&lt;figure&gt;&lt;img alt="The column requested by the users" src="https://hakibenita.com/images/01-all-you-need-to-know-about-prefetching-in-django.png"&gt;&lt;figcaption&gt;The column requested by the users&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The models looked (roughly) like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Program&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&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;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&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;20&lt;/span&gt;&lt;span class="p"&gt;,&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;Price&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&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;program&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;Program&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;from_date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DateTimeField&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;to_date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DateTimeField&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;Order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&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;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&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;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ManyToManyField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Price&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Program&lt;/strong&gt; - a session, lecture or a conference day.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Price&lt;/strong&gt; - Prices can change over time. One way to model changes over time is using a &lt;a href="https://en.wikipedia.org/wiki/Slowly_changing_dimension#Type_2:_add_new_row" rel="noopener"&gt;type 2 slowly changing dimension&lt;/a&gt; (SCD). The &lt;code&gt;Price&lt;/code&gt; model represents the price of a program at a certain point in time.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Order&lt;/strong&gt; - An order to one or more programs. Each item in the order is the price of the program at the time the order was made.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="before-we-start"&gt;&lt;a class="toclink" href="#before-we-start"&gt;Before We Start&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Throughout this article we are going to monitor the queries executed by Django.
To log the queries add the following to the &lt;code&gt;LOGGING&lt;/code&gt; settings in &lt;code&gt;settings.py&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;LOGGING&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;# ...&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;loggers&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;django.db.backends&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;level&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;DEBUG&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;hr&gt;
&lt;h3 id="whats-the-problem"&gt;&lt;a class="toclink" href="#whats-the-problem"&gt;What's The Problem?&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Let's try to fetch the program names for a single order:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;completed&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;
&lt;span class="go"&gt;(0.002) SELECT ... FROM &amp;quot;orders_order&amp;quot;&lt;/span&gt;
&lt;span class="go"&gt;WHERE &amp;quot;orders_order&amp;quot;.&amp;quot;state&amp;quot; = &amp;#39;completed&amp;#39;&lt;/span&gt;
&lt;span class="go"&gt;ORDER BY &amp;quot;orders_order&amp;quot;.&amp;quot;id&amp;quot; ASC LIMIT 1;&lt;/span&gt;
&lt;span class="hll"&gt;
&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;program&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;

&lt;span class="go"&gt;(0.002) SELECT ... FROM &amp;quot;events_price&amp;quot;&lt;/span&gt;
&lt;span class="go"&gt;INNER JOIN &amp;quot;orders_order_items&amp;quot; ON (&amp;quot;events_price&amp;quot;.&amp;quot;id&amp;quot; = &amp;quot;orders_order_items&amp;quot;.&amp;quot;price_id&amp;quot;)&lt;/span&gt;
&lt;span class="go"&gt;WHERE &amp;quot;orders_order_items&amp;quot;.&amp;quot;order_id&amp;quot; = 29; args=(29,)&lt;/span&gt;

&lt;span class="go"&gt;(0.001) SELECT ... FROM &amp;quot;events_program&amp;quot;&lt;/span&gt;
&lt;span class="go"&gt;WHERE &amp;quot;events_program&amp;quot;.&amp;quot;id&amp;quot; = 8; args=(8,)&lt;/span&gt;

&lt;span class="go"&gt;[&amp;#39;Day 1 Pass&amp;#39;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;To fetch completed orders we need &lt;strong&gt;one query&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;To fetch the program names for each order we need &lt;strong&gt;two more queries&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I previously &lt;a href="/things-you-must-know-about-django-admin-as-your-app-gets-bigger"&gt;wrote about the N+1 problem&lt;/a&gt; and this is a classic case. If we need two queries for each order, &lt;strong&gt;the number of queries for 100 orders will be 1 + 100 * 2 = 201 queries&lt;/strong&gt;, that's a lot!&lt;/p&gt;
&lt;p&gt;Let's use Django to reduce the amount of queries:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;values_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;program__name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="go"&gt;(0.003) SELECT &amp;quot;events_program&amp;quot;.&amp;quot;name&amp;quot; FROM &amp;quot;events_price&amp;quot;&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="go"&gt;INNER JOIN &amp;quot;orders_order_items&amp;quot; ON (&amp;quot;events_price&amp;quot;.&amp;quot;id&amp;quot; = &amp;quot;orders_order_items&amp;quot;.&amp;quot;price_id&amp;quot;)&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="go"&gt;INNER JOIN &amp;quot;events_program&amp;quot; ON (&amp;quot;events_price&amp;quot;.&amp;quot;program_id&amp;quot; = &amp;quot;events_program&amp;quot;.&amp;quot;id&amp;quot;)&lt;/span&gt;
&lt;/span&gt;&lt;span class="go"&gt;WHERE &amp;quot;orders_order_items&amp;quot;.&amp;quot;order_id&amp;quot; = 29 LIMIT 21;&lt;/span&gt;

&lt;span class="go"&gt;[&amp;#39;Day 1 Pass&amp;#39;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Great!&lt;/strong&gt; Django performed a join between &lt;code&gt;Price&lt;/code&gt; and &lt;code&gt;Program&lt;/code&gt; and reduced the amount of queries to just one per order.&lt;/p&gt;
&lt;p&gt;At this point instead of 201 queries we only need 101 queries for 100 orders. Can we do better?&lt;/p&gt;
&lt;h3 id="why-cant-we-join"&gt;&lt;a class="toclink" href="#why-cant-we-join"&gt;Why Can't We Join?&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The first question that should come to mind is &lt;em&gt;"why can't we join the tables?"&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;If we have a foreign key we can use &lt;code&gt;select_related&lt;/code&gt; or use snake case like we did above to fetch the related fields in a single query.&lt;/p&gt;
&lt;p&gt;For example, we fetched the program name for a list of prices in a single query using &lt;code&gt;values_list('program__name')&lt;/code&gt;&lt;strong&gt;. &lt;/strong&gt;We were able to do that because each price is related to exactly one program.&lt;/p&gt;
&lt;p&gt;If the relation between two models is many to many we can't do that. Every order has one or more related prices - if we join the two tables we get duplicate orders:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;price_id&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;orders_order&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;JOIN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;orders_order_items&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;JOIN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;events_price&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;op&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;price_id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;price_id&lt;/span&gt;
&lt;span class="c1"&gt;----------+----------&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="mi"&gt;45&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="mi"&gt;38&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="mi"&gt;45&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="mi"&gt;56&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="mi"&gt;70&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="mi"&gt;38&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="mi"&gt;70&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="mi"&gt;70&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="mi"&gt;77&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="mi"&gt;71&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="mi"&gt;38&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Orders 70 and 45 have multiple items so they come up more than once in the result - &lt;strong&gt;Django can't handle that.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="enter-prefetch_related"&gt;&lt;a class="toclink" href="#enter-prefetch_related"&gt;Enter &lt;code&gt;prefetch_related&lt;/code&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Django has a nice, built-in way, of dealing with this problem called &lt;a href="https://docs.djangoproject.com/en/1.10/ref/models/querysets/#prefetch-related" rel="noopener"&gt;&lt;code&gt;prefetch_related&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="gp"&gt;... &lt;/span&gt;    &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;completed&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="gp"&gt;... &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prefetch_related&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="gp"&gt;... &lt;/span&gt;    &lt;span class="s1"&gt;&amp;#39;items__program&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;span class="gp"&gt;... &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="go"&gt;(0.002) SELECT ... FROM &amp;quot;orders_order&amp;quot;&lt;/span&gt;
&lt;span class="go"&gt;WHERE &amp;quot;orders_order&amp;quot;.&amp;quot;state&amp;quot; = &amp;#39;completed&amp;#39;&lt;/span&gt;
&lt;span class="go"&gt;ORDER BY &amp;quot;orders_order&amp;quot;.&amp;quot;id&amp;quot; ASC LIMIT 1;&lt;/span&gt;

&lt;span class="go"&gt;(0.001) SELECT (&amp;quot;orders_order_items&amp;quot;.&amp;quot;order_id&amp;quot;) AS &amp;quot;_prefetch_related_val_order_id&amp;quot;, &amp;quot;events_price&amp;quot;...&lt;/span&gt;
&lt;span class="go"&gt;FROM &amp;quot;events_price&amp;quot;&lt;/span&gt;
&lt;span class="go"&gt;INNER JOIN &amp;quot;orders_order_items&amp;quot; ON (&amp;quot;events_price&amp;quot;.&amp;quot;id&amp;quot; = &amp;quot;orders_order_items&amp;quot;.&amp;quot;price_id&amp;quot;)&lt;/span&gt;
&lt;span class="go"&gt;WHERE &amp;quot;orders_order_items&amp;quot;.&amp;quot;order_id&amp;quot; IN (29);&lt;/span&gt;

&lt;span class="go"&gt;(0.001) SELECT &amp;quot;events_program&amp;quot;.&amp;quot;id&amp;quot;, &amp;quot;events_program&amp;quot;.&amp;quot;name&amp;quot; FROM &amp;quot;events_program&amp;quot;&lt;/span&gt;
&lt;span class="go"&gt;WHERE &amp;quot;events_program&amp;quot;.&amp;quot;id&amp;quot; IN (8);&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We told Django we intend to fetch &lt;code&gt;items__program&lt;/code&gt; from the result set. In the second and third query we can see that Django fetched the through table &lt;code&gt;orders_order_items&lt;/code&gt; and the relevant programs from &lt;code&gt;events_program&lt;/code&gt;. &lt;strong&gt;The results of the prefetch are cached on the objects&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;What happens when we try to fetch program names from the result?&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;program&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;

&lt;span class="go"&gt;[&amp;#39;Day 1 Pass&amp;#39;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;No additional queries&lt;/strong&gt; - exactly what we wanted!&lt;/p&gt;
&lt;p&gt;When using prefetch, it's important to &lt;strong&gt;work on the object and not on the query&lt;/strong&gt;. Trying to fetch the program names with a query will produce the same outcome but will result in an additional query:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;values_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;program__name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="go"&gt;(0.002) SELECT &amp;quot;events_program&amp;quot;.&amp;quot;name&amp;quot; FROM &amp;quot;events_price&amp;quot;&lt;/span&gt;
&lt;span class="go"&gt;INNER JOIN &amp;quot;orders_order_items&amp;quot; ON (&amp;quot;events_price&amp;quot;.&amp;quot;id&amp;quot; = &amp;quot;orders_order_items&amp;quot;.&amp;quot;price_id&amp;quot;)&lt;/span&gt;
&lt;span class="go"&gt;INNER JOIN &amp;quot;events_program&amp;quot; ON (&amp;quot;events_price&amp;quot;.&amp;quot;program_id&amp;quot; = &amp;quot;events_program&amp;quot;.&amp;quot;id&amp;quot;)&lt;/span&gt;
&lt;span class="go"&gt;WHERE &amp;quot;orders_order_items&amp;quot;.&amp;quot;order_id&amp;quot; = 29 LIMIT 21;&lt;/span&gt;

&lt;span class="go"&gt;[&amp;#39;Day 1 Pass&amp;#39;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;At this point, &lt;strong&gt;fetching 100 orders requires only 3 queries&lt;/strong&gt;. Can we do even better?&lt;/p&gt;
&lt;h3 id="introducing-prefetch"&gt;&lt;a class="toclink" href="#introducing-prefetch"&gt;Introducing &lt;code&gt;Prefetch&lt;/code&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;In version 1.7 Django introduced a new &lt;a href="https://docs.djangoproject.com/en/1.11/ref/models/querysets/#django.db.models.Prefetch" rel="noopener"&gt;&lt;code&gt;Prefetch&lt;/code&gt; object&lt;/a&gt; that extends the capabilities of &lt;code&gt;prefetch_related&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The new object allows the developer to override the query used by Django to prefetch the related objects.&lt;/p&gt;
&lt;p&gt;In our previous example Django used two queries for the prefetch - one for the through table and one for the program table. What if we could tell Django to join these two together?&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;prices_and_programs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Price&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;select_related&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;program&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="gp"&gt;... &lt;/span&gt;    &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;completed&amp;#39;&lt;/span&gt;
&lt;span class="gp"&gt;... &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prefetch_related&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="gp"&gt;... &lt;/span&gt;    &lt;span class="n"&gt;Prefetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;items&amp;#39;&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;prices_and_programs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;span class="gp"&gt;... &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="go"&gt;(0.001) SELECT ... FROM &amp;quot;orders_order&amp;quot;&lt;/span&gt;
&lt;span class="go"&gt;WHERE &amp;quot;orders_order&amp;quot;.&amp;quot;state&amp;quot; = &amp;#39;completed&amp;#39;&lt;/span&gt;
&lt;span class="go"&gt;ORDER BY &amp;quot;orders_order&amp;quot;.&amp;quot;id&amp;quot; ASC LIMIT 1;&lt;/span&gt;

&lt;span class="go"&gt;(0.001) SELECT (&amp;quot;orders_order_items&amp;quot;.&amp;quot;order_id&amp;quot;) AS &amp;quot;_prefetch_related_val_order_id&amp;quot;,&lt;/span&gt;
&lt;span class="go"&gt;&amp;quot;events_price&amp;quot;..., &amp;quot;events_program&amp;quot;...&lt;/span&gt;
&lt;span class="go"&gt;INNER JOIN &amp;quot;events_program&amp;quot; ON (&amp;quot;events_price&amp;quot;.&amp;quot;program_id&amp;quot; = &amp;quot;events_program&amp;quot;.&amp;quot;id&amp;quot;)&lt;/span&gt;
&lt;span class="go"&gt;WHERE &amp;quot;orders_order_items&amp;quot;.&amp;quot;order_id&amp;quot; IN (29);&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We created a query that joins prices with programs. Than we told Django to use this query to prefetch the values. This is like telling Django that you intend to fetch both items and programs for each order.&lt;/p&gt;
&lt;p&gt;Fetching program names for an order:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;program&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;all&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;

&lt;span class="go"&gt;[&amp;#39;Day 1 Pass&amp;#39;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;No additional queries&lt;/strong&gt; - it worked!&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="taking-it-to-the-next-level"&gt;&lt;a class="toclink" href="#taking-it-to-the-next-level"&gt;Taking It To The Next Level&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;When we talked earlier about the models we mentioned that the prices are modeled as an SCD table. This means we might want to query only active prices at a certain date.&lt;/p&gt;
&lt;p&gt;A price is active at a certain date if it's between &lt;code&gt;from_date&lt;/code&gt; and &lt;code&gt;end_date&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.utils&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;timezone&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;active_prices&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Price&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="gp"&gt;... &lt;/span&gt;    &lt;span class="n"&gt;from_date__lte&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="gp"&gt;... &lt;/span&gt;    &lt;span class="n"&gt;to_date__gt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="gp"&gt;... &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Using the Prefetch object we can tell Django to store the prefetched objects in
a new attribute of the result set:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.utils&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;timezone&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;active_prices_and_programs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="gp"&gt;... &lt;/span&gt;    &lt;span class="n"&gt;Price&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="gp"&gt;... &lt;/span&gt;        &lt;span class="n"&gt;from_date__lte&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="gp"&gt;... &lt;/span&gt;        &lt;span class="n"&gt;to_date__gt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="gp"&gt;... &lt;/span&gt;    &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;select_related&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;program&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;... &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="gp"&gt;... &lt;/span&gt;    &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;completed&amp;#39;&lt;/span&gt;
&lt;span class="gp"&gt;... &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prefetch_related&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="gp"&gt;... &lt;/span&gt;    &lt;span class="n"&gt;Prefetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="gp"&gt;... &lt;/span&gt;        &lt;span class="s1"&gt;&amp;#39;items&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="gp"&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;active_prices_and_programs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="gp"&gt;... &lt;/span&gt;        &lt;span class="n"&gt;to_attr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;active_prices&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="gp"&gt;... &lt;/span&gt;    &lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;span class="gp"&gt;... &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="go"&gt;(0.001) SELECT ... FROM &amp;quot;orders_order&amp;quot;&lt;/span&gt;
&lt;span class="go"&gt;WHERE &amp;quot;orders_order&amp;quot;.&amp;quot;state&amp;quot; = &amp;#39;completed&amp;#39;&lt;/span&gt;
&lt;span class="go"&gt;ORDER BY &amp;quot;orders_order&amp;quot;.&amp;quot;id&amp;quot; ASC&lt;/span&gt;
&lt;span class="go"&gt;LIMIT 1;&lt;/span&gt;

&lt;span class="go"&gt;(0.001) SELECT ... FROM &amp;quot;events_price&amp;quot;&lt;/span&gt;
&lt;span class="go"&gt;INNER JOIN &amp;quot;orders_order_items&amp;quot; ON (&amp;quot;events_price&amp;quot;.&amp;quot;id&amp;quot; = &amp;quot;orders_order_items&amp;quot;.&amp;quot;price_id&amp;quot;)&lt;/span&gt;
&lt;span class="go"&gt;INNER JOIN &amp;quot;events_program&amp;quot; ON (&amp;quot;events_price&amp;quot;.&amp;quot;program_id&amp;quot; = &amp;quot;events_program&amp;quot;.&amp;quot;id&amp;quot;)&lt;/span&gt;
&lt;span class="go"&gt;WHERE (&amp;quot;orders_order_items&amp;quot;.&amp;quot;order_id&amp;quot; IN (29)&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="go"&gt;AND &amp;quot;events_price&amp;quot;.&amp;quot;from_date&amp;quot; &amp;lt;= &amp;#39;2017–04–29T07:53:00.210537+00:00&amp;#39;::timestamptz&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="go"&gt;AND &amp;quot;events_price&amp;quot;.&amp;quot;to_date&amp;quot; &amp;gt; &amp;#39;2017–04–29T07:53:00.210537+00:00&amp;#39;::timestamptz);&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We can see in the log that Django performed only two queries, and the prefetch query now include the custom filter we defined.&lt;/p&gt;
&lt;p&gt;To fetch the active prices we can use the new attribute defined in &lt;code&gt;to_attr&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;program&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;active_prices&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="go"&gt;[&amp;#39;Day 1 Pass&amp;#39;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;No additional query!&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="final-words"&gt;&lt;a class="toclink" href="#final-words"&gt;Final Words&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Prefetch is a very powerful feature of Django ORM. I strongly recommend going over &lt;a href="https://docs.djangoproject.com/en/2.1/ref/models/querysets/#prefetch-related" rel="noopener"&gt;the documentation&lt;/a&gt;, you are bound to strike a gem.&lt;/p&gt;</content><category term="articles"></category><category term="Django"></category><category term="ORM"></category><category term="Performance"></category></entry><entry><title>How to Turn Django Admin Into a Lightweight Dashboard</title><link href="https://hakibenita.com/how-to-turn-django-admin-into-a-lightweight-dashboard" rel="alternate"></link><published>2017-03-31T00:00:00+03:00</published><updated>2017-03-31T00:00:00+03:00</updated><author><name>Haki Benita</name></author><id>tag:hakibenita.com,2017-03-31:/how-to-turn-django-admin-into-a-lightweight-dashboard</id><summary type="html">&lt;p&gt;Django Admin is a powerful tool for managing data in your app. However, it was not designed with summary tables and charts in mind. Luckily, the developers of Django Admin made it easy for us to customize. We are going to turn Django Admin into a dashboard by adding a chart and a summary table.&lt;/p&gt;</summary><content type="html">&lt;hr&gt;
&lt;div class="admonition info"&gt;
&lt;p class="admonition-title"&gt;source code&lt;/p&gt;
&lt;p&gt;The complete source code for this article can be found in &lt;a href="https://gist.github.com/hakib/ec462baef03a6146654e4c095142b5eb" rel="noopener"&gt;this gist&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Django Admin is a powerful tool for managing data in your app. However, it was not designed with summary tables and charts in mind. Luckily, the developers of Django Admin made it easy for us to customize.&lt;/p&gt;
&lt;p&gt;This is what it's going to look like at the end:&lt;/p&gt;
&lt;figure&gt;&lt;img alt="Django admin dashboard" src="https://hakibenita.com/images/01-how-to-turn-django-admin-into-a-lightweight-dashboard.png"&gt;&lt;figcaption&gt;Django admin dashboard&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="why-would-i-want-to-do-that"&gt;&lt;a class="toclink" href="#why-would-i-want-to-do-that"&gt;Why Would I Want To Do That&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;There are a lot of tools, apps and packages out there that can produce very nice looking dashboards. I personally found that &lt;strong&gt;unless the product is an actual dashboard&lt;/strong&gt;, most of the time all you need is a simple summary table and a few charts.&lt;/p&gt;
&lt;p&gt;Second, and just as important - no dependencies.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;If all you need is a little boost to your admin interface this approach is definitely worth considering.&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id="setup"&gt;&lt;a class="toclink" href="#setup"&gt;Setup&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;We are going to use a made up &lt;code&gt;Sale&lt;/code&gt; model.&lt;/p&gt;
&lt;p&gt;To harness the full power of Django Admin we are going to &lt;strong&gt;base our dashboard on a the built-in ModelAdmin&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;To do that we need a model:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# models.py&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SaleSummary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Sale&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="hll"&gt;        &lt;span class="n"&gt;proxy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;
&lt;/span&gt;        &lt;span class="n"&gt;verbose_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Sale Summary&amp;#39;&lt;/span&gt;
        &lt;span class="n"&gt;verbose_name_plural&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Sales Summary&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;A &lt;a href="https://docs.djangoproject.com/en/1.10/topics/db/models/#proxy-models" rel="noopener"&gt;proxy model&lt;/a&gt; extends the functionality of another model without creating an actual table in the database.&lt;/p&gt;
&lt;p&gt;Now that we have a model we can create the &lt;code&gt;ModelAdmin&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# admin.py&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.contrib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SaleSummary&lt;/span&gt;

&lt;span class="nd"&gt;@admin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SaleSummary&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;SaleSummaryAdmin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ModelAdmin&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;change_list_template&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;admin/sale_summary_change_list.html&amp;#39;&lt;/span&gt;
    &lt;span class="n"&gt;date_hierarchy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;created&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Because we are using a standard &lt;code&gt;ModelAdmin&lt;/code&gt; we can use its features. In this example I added a &lt;code&gt;date_hierarchy&lt;/code&gt; to filter sales by creation date. We are going to use this later for the chart.&lt;/p&gt;
&lt;p&gt;To keep the page looking like a "regular" admin page we extend Django's &lt;code&gt;change_list&lt;/code&gt; template and place our content in the &lt;code&gt;result_list&lt;/code&gt; block:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="x"&gt;&amp;lt;!-- sales/templates/admin/sale_summary_change_list.html --&amp;gt;&lt;/span&gt;

&lt;span class="hll"&gt;&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;extends&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;admin/change_list.html&amp;quot;&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;/span&gt;
&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;block&lt;/span&gt; &lt;span class="nv"&gt;content_title&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;span class="x"&gt;    &amp;lt;h1&amp;gt; Sales Summary &amp;lt;/h1&amp;gt;&lt;/span&gt;
&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;endblock&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;

&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;block&lt;/span&gt; &lt;span class="nv"&gt;result_list&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="x"&gt;    &amp;lt;!-- Our content goes here... --&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;endblock&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;

&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;block&lt;/span&gt; &lt;span class="nv"&gt;pagination&lt;/span&gt; &lt;span class="cp"&gt;%}{%&lt;/span&gt; &lt;span class="k"&gt;endblock&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This is what our page looks like at this point:&lt;/p&gt;
&lt;figure&gt;&lt;img alt="A bare Django admin dashboard" src="https://hakibenita.com/images/02-how-to-turn-django-admin-into-a-lightweight-dashboard.png"&gt;&lt;figcaption&gt;A bare Django admin dashboard&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="adding-a-summary-table"&gt;&lt;a class="toclink" href="#adding-a-summary-table"&gt;Adding a Summary Table&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The context sent to the template is populated by the &lt;code&gt;ModelAdmin&lt;/code&gt; in a function called &lt;code&gt;changelist_view&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;To render the table in the template we fetch the data in &lt;code&gt;changelist_view&lt;/code&gt; and add it to the context:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# admin.py&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SaleSummaryAdmin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ModelAdmin&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="c1"&gt;# ...&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;changelist_view&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;extra_context&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;changelist_view&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;extra_context&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;extra_context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="hll"&gt;            &lt;span class="n"&gt;qs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;context_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;cl&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;queryset&lt;/span&gt;
&lt;/span&gt;        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ne"&gt;AttributeError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ne"&gt;KeyError&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;

        &lt;span class="n"&gt;metrics&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;total&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;total_sales&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;price&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="hll"&gt;        &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;context_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;summary&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;            &lt;span class="n"&gt;qs&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;sale__category__name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;annotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;order_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;-total_sales&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Let's break it down:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Call super to let Django do its thing (populate headers, breadcrumbs, queryset, filters and so on).&lt;/li&gt;
&lt;li&gt;Extract the queryset created for us from the context. At this point the query is filtered with any inline filters or date hierarchy selected by the user.&lt;/li&gt;
&lt;li&gt;If we can't fetch the queryset from the context it's most likely due to invalid query parameters. In cases like this Django will redirect so we don't interfere and return the response.&lt;/li&gt;
&lt;li&gt;Aggregate total sales by category and return a list (the "metrics" dict will become clear in the next section).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now that we have the data in the context we can render it in the template:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="x"&gt;&amp;lt;!-- sale_summary_change_list.html --&amp;gt;&lt;/span&gt;

&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;load&lt;/span&gt; &lt;span class="nv"&gt;humanize&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;

&lt;span class="x"&gt;&amp;lt;!-- ... --&amp;gt;&lt;/span&gt;

&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;block&lt;/span&gt; &lt;span class="nv"&gt;result_list&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;

&lt;span class="x"&gt;&amp;lt;div class=&amp;quot;results&amp;quot;&amp;gt;&lt;/span&gt;
&lt;span class="x"&gt;    &amp;lt;table&amp;gt;&lt;/span&gt;

&lt;span class="x"&gt;    &amp;lt;thead&amp;gt;&lt;/span&gt;
&lt;span class="x"&gt;      &amp;lt;tr&amp;gt;&lt;/span&gt;
&lt;span class="x"&gt;        &amp;lt;th&amp;gt;&lt;/span&gt;
&lt;span class="x"&gt;          &amp;lt;div class=&amp;quot;text&amp;quot;&amp;gt;&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="x"&gt;            &amp;lt;a href=&amp;quot;#&amp;quot;&amp;gt;Category&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;span class="x"&gt;          &amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="x"&gt;        &amp;lt;/th&amp;gt;&lt;/span&gt;
&lt;span class="x"&gt;        &amp;lt;th&amp;gt;&lt;/span&gt;
&lt;span class="x"&gt;          &amp;lt;div class=&amp;quot;text&amp;quot;&amp;gt;&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="x"&gt;            &amp;lt;a href=&amp;quot;#&amp;quot;&amp;gt;Total&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;span class="x"&gt;          &amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="x"&gt;        &amp;lt;/th&amp;gt;&lt;/span&gt;
&lt;span class="x"&gt;        &amp;lt;th&amp;gt;&lt;/span&gt;
&lt;span class="x"&gt;          &amp;lt;div class=&amp;quot;text&amp;quot;&amp;gt;&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="x"&gt;            &amp;lt;a href=&amp;quot;#&amp;quot;&amp;gt;Total Sales&amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;span class="x"&gt;          &amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="x"&gt;        &amp;lt;/th&amp;gt;&lt;/span&gt;
&lt;span class="x"&gt;        &amp;lt;th&amp;gt;&lt;/span&gt;
&lt;span class="x"&gt;          &amp;lt;div class=&amp;quot;text&amp;quot;&amp;gt;&lt;/span&gt;
&lt;span class="x"&gt;            &amp;lt;a href=&amp;quot;#&amp;quot;&amp;gt;&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="x"&gt;              &amp;lt;strong&amp;gt;% Of Total Sales&amp;lt;/strong&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;span class="x"&gt;            &amp;lt;/a&amp;gt;&lt;/span&gt;
&lt;span class="x"&gt;          &amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="x"&gt;        &amp;lt;/th&amp;gt;&lt;/span&gt;
&lt;span class="x"&gt;      &amp;lt;/tr&amp;gt;&lt;/span&gt;
&lt;span class="x"&gt;    &amp;lt;/thead&amp;gt;&lt;/span&gt;

&lt;span class="x"&gt;    &amp;lt;tbody&amp;gt;&lt;/span&gt;
&lt;span class="x"&gt;      &lt;/span&gt;&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;row&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nv"&gt;summary&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="x"&gt;      &amp;lt;tr class=&amp;quot;&lt;/span&gt;&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;cycle&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;row1&amp;#39;&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;row2&amp;#39;&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;&lt;span class="x"&gt;&amp;quot;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;span class="x"&gt;        &amp;lt;td&amp;gt; &lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;row.sale__category__name&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="x"&gt; &amp;lt;/td&amp;gt;&lt;/span&gt;
&lt;span class="x"&gt;        &amp;lt;td&amp;gt; &lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;row.total&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nf"&gt;intcomma&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="x"&gt; &amp;lt;/td&amp;gt;&lt;/span&gt;
&lt;span class="x"&gt;        &amp;lt;td&amp;gt; &lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;row.total_sales&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nf"&gt;intcomma&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="x"&gt;$ &amp;lt;/td&amp;gt;&lt;/span&gt;
&lt;span class="x"&gt;        &amp;lt;td&amp;gt;&lt;/span&gt;
&lt;span class="x"&gt;          &amp;lt;strong&amp;gt;&lt;/span&gt;
&lt;span class="x"&gt;          &lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;row.total_sales&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
              &lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;
              &lt;span class="nf"&gt;percentof&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;summary_total.total_sales&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;span class="x"&gt;          &amp;lt;/strong&amp;gt;&lt;/span&gt;
&lt;span class="x"&gt;        &amp;lt;/td&amp;gt;&lt;/span&gt;
&lt;span class="x"&gt;      &amp;lt;/tr&amp;gt;&lt;/span&gt;
&lt;span class="x"&gt;      &lt;/span&gt;&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;endfor&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;span class="x"&gt;    &amp;lt;/tbody&amp;gt;&lt;/span&gt;

&lt;span class="x"&gt;  &amp;lt;/table&amp;gt;&lt;/span&gt;
&lt;span class="x"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;span class="x"&gt;&amp;lt;!-- ... --&amp;gt;&lt;/span&gt;

&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;endblock&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;The markup is important&lt;/strong&gt;. To get the native Django look we need to render tables in the same way Django renders them.&lt;/p&gt;
&lt;p&gt;This is what we have so far:&lt;/p&gt;
&lt;figure&gt;&lt;img alt="Django admin dashboard with just a table" src="https://hakibenita.com/images/03-how-to-turn-django-admin-into-a-lightweight-dashboard.png"&gt;&lt;figcaption&gt;Django admin dashboard with just a table&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;A summary table is not much without a bottom line. We can use the metrics and do some Django ORM voodoo to quickly &lt;strong&gt;calculate the bottom line&lt;/strong&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# admin.py&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SaleSummaryAdmin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ModelAdmin&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# ...&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;changelist_view&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;extra_context&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# ...&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;context_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;summary_total&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="hll"&gt;            &lt;span class="n"&gt;qs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;aggregate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;That's a pretty cool trick…&lt;/p&gt;
&lt;p&gt;Lets add the bottom line to the table:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="x"&gt;&amp;lt;!-- sale_summary_change_list.html --&amp;gt;&lt;/span&gt;

&lt;span class="x"&gt;&amp;lt;div class=&amp;quot;results&amp;quot;&amp;gt;&lt;/span&gt;
&lt;span class="x"&gt;    &amp;lt;table&amp;gt;&lt;/span&gt;

&lt;span class="x"&gt;        &amp;lt;!-- ... --&amp;gt;&lt;/span&gt;

&lt;span class="x"&gt;        &amp;lt;tr style=&amp;quot;font-weight:bold; border-top:2px solid #DDDDDD;&amp;quot;&amp;gt;&lt;/span&gt;
&lt;span class="x"&gt;            &amp;lt;td&amp;gt; Total &amp;lt;/td&amp;gt;&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="x"&gt;            &amp;lt;td&amp;gt; &lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;summary_total.total&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nf"&gt;intcomma&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="x"&gt; &amp;lt;/td&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="x"&gt;            &amp;lt;td&amp;gt; &lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;summary_total.total_sales&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="x"&gt;$ &amp;lt;/td&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;span class="x"&gt;            &amp;lt;td&amp;gt; 100% &amp;lt;/td&amp;gt;&lt;/span&gt;
&lt;span class="x"&gt;        &amp;lt;/tr&amp;gt;&lt;/span&gt;

&lt;span class="x"&gt;    &amp;lt;/table&amp;gt;&lt;/span&gt;
&lt;span class="x"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This is starting to take shape:&lt;/p&gt;
&lt;figure&gt;&lt;img alt="Django admin dashboard with a summary table" src="https://hakibenita.com/images/04-how-to-turn-django-admin-into-a-lightweight-dashboard.png"&gt;&lt;figcaption&gt;Django admin dashboard with a summary table&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="adding-filters"&gt;&lt;a class="toclink" href="#adding-filters"&gt;Adding Filters&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;We are using a "regular" model admin so &lt;strong&gt;filters are already baked in&lt;/strong&gt;. Let's add a filter by device:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# admin.py&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SaleSummaryAdmin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ModelAdmin&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="c1"&gt;# ...&lt;/span&gt;

&lt;span class="hll"&gt;    &lt;span class="n"&gt;list_filter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;        &lt;span class="s1"&gt;&amp;#39;device&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;And the result:&lt;/p&gt;
&lt;figure&gt;&lt;img alt="Django admin dashboard with a filter" src="https://hakibenita.com/images/05-how-to-turn-django-admin-into-a-lightweight-dashboard.png"&gt;&lt;figcaption&gt;Django admin dashboard with a filter&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="adding-a-chart"&gt;&lt;a class="toclink" href="#adding-a-chart"&gt;Adding a Chart&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;A dashboard is not complete without a chart so &lt;strong&gt;we are going to add a bar chart to show sales over time.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;To build our chart we are going to use plain HTML and some good ol' CSS with flexbox. The data for the chart is going to be a time series of percents to use as the bar height.&lt;/p&gt;
&lt;p&gt;Back to our &lt;code&gt;changelist_view&lt;/code&gt;, we add the following:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# admin.py&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db.models.functions&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Trunc&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;DateTimeField&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SalesSummaryAdmin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ModelAdmin&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="c1"&gt;# ...&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;changelist_view&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;extra_context&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

        &lt;span class="c1"&gt;# ...&lt;/span&gt;

        &lt;span class="n"&gt;summary_over_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;qs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;annotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;period&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Trunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s1"&gt;&amp;#39;created&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="s1"&gt;&amp;#39;day&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;output_field&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;DateTimeField&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;period&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;annotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;price&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;order_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;period&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;summary_range&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;summary_over_time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;aggregate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;low&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;total&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;high&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;total&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;high&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;summary_range&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;high&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;low&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;summary_range&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;low&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="hll"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;context_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;summary_over_time&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;period&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;period&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;total&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;total&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;pct&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; \
               &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;total&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;low&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;high&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;low&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;
               &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;high&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;low&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&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;x&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;summary_over_time&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Let's add the bar chart to the template and style it a bit:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="x"&gt;&amp;lt;!-- sale_summary_change_list.html --&amp;gt;&lt;/span&gt;

&lt;span class="x"&gt;&amp;lt;div class=&amp;quot;results&amp;quot;&amp;gt;&lt;/span&gt;

&lt;span class="x"&gt;    &amp;lt;!-- ... --&amp;gt;&lt;/span&gt;

&lt;span class="x"&gt;    &amp;lt;h2&amp;gt; Sales over time &amp;lt;/h2&amp;gt;&lt;/span&gt;

&lt;span class="x"&gt;    &amp;lt;style&amp;gt;&lt;/span&gt;
&lt;span class="x"&gt;    .bar-chart {&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="x"&gt;      display: flex;&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="x"&gt;      justify-content: space-around;&lt;/span&gt;
&lt;/span&gt;&lt;span class="x"&gt;      height: 160px;&lt;/span&gt;
&lt;span class="x"&gt;      padding-top: 60px;&lt;/span&gt;
&lt;span class="x"&gt;      overflow: hidden;&lt;/span&gt;
&lt;span class="x"&gt;    }&lt;/span&gt;
&lt;span class="x"&gt;    .bar-chart .bar {&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="x"&gt;        flex: 100%;&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="x"&gt;        align-self: flex-end;&lt;/span&gt;
&lt;/span&gt;&lt;span class="x"&gt;        margin-right: 2px;&lt;/span&gt;
&lt;span class="x"&gt;        position: relative;&lt;/span&gt;
&lt;span class="x"&gt;        background-color: #79aec8;&lt;/span&gt;
&lt;span class="x"&gt;    }&lt;/span&gt;
&lt;span class="x"&gt;    .bar-chart .bar:last-child {&lt;/span&gt;
&lt;span class="x"&gt;        margin: 0;&lt;/span&gt;
&lt;span class="x"&gt;    }&lt;/span&gt;
&lt;span class="x"&gt;    .bar-chart .bar:hover {&lt;/span&gt;
&lt;span class="x"&gt;        background-color: #417690;&lt;/span&gt;
&lt;span class="x"&gt;    }&lt;/span&gt;

&lt;span class="x"&gt;    .bar-chart .bar .bar-tooltip {&lt;/span&gt;
&lt;span class="x"&gt;        position: relative;&lt;/span&gt;
&lt;span class="x"&gt;        z-index: 999;&lt;/span&gt;
&lt;span class="x"&gt;    }&lt;/span&gt;
&lt;span class="x"&gt;    .bar-chart .bar .bar-tooltip {&lt;/span&gt;
&lt;span class="x"&gt;        position: absolute;&lt;/span&gt;
&lt;span class="x"&gt;        top: -60px;&lt;/span&gt;
&lt;span class="x"&gt;        left: 50%;&lt;/span&gt;
&lt;span class="x"&gt;        transform: translateX(-50%);&lt;/span&gt;
&lt;span class="x"&gt;        text-align: center;&lt;/span&gt;
&lt;span class="x"&gt;        font-weight: bold;&lt;/span&gt;
&lt;span class="x"&gt;        opacity: 0;&lt;/span&gt;
&lt;span class="x"&gt;    }&lt;/span&gt;
&lt;span class="x"&gt;    .bar-chart .bar:hover .bar-tooltip {&lt;/span&gt;
&lt;span class="x"&gt;        opacity: 1;&lt;/span&gt;
&lt;span class="x"&gt;    }&lt;/span&gt;
&lt;span class="x"&gt;    &amp;lt;/style&amp;gt;&lt;/span&gt;

&lt;span class="x"&gt;    &amp;lt;div class=&amp;quot;results&amp;quot;&amp;gt;&lt;/span&gt;
&lt;span class="x"&gt;        &amp;lt;div class=&amp;quot;bar-chart&amp;quot;&amp;gt;&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="x"&gt;        &lt;/span&gt;&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;x&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nv"&gt;summary_over_time&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;/span&gt;&lt;span class="x"&gt;            &amp;lt;div class=&amp;quot;bar&amp;quot; style=&amp;quot;height:&lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;x.pct&lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="x"&gt;%&amp;quot;&amp;gt;&lt;/span&gt;
&lt;span class="x"&gt;                &amp;lt;div class=&amp;quot;bar-tooltip&amp;quot;&amp;gt;&lt;/span&gt;
&lt;span class="x"&gt;                    &lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;x.total&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nf"&gt;intcomma&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;&lt;span class="x"&gt;&amp;lt;br&amp;gt;&lt;/span&gt;
&lt;span class="x"&gt;                    &lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;x.period&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="s2"&gt;:&amp;quot;d/m/Y&amp;quot;&lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;span class="x"&gt;                &amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="x"&gt;            &amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="x"&gt;        &lt;/span&gt;&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;endfor&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;span class="x"&gt;        &amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="x"&gt;    &amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;span class="x"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;For those of you not familiar with flexbox, that piece of CSS means "draw from the bottom up, pull to the left and adjust the width to fit".&lt;/p&gt;
&lt;p&gt;This is how it looks like now:&lt;/p&gt;
&lt;figure&gt;&lt;img alt="Django admin dashboard with a basic chart" src="https://hakibenita.com/images/06-how-to-turn-django-admin-into-a-lightweight-dashboard.png"&gt;&lt;figcaption&gt;Django admin dashboard with a basic chart&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;That's looking pretty good, but... Each bar in the chart represents a day. What will happen when we try to show data for a single day? Or several years?&lt;/p&gt;
&lt;figure&gt;&lt;img alt="Daily chart for several years" src="https://hakibenita.com/images/07-how-to-turn-django-admin-into-a-lightweight-dashboard.png"&gt;&lt;figcaption&gt;Daily chart for several years&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;A chart like that is both &lt;strong&gt;unreadable and dangerous&lt;/strong&gt;. Fetching so much data will flood the server and generate a huge HTML file.&lt;/p&gt;
&lt;p&gt;Django Admin has a date hierarchy - let's see if we can use that to &lt;strong&gt;adjust the period of the bars based on the selected date hierarchy:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_next_in_date_hierarchy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;date_hierarchy&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;date_hierarchy&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__day&amp;#39;&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;hour&amp;#39;&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;date_hierarchy&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__month&amp;#39;&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;day&amp;#39;&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;date_hierarchy&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;__year&amp;#39;&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GET&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;week&amp;#39;&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;month&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;If the user filtered a &lt;strong&gt;single day &lt;/strong&gt;each bar will be &lt;strong&gt;one hour&lt;/strong&gt; (max 24 bars).&lt;/li&gt;
&lt;li&gt;If the user selected a &lt;strong&gt;month &lt;/strong&gt;each bar will be &lt;strong&gt;one day&lt;/strong&gt; (max 31 bars).&lt;/li&gt;
&lt;li&gt;If the user selected a &lt;strong&gt;year &lt;/strong&gt;each bar will be &lt;strong&gt;one week &lt;/strong&gt;(max 52 bars).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;More&lt;/strong&gt; than that and each bar will be &lt;strong&gt;one month&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now we need just one small adjustment to the change list view:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# admin.py&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SalesSummaryAdmin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ModelAdmin&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="c1"&gt;# ...&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;changelist_view&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;extra_context&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

        &lt;span class="c1"&gt;# ...&lt;/span&gt;

&lt;span class="hll"&gt;        &lt;span class="n"&gt;period&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_next_in_date_hierarchy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;date_hierarchy&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;        &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;context_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;period&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;period&lt;/span&gt;
&lt;/span&gt;
        &lt;span class="n"&gt;summary_over_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;qs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;annotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="hll"&gt;            &lt;span class="n"&gt;period&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Trunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;created&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;period&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;output_field&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;DateTimeField&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
&lt;/span&gt;        &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;period&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;annotate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;price&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;order_by&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;period&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The &lt;code&gt;period&lt;/code&gt; argument passed to &lt;code&gt;Trunc&lt;/code&gt; is now a parameter. The result:&lt;/p&gt;
&lt;figure&gt;&lt;img alt="Django admin dashboard chart with adjusted period" src="https://hakibenita.com/images/08-how-to-turn-django-admin-into-a-lightweight-dashboard.png"&gt;&lt;figcaption&gt;Django admin dashboard chart with adjusted period&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;That's a beautiful trend...&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="where-can-we-take-it-from-here"&gt;&lt;a class="toclink" href="#where-can-we-take-it-from-here"&gt;Where Can We Take It From Here?&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Now that you have all this spare time from &lt;em&gt;not&lt;/em&gt; rolling your own dashboard you can:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="/things-you-must-know-about-django-admin-as-your-app-gets-bigger"&gt;Make it faster&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="/how-to-add-custom-action-buttons-to-django-admin"&gt;Add a button&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;</content><category term="articles"></category><category term="Django"></category><category term="Django Admin"></category></entry><entry><title>How to Test Django Signals Like a Pro</title><link href="https://hakibenita.com/how-to-test-django-signals-like-a-pro" rel="alternate"></link><published>2017-02-18T00:00:00+02:00</published><updated>2017-02-18T00:00:00+02:00</updated><author><name>Haki Benita</name></author><id>tag:hakibenita.com,2017-02-18:/how-to-test-django-signals-like-a-pro</id><summary type="html">&lt;p&gt;Django signals are extremely useful for decoupling modules. They allow a low-level Django app to send events for other apps to handle without creating a direct dependency. Signals are easy to set up, but harder to test. In this article we implement a context manager for testing Django signals, step by step.&lt;/p&gt;</summary><content type="html">&lt;hr&gt;
&lt;p&gt;&lt;a href="https://docs.djangoproject.com/en/1.10/topics/signals/" rel="noopener"&gt;Django Signals&lt;/a&gt; are extremely useful for decoupling modules. They allow a low-level Django app to
send events for other apps to handle without creating a direct dependency.&lt;/p&gt;
&lt;h3 id="the-use-case"&gt;&lt;a class="toclink" href="#the-use-case"&gt;The Use Case&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Let's say you have a payment module with a charge function. (I &lt;a href="/working-with-apis-the-pythonic-way"&gt;write a lot about payments&lt;/a&gt;, so I know this use case well.) Once a charge is made, you want to increment a total charges counter.&lt;/p&gt;
&lt;p&gt;What would that look like using signals?&lt;/p&gt;
&lt;p&gt;First, define the signal:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# signals.py&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.dispatch&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Signal&lt;/span&gt;

&lt;span class="n"&gt;charge_completed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Signal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;providing_args&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;total&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Then send the signal when a charge completes successfully:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# payment.py&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;.signals&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;charge_completed&lt;/span&gt;

&lt;span class="nd"&gt;@classmethod&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_charge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="c1"&gt;# Process charge...&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="hll"&gt;        &lt;span class="n"&gt;charge_completed&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send_robust&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;A different app, such as a summary app, can connect a handler that increments a total charges counter:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# summary.py&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.dispatch&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;receiver&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;.signals&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;charge_completed&lt;/span&gt;

&lt;span class="hll"&gt;&lt;span class="nd"&gt;@receiver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;charge_completed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;increment_total_charges&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;total_charges&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The payment module does not have to know the summary module or any other module handling completed charges. &lt;strong&gt;You can add many receivers without modifying the payment module&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;For example, the following are good candidates for receivers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Update the transaction status.&lt;/li&gt;
&lt;li&gt;Send an email notification to the user.&lt;/li&gt;
&lt;li&gt;Update the last used date of the credit card.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id="testing-signals"&gt;&lt;a class="toclink" href="#testing-signals"&gt;Testing Signals&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Now that you got the basics covered, let's write a test for &lt;code&gt;process_charge&lt;/code&gt;. You want to make sure the signal is sent with the right arguments when a charge completes successfully.&lt;/p&gt;
&lt;p&gt;The best way to test if a signal was sent is to connect to it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# test.py&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.test&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TestCase&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;.payment&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;charge&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;.signals&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;charge_completed&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TestCharge&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_should_send_signal_when_charge_succeeds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;signal_was_called&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;

&lt;span class="hll"&gt;        &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;signal_was_called&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;
&lt;span class="hll"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;charge_completed&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;charge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertTrue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;signal_was_called&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="hll"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;charge_completed&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;disconnect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We create a handler, connect to the signal, execute the function and check the args.&lt;/p&gt;
&lt;p&gt;We use &lt;code&gt;self&lt;/code&gt; inside the handler to create a closure. If we hadn't used &lt;code&gt;self&lt;/code&gt; the handler function would update the variables in its local scope and we won't have access to them. We will revisit this later.&lt;/p&gt;
&lt;p&gt;Let's add a test to &lt;strong&gt;make sure the signal is not called if the charge failed&lt;/strong&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# test.py&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_should_not_send_signal_when_charge_failed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;signal_was_called&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;signal_was_called&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;

    &lt;span class="n"&gt;charge_completed&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="hll"&gt;    &lt;span class="n"&gt;charge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertFalse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;signal_was_called&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;charge_completed&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;disconnect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This is working but it's &lt;strong&gt;a lot of boilerplate! &lt;/strong&gt;There must be a better way.&lt;/p&gt;
&lt;h3 id="enter-context-manager"&gt;&lt;a class="toclink" href="#enter-context-manager"&gt;Enter Context Manager&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Let's break down what we did so far:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Connect a signal to some handler.&lt;/li&gt;
&lt;li&gt;Run the test code and save the arguments passed to the handler.&lt;/li&gt;
&lt;li&gt;Disconnect the handler from the signal.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This pattern sounds familiar...&lt;/p&gt;
&lt;p&gt;Let's look at what a (file) &lt;a href="https://docs.python.org/3/library/functions.html#open" rel="noopener"&gt;open context manager&lt;/a&gt; does:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Open a file.&lt;/li&gt;
&lt;li&gt;Process the file.&lt;/li&gt;
&lt;li&gt;Close the file.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;And a &lt;a href="https://docs.djangoproject.com/en/1.10/topics/db/transactions/#controlling-transactions-explicitly" rel="noopener"&gt;database transaction context manager&lt;/a&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Open transaction.&lt;/li&gt;
&lt;li&gt;Execute some operations.&lt;/li&gt;
&lt;li&gt;Close transaction (commit / rollback).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;It looks like &lt;strong&gt;a context manager can work for signals as well&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Before you start, think how you want to use a context manager to test signals:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;CatchSignal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;charge_completed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;signal_args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;charge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;signal_args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Nice, let's give it a try:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CatchSignal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="hll"&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;signal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;signal&lt;/span&gt;
&lt;/span&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;signal_kwargs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="hll"&gt;        &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;signal_kwrags&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__enter__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="hll"&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;signal_kwrags&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__exit__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exc_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exc_value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tb&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="hll"&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;disconnect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;What we have here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You initialized the context with the signal you want to "catch".&lt;/li&gt;
&lt;li&gt;The context creates a handler function to save the arguments sent by the signal.&lt;/li&gt;
&lt;li&gt;You create closure by updating an existing object (&lt;code&gt;signal_kwargs&lt;/code&gt;) on &lt;code&gt;self&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;You connect the handler to the signal.&lt;/li&gt;
&lt;li&gt;Some processing is done (by the test) between &lt;code&gt;__enter__&lt;/code&gt; and &lt;code&gt;__exit__&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;You disconnect the handler from the signal.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let's use the context manager to test the charge function:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_should_send_signal_when_charge_succeeds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="hll"&gt;    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;CatchSignal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;charge_completed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;signal_args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;        &lt;span class="n"&gt;charge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;signal_args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;total&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This is better, but &lt;strong&gt;how would the negative test look like?&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_should_not_send_signal_when_charge_failed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;CatchSignal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;signal_args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;charge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="hll"&gt;    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;signal_args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Yak, that's bad.&lt;/p&gt;
&lt;p&gt;Let's take another look at the handler:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We want to make sure the handler function was invoked.&lt;/li&gt;
&lt;li&gt;We want to test the args sent to the handler function.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Wait... &lt;strong&gt;I already know this function!&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id="enter-mock"&gt;&lt;a class="toclink" href="#enter-mock"&gt;Enter Mock&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Let's replace our handler with a Mock:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;unittest&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;mock&lt;/span&gt;
&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CatchSignal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;signal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;signal&lt;/span&gt;
&lt;span class="hll"&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Mock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__enter__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__exit__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exc_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exc_value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tb&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;disconnect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;And the tests:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# test.py&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_should_send_signal_when_charge_succeeds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;CatchSignal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;charge_completed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;charge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="hll"&gt;    &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assert_called_once_with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;        &lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;mock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ANY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;charge_completed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_should_not_send_signal_when_charge_failed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;CatchSignal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;charge_completed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;charge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="hll"&gt;    &lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assert_not_called&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Much better!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;You used the mock for exactly what it should be used for, and you don't need to worry about scope and closure.&lt;/p&gt;
&lt;p&gt;Now that you have this working, &lt;strong&gt;can you make it even better?&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id="enter-contextlib"&gt;&lt;a class="toclink" href="#enter-contextlib"&gt;Enter &lt;code&gt;contextlib&lt;/code&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Python has a utility module for handling context managers called &lt;a href="https://docs.python.org/3.6/library/contextlib.html" rel="noopener"&gt;contextlib&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Let's rewrite our context using &lt;code&gt;contextlib&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;unittest&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;mock&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;contextlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;contextmanager&lt;/span&gt;
&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="nd"&gt;@contextmanager&lt;/span&gt;
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;catch_signal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Catch django signal and return the mocked call.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Mock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;handler&lt;/span&gt;
    &lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;disconnect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;I like this approach better because it's easier to follow:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The yield makes it clear where the test code is executed.&lt;/li&gt;
&lt;li&gt;No need to save objects on &lt;code&gt;self&lt;/code&gt; because the setup code (enter and exit) are in the same scope.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And that's it, 4 lines of code to rule them all! &lt;strong&gt;Profit!&lt;/strong&gt;&lt;/p&gt;</content><category term="articles"></category><category term="Django"></category><category term="Testing"></category></entry><entry><title>Working With APIs the Pythonic Way</title><link href="https://hakibenita.com/working-with-apis-the-pythonic-way" rel="alternate"></link><published>2017-01-05T00:00:00+02:00</published><updated>2017-01-05T00:00:00+02:00</updated><author><name>Haki Benita</name></author><id>tag:hakibenita.com,2017-01-05:/working-with-apis-the-pythonic-way</id><summary type="html">&lt;p&gt;Communication with external services is an integral part of any modern system. Whether it's a payment service, authentication, analytics or an internal one - systems need to talk to each other. In this short article we are going to implement a module for communicating with a made-up payment gateway, step by step.&lt;/p&gt;</summary><content type="html">&lt;hr&gt;
&lt;p&gt;Communication with external services is an integral part of any modern system. Whether it's a payment service, authentication, analytics or an internal one - &lt;strong&gt;systems need to talk to each other&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;In this short article we are going to implement a module for communicating with a made-up payment gateway, step by step.&lt;/strong&gt;&lt;/p&gt;
&lt;figure&gt;&lt;img alt="It used to be harder" src="https://hakibenita.com/images/01-working-with-apis-the-pythonic-way.jpeg"&gt;&lt;figcaption&gt;It used to be harder&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="the-external-service"&gt;&lt;a class="toclink" href="#the-external-service"&gt;The External Service&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Let's start by defining an imaginary payment service.&lt;/p&gt;
&lt;p&gt;To charge a credit card we need a credit card token, an amount to charge (in cents) and some unique ID provided by the client (us):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;POST
{
    token: &amp;lt;string&amp;gt;,
    amount: &amp;lt;number&amp;gt;,
    uid: &amp;lt;string&amp;gt;,
}
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;If the charge was successful we get a 200 OK status with the data from our request, an expiration time for the charge and a transaction ID:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;200 OK
{
    uid: &amp;lt;string&amp;gt;,
    amount: &amp;lt;number&amp;gt;,
    token: &amp;lt;string&amp;gt;,
    expiration: &amp;lt;string, isoformat&amp;gt;,
    transaction_id: &amp;lt;number&amp;gt;
}
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;If the charge was not successful we get a 400 status with an error code and an informative message:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;400 Bad Request
{
    uid: &amp;lt;string&amp;gt;,
    error: &amp;lt;number&amp;gt;,
    message: &amp;lt;string&amp;gt;
}
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;There are two error codes we want to handle - 1 = refused, and 2 = stolen.&lt;/p&gt;
&lt;h3 id="naive-implementation"&gt;&lt;a class="toclink" href="#naive-implementation"&gt;Naive Implementation&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To get the ball rolling, we start with a naive implementation and build from there:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# payments.py&lt;/span&gt;

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

&lt;span class="n"&gt;PAYMENT_GATEWAY_BASE_URL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;https://gw.com/api&amp;#39;&lt;/span&gt;
&lt;span class="n"&gt;PAYMENT_GATEWAY_TOKEN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;topsecret&amp;#39;&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;charge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Charge.&lt;/span&gt;

&lt;span class="sd"&gt;    amount (int):&lt;/span&gt;
&lt;span class="sd"&gt;        Amount in cents to charge.&lt;/span&gt;
&lt;span class="sd"&gt;    token (str):&lt;/span&gt;
&lt;span class="sd"&gt;        Credit card token.&lt;/span&gt;
&lt;span class="sd"&gt;    timeout (int):&lt;/span&gt;
&lt;span class="sd"&gt;        Timeout in seconds.&lt;/span&gt;

&lt;span class="sd"&gt;    Returns (dict):&lt;/span&gt;
&lt;span class="sd"&gt;        New payment information.&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;Authorization&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Bearer &amp;quot;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;PAYMENT_GATEWAY_TOKEN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;token&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;amount&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;uid&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uuid4&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;PAYMENT_GATEWAY_BASE_URL&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;/charge&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;90% of developer will stop here, &lt;strong&gt;so what is the problem?&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id="handling-errors"&gt;&lt;a class="toclink" href="#handling-errors"&gt;Handling Errors&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;There are two types of errors we need to handle:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;HTTP errors such as connection errors, timeout or connection refused.&lt;/li&gt;
&lt;li&gt;Remote payment errors such as refusal or stolen card.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Our decision to use &lt;code&gt;requests&lt;/code&gt; is an internal implementation detail. The consumer of our module shouldn't have to be aware of that.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;To provide a complete API our module must communicate errors.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Let's start by defining custom error classes:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# errors.py&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ne"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Unavailable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PaymentGatewayError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Refused&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PaymentGatewayError&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Stolen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PaymentGatewayError&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;I &lt;a href="https://medium.com/@hakibenita/bullet-proofing-django-models-c080739be4e#.4ju7vgl0t" rel="noopener"&gt;previously wrote&lt;/a&gt; about the benefits of using a base error class.&lt;/p&gt;
&lt;p&gt;Let's add exception handling and logging to our function:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;logging&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;.&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;

&lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;payments&amp;#39;&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;charge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="c1"&gt;# ...&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;PAYMENT_GATEWAY_BASE_URL&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;/charge&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ConnectionError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Timeout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="hll"&gt;        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unavailable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;e&lt;/span&gt;
&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exceptions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTPError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;code&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;message&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="hll"&gt;            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Refused&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;e&lt;/span&gt;
&lt;/span&gt;        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="hll"&gt;            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stolen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;e&lt;/span&gt;
&lt;/span&gt;        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="hll"&gt;            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PaymentGatewayError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;e&lt;/span&gt;
&lt;/span&gt;
        &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Payment service had internal error.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="hll"&gt;        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unavailable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;e&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Great! Our function no longer raises &lt;code&gt;requests&lt;/code&gt; exceptions. Important errors such as stolen card or refusal are raised as custom exceptions.&lt;/p&gt;
&lt;h3 id="defining-the-response"&gt;&lt;a class="toclink" href="#defining-the-response"&gt;Defining the Response&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Our function returns a dict. A dict is a great and flexible data structure, but when you have a defined set of fields you are better off using a more targeted data type.&lt;/p&gt;
&lt;p&gt;In every OOP class you learn that everything is an object. While it is true in Java land, Python has a lightweight solution that works better in our case - &lt;a href="https://docs.python.org/3.7/library/collections.html#collections.namedtuple" rel="noopener"&gt;&lt;strong&gt;namedtuple&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;A namedtuple is just like it sounds, a tuple where the fields have names. You use it like a class and it consumes less space (even compared to a class with slots).&lt;/p&gt;
&lt;p&gt;Let's define a namedtuple for the charge response:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;collections&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;namedtuple&lt;/span&gt;

&lt;span class="n"&gt;ChargeResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;namedtuple&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ChargeResponse&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;uid&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;amount&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;token&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;expiration&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;transaction_id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;If the charge was successful, we create a &lt;code&gt;ChargeResponse&lt;/code&gt; object:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;

&lt;span class="c1"&gt;# ...&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;charge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="c1"&gt;# ...&lt;/span&gt;

    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="hll"&gt;    &lt;span class="n"&gt;charge_response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ChargeResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;        &lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UID&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="s1"&gt;&amp;#39;uid&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
        &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;amount&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;token&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;expiration&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;strptime&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="s1"&gt;&amp;#39;expiration&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;%Y-%m-&lt;/span&gt;&lt;span class="si"&gt;%d&lt;/span&gt;&lt;span class="s2"&gt;T%H:%M:%S.&lt;/span&gt;&lt;span class="si"&gt;%f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;transaction_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;transaction_id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&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;charge_response&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Our function now returns a &lt;code&gt;ChargeResponse&lt;/code&gt; object. Additional processing such as casting and validations can be added easily.&lt;/p&gt;
&lt;p&gt;In the case of our imaginary payment gateway, we convert the expiration date to a datetime object. The consumer doesn't have to guess the date format used by the remote service (when it comes to date formats I am sure we all encountered a fair share of horrors).&lt;/p&gt;
&lt;p&gt;By using a custom "class" as the return value we reduce the dependency in the payment vendor‘s serialization format. If the response was an XML, would we still return a dict? That's just awkward.&lt;/p&gt;
&lt;h3 id="using-a-session"&gt;&lt;a class="toclink" href="#using-a-session"&gt;Using a Session&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To skim some extra milliseconds from API calls we can use a session. &lt;a href="http://docs.python-requests.org/en/master/user/advanced/#session-objects" rel="noopener"&gt;Requests session &lt;/a&gt; uses
a connection pool internally. Requests to the same host can benefit from that. We also take the opportunity to add useful configuration such as blocking cookies:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;http.cookiejar&lt;/span&gt;

&lt;span class="c1"&gt;# A shared requests session for payment requests.&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BlockAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cookiejar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CookiePolicy&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;set_ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cookie&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;

&lt;span class="n"&gt;payment_session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Session&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;payment_session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cookies&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;policy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;BlockAll&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# ...&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;charge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;span class="hll"&gt;    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;payment_session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;    &lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;h3 id="more-actions"&gt;&lt;a class="toclink" href="#more-actions"&gt;More Actions&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Any external service, and a payment service in particular, has more than one action.&lt;/p&gt;
&lt;p&gt;The first section of our function takes care of authorization, the request and HTTP errors. The second part handle protocol errors and serialization specific to the charge action.&lt;/p&gt;
&lt;p&gt;The first part is relevant to all actions while the second part is specific only to the charge.&lt;/p&gt;
&lt;p&gt;Let's split the function so we can reuse the first part:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;uuid&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;logging&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;requests&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;http.cookiejar&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;


&lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;payments&amp;#39;&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;BlockAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cookiejar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CookiePolicy&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;set_ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cookie&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;

&lt;span class="n"&gt;payment_session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Session&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;payment_session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cookies&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;policy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;BlockAll&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;make_payment_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Make a request to the payment gateway.&lt;/span&gt;

&lt;span class="sd"&gt;    path (str):&lt;/span&gt;
&lt;span class="sd"&gt;        Path to post to.&lt;/span&gt;
&lt;span class="sd"&gt;    payload (object):&lt;/span&gt;
&lt;span class="sd"&gt;        JSON-serializable request payload.&lt;/span&gt;
&lt;span class="sd"&gt;    timeout (int):&lt;/span&gt;
&lt;span class="sd"&gt;        Timeout in seconds.&lt;/span&gt;

&lt;span class="sd"&gt;    Raises&lt;/span&gt;
&lt;span class="sd"&gt;        Unavailable&lt;/span&gt;
&lt;span class="sd"&gt;        requests.exceptions.HTTPError&lt;/span&gt;

&lt;span class="sd"&gt;    Returns (response)&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;&amp;quot;Authorization&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Bearer &amp;quot;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;PAYMENT_GATEWAY_TOKEN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;payment_session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;PAYMENT_GATEWAY_BASE_URL&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ConnectionError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Timeout&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unavailable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;e&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;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;charge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Charge credit card.&lt;/span&gt;

&lt;span class="sd"&gt;    amount (int):&lt;/span&gt;
&lt;span class="sd"&gt;        Amount to charge in cents.&lt;/span&gt;
&lt;span class="sd"&gt;    token (str):&lt;/span&gt;
&lt;span class="sd"&gt;        Credit card token.&lt;/span&gt;

&lt;span class="sd"&gt;    Raises&lt;/span&gt;
&lt;span class="sd"&gt;        Unavailable&lt;/span&gt;
&lt;span class="sd"&gt;        Refused&lt;/span&gt;
&lt;span class="sd"&gt;        Stolen&lt;/span&gt;
&lt;span class="sd"&gt;        PaymentGatewayError&lt;/span&gt;

&lt;span class="sd"&gt;    Returns (ChargeResponse)&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;make_payment_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/charge&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;uid&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uuid4&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;amount&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;token&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTPError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;code&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;message&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;Refused&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;e&lt;/span&gt;

            &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;Stolen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;e&lt;/span&gt;

            &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;PaymentGatewayError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;e&lt;/span&gt;

        &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Payment service had internal error&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unavailable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;e&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ChargeResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UID&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="s1"&gt;&amp;#39;uid&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
        &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;amount&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;token&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;expiration&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;strptime&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="s1"&gt;&amp;#39;expiration&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;%Y-%m-&lt;/span&gt;&lt;span class="si"&gt;%d&lt;/span&gt;&lt;span class="s2"&gt;T%H:%M:%S.&lt;/span&gt;&lt;span class="si"&gt;%f&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;transaction_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;transaction_id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;This is the entire code.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;There is a clear separation between "transport", serialization, authentication and request processing. We also have a well defined interface to our top level function &lt;code&gt;charge&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;To add a new action we define a new return type, call &lt;code&gt;make_payment_request&lt;/code&gt; and handle the response the same way:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;RefundResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;namedtuple&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;RefundResponse&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;transaction_id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;refunded_transaction_id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;])&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;refund&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;transaction_id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Refund charged transaction.&lt;/span&gt;

&lt;span class="sd"&gt;    transaction_id (str):&lt;/span&gt;
&lt;span class="sd"&gt;        Transaction id to refund.&lt;/span&gt;

&lt;span class="sd"&gt;    Raises:&lt;/span&gt;

&lt;span class="sd"&gt;    Return (RefundResponse)&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;make_payment_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/refund&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;uid&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uuid4&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;transaction_id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;transaction_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTPError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# TODO: Handle refund remote errors&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;RefundResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;transaction_id&amp;#39;&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="s1"&gt;&amp;#39;transaction_id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;refunded_transaction_id&amp;#39;&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="s1"&gt;&amp;#39;refunded_transaction_id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Profit!&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id="testing"&gt;&lt;a class="toclink" href="#testing"&gt;Testing&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The challenge with external APIs is that you can't (or at least, shouldn't) make calls to them in automated tests. I want to focus on &lt;strong&gt;testing code that uses our payments module&lt;/strong&gt; rather than testing the actual module.&lt;/p&gt;
&lt;p&gt;Our module has a simple interface so it's easy to mock. Let's test a made up function called &lt;code&gt;charge_user_for_product&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# test.py&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;unittest&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TestCase&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;unittest.mock&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;patch&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;payment.payment&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ChargeResponse&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;payment&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;TestApp&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="hll"&gt;    &lt;span class="nd"&gt;@mock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;payment.charge&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_should_charge_user_for_product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mock_charge&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="hll"&gt;        &lt;span class="n"&gt;mock_charge&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;return_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ChargeResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;            &lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;test-uid&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;test-token&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;expiration&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2017&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;transaction_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;12345&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;charge_user_for_product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;approved_transactions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="hll"&gt;    &lt;span class="nd"&gt;@mock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;payment.charge&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_should_suspend_user_if_stolen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mock_charge&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="hll"&gt;        &lt;span class="n"&gt;mock_charge&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;side_effect&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stolen&lt;/span&gt;
&lt;/span&gt;        &lt;span class="n"&gt;charge_user_for_product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_active&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Pretty straight forward - no need to mock the API response. The tests are contained to data structures we defined ourselves and have full control of.&lt;/p&gt;
&lt;h4 id="note-about-dependency-injection"&gt;&lt;a class="toclink" href="#note-about-dependency-injection"&gt;Note About Dependency Injection&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Another approach to test a service is to provide two implementations: the real one, and a fake one. Then for tests, inject the fake one.&lt;/p&gt;
&lt;p&gt;This is of course, how dependency injection works. Django doesn't do DI but it utilizes the same concept with "backends" (email, cache, template, etc). For example you can test emails in django by using a test backend, test caching by using in-memory backend, etc.&lt;/p&gt;
&lt;p&gt;This also has other advantages in that you can have multiple "real" backends.&lt;/p&gt;
&lt;p&gt;Whether you choose to mock the service calls as illustrated above or inject a "fake" service, you must have a proper interface.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="summary"&gt;&lt;a class="toclink" href="#summary"&gt;Summary&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;We have an external service we want to use in our app. We want to implement a module to communicate with that external service and make it robust, resilient and reusable.&lt;/p&gt;
&lt;p&gt;We worked the following steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Naive implementation &lt;/strong&gt;- Fetch using requests and return a json response.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Handled errors&lt;/strong&gt; - Defined custom errors to catch both transport and remote application errors. The consumer is indifferent to the transport (HTTP, RPC, Web Socket) and implementation details (requests).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Formalize the return value &lt;/strong&gt;- Used a namedtuple to return a class-like type that represents a response from the remote service. The consumer is now indifferent to the serialization format as well.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Added a session&lt;/strong&gt; - Skimmed off a few milliseconds from the request and added a place for global connection configuration.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Split request from action&lt;/strong&gt; - The request part is reusable and new actions can be added more easily.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Test&lt;/strong&gt; - Mocked calls to our module and replaced them with our own custom exceptions.&lt;/li&gt;
&lt;/ol&gt;</content><category term="articles"></category><category term="Python"></category><category term="Django"></category><category term="Testing"></category></entry><entry><title>Timing Tests in Python For Fun and Profit</title><link href="https://hakibenita.com/timing-tests-in-python-for-fun-and-profit" rel="alternate"></link><published>2016-11-09T00:00:00+02:00</published><updated>2016-11-09T00:00:00+02:00</updated><author><name>Haki Benita</name></author><id>tag:hakibenita.com,2016-11-09:/timing-tests-in-python-for-fun-and-profit</id><summary type="html">&lt;p&gt;Hunting down slow tests by reporting tests that take longer than a certain threshold (Because the first step to better test performance is awareness!)&lt;/p&gt;</summary><content type="html">&lt;hr&gt;
&lt;p&gt;I was preparing to push some changes a couple of days ago and as I usually do, I ran the tests. I sat back in my chair as the dots raced across the screen when suddenly I noticed that one of the dots linger. "OS is probably running some updates in the background or something" I said to myself, and ran the tests again just to be sure. I watched closely as the dots filled the screen and there it was again - &lt;strong&gt;I have a slow test&lt;/strong&gt;!&lt;/p&gt;
&lt;figure&gt;&lt;img alt="Can you spot the slow test? Neither can I..." src="https://hakibenita.com/images/01-timing-tests-in-python-for-fun-and-profit.png"&gt;&lt;figcaption&gt;Can you spot the slow test? Neither can I...&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;strong&gt;We are going to hunt down slow tests by reporting tests that take longer than a certain threshold.&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id="the-basics"&gt;&lt;a class="toclink" href="#the-basics"&gt;The Basics&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To get the ball rolling let's create a simple test case with a fast test and a slow test:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;unittest&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SlowTestCase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;unittest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TestCase&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

&lt;span class="hll"&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_should_run_fast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="hll"&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_should_run_slow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;        &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Running this script from the command line produces the following output:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;python&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;unittest&lt;span class="w"&gt; &lt;/span&gt;timing.py
..
Ran&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;tests&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.502s

OK
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;I'm sorry unittest, but &lt;strong&gt;this is definitely not OK&lt;/strong&gt; - 0.5s for two tests?&lt;/p&gt;
&lt;p&gt;To figure out which tests are slow we need to &lt;strong&gt;measure the time it takes each test to execute&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;A python &lt;code&gt;unittest.TestCase&lt;/code&gt; has hooks that execute in the following order:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&amp;gt; setUpClass
    &amp;gt; setUp
        &amp;gt; test_*
    &amp;gt; tearDown
&amp;gt; tearDownClass
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;If we want to time a single test (&lt;code&gt;test_*&lt;/code&gt;) we need to start a timer in &lt;code&gt;setUp&lt;/code&gt; and stop it in tearDown:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;unittest&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SlowTestCase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;unittest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TestCase&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

&lt;span class="hll"&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;setUp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_started_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="hll"&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;tearDown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;        &lt;span class="n"&gt;elapsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_started_at&lt;/span&gt;
        &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s1"&gt; (&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s1"&gt;s)&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nb"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;elapsed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&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_should_run_fast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&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_should_run_slow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This produces the following output:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;python&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;unittest&lt;span class="w"&gt; &lt;/span&gt;timing.py

__main__.SlowTestCase.test_should_run_fast&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.0s&lt;span class="o"&gt;)&lt;/span&gt;
.__main__.SlowTestCase.test_should_run_slow&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.5s&lt;span class="o"&gt;)&lt;/span&gt;
.

Ran&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;tests&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.503s
OK
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Great! We got the timing for each test but we really want &lt;strong&gt;only the slow ones&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Let's say a slow test is a test that takes longer than 0.3s:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="n"&gt;SLOW_TEST_THRESHOLD&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.3&lt;/span&gt;
&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SlowTestCase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;unittest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TestCase&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="c1"&gt;# ...&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;tearDown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;elapsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_started_at&lt;/span&gt;
&lt;span class="hll"&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;elapsed&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;SLOW_TEST_THRESHOLD&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;            &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s1"&gt; (&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s1"&gt;s)&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nb"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;elapsed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;And the output:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;python&lt;span class="w"&gt; &lt;/span&gt;-m&lt;span class="w"&gt; &lt;/span&gt;unittest&lt;span class="w"&gt; &lt;/span&gt;timing.py
.__main__.SlowTestCase.test_should_run_slow&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.5s&lt;span class="o"&gt;)&lt;/span&gt;
.
Ran&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;tests&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.503s

OK
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Awesome! We got exactly what we wanted but it is still incomplete. We are good developers so we are most likely dead lazy. We don't want to go around and update every test case - &lt;strong&gt;we need a more robust solution&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id="the-runner"&gt;&lt;a class="toclink" href="#the-runner"&gt;The Runner&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;One of the roles of the &lt;strong&gt;&lt;code&gt;TestRunner&lt;/code&gt;&lt;/strong&gt; is to print test results to an output stream. The runner uses a &lt;strong&gt;&lt;code&gt;TestResult&lt;/code&gt;&lt;/strong&gt; object to format the results. The unittest module comes with a default &lt;strong&gt;&lt;a href="https://docs.python.org/3/library/unittest.html#unittest.TextTestRunner" rel="noopener"&gt;TextTestRunner&lt;/a&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;a href="https://docs.python.org/3/library/unittest.html#unittest.TextTestResult" rel="noopener"&gt;TextTestResult&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Let's implement a custom &lt;code&gt;TestResult&lt;/code&gt; to report slow tests:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;unittest.runner&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TextTestResult&lt;/span&gt;

&lt;span class="n"&gt;SLOW_TEST_THRESHOLD&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.3&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TimeLoggingTestResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TextTestResult&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;startTest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_started_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;startTest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;test&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;addSuccess&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;elapsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_started_at&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;elapsed&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;SLOW_TEST_THRESHOLD&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getDescription&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s2"&gt; (&lt;/span&gt;&lt;span class="si"&gt;{:.03}&lt;/span&gt;&lt;span class="s2"&gt;s)&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;elapsed&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

        &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addSuccess&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Almost identical to what we already have but using &lt;strong&gt;different hooks&lt;/strong&gt;. Instead of setUp we use testStart and instead of tearDown we use &lt;code&gt;addSuccess&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The built-in TextTestRunner uses &lt;code&gt;TextTestResult&lt;/code&gt;. To use a different &lt;code&gt;TestResult&lt;/code&gt; we create an instance of &lt;strong&gt;&lt;code&gt;TextTestRunner&lt;/code&gt;&lt;/strong&gt; with our runner:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;from unittest import TextTestRunner

if __name__ == &amp;#39;__main__&amp;#39;:
    test_runner = TextTestRunner(resultclass=TimeLoggingTestResult)
    unittest.main(testRunner=test_runner)
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;And the output:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;$&lt;span class="w"&gt; &lt;/span&gt;python&lt;span class="w"&gt; &lt;/span&gt;runner.py
.
test_should_run_slow&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;__main__.SlowTestCase&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.501s&lt;span class="o"&gt;)&lt;/span&gt;
.
Ran&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;tests&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.501s

OK
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We get a nice report &lt;strong&gt;without having to make any changes&lt;/strong&gt; to existing test cases.&lt;/p&gt;
&lt;h3 id="can-we-do-better"&gt;&lt;a class="toclink" href="#can-we-do-better"&gt;Can We Do Better?&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Right now we have a bunch of messages sprinkled around in random places across the screen. What if we could get a nice report with all the slow tests? Well, we can!&lt;/p&gt;
&lt;p&gt;Let's start by making our TestResult store the timings without reporting them:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;unittest.runner&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TextTestResult&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TimeLoggingTestResult&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TextTestResult&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="hll"&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;test_timings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;startTest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_test_started_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;startTest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;test&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;addSuccess&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;elapsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_test_started_at&lt;/span&gt;
        &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getDescription&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="hll"&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;test_timings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;elapsed&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;        &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addSuccess&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="hll"&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;getTestTimings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;test_timings&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The test result now holds a list of tuples containing the test name and the elapsed time. Moving over to our custom &lt;code&gt;TestRunner&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# common/test/runner.py&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;unittest&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TimeLoggingTestRunner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;unittest&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TextTestRunner&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;slow_test_threshold&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="hll"&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;slow_test_threshold&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;slow_test_threshold&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resultclass&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;TimeLoggingTestResult&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;test&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;writeln&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;Slow Tests (&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{:.03}&lt;/span&gt;&lt;span class="s2"&gt;s):&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;slow_test_threshold&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="hll"&gt;        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;elapsed&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getTestTimings&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;elapsed&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;slow_test_threshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;                &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;writeln&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;(&lt;/span&gt;&lt;span class="si"&gt;{:.03}&lt;/span&gt;&lt;span class="s2"&gt;s) &lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;elapsed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Let's break it down:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We've replaced &lt;code&gt;SLOW_TEST_THRESHOLD&lt;/code&gt; with a parameter to the init - Much cleaner.&lt;/li&gt;
&lt;li&gt;We've set the appropriate TestResult class.&lt;/li&gt;
&lt;li&gt;We've override run and add our custom "slow test" report.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is what the output looks like (I added some slow tests to illustrate):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;python&lt;span class="w"&gt; &lt;/span&gt;timing.py
.....
Ran&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;tests&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;.706s
OK

Slow&lt;span class="w"&gt; &lt;/span&gt;Tests&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&amp;gt;0.3s&lt;span class="o"&gt;)&lt;/span&gt;:
&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.501s&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;test_should_run_slow&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;__main__.SlowTestCase&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.802s&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;test_should_run_very_slow&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;__main__.SlowTestCase&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.301s&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;test_should_run_slow_enough&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;__main__.SlowTestCase&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Now that we have the timing data we can use that to generate interesting reports. We can sort by elapsed time, show potential time reduction and highlight sluggish tests.&lt;/p&gt;
&lt;h3 id="how-to-use-this-with-django"&gt;&lt;a class="toclink" href="#how-to-use-this-with-django"&gt;How To Use This With Django&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Django has its own test runner so we need to make some adjustments:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# common/test/runner.py&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.test.runner&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;DiscoverRunner&lt;/span&gt;

&lt;span class="c1"&gt;# ...&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TimeLoggingTestRunner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DiscoverRunner&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="hll"&gt;
&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_resultclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;TimeLoggingTestResult&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;And to make Django use our custom runner we set the following:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# settings.py&lt;/span&gt;

&lt;span class="n"&gt;TEST_RUNNER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;common.tests.runner.TimeLoggingTestRunner&amp;#39;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Go make some tests faster!&lt;/p&gt;</content><category term="articles"></category><category term="Python"></category><category term="Django"></category><category term="Testing"></category><category term="Performance"></category></entry><entry><title>How to Add Custom Action Buttons to Django Admin</title><link href="https://hakibenita.com/how-to-add-custom-action-buttons-to-django-admin" rel="alternate"></link><published>2016-11-02T00:00:00+02:00</published><updated>2016-11-02T00:00:00+02:00</updated><author><name>Haki Benita</name></author><id>tag:hakibenita.com,2016-11-02:/how-to-add-custom-action-buttons-to-django-admin</id><summary type="html">&lt;p&gt;The built-in admin actions, operate on a queryset and are hidden in a dropbox menu. They are not suitable for most use cases. In this article we are going to add custom action buttons for each row in a Django Admin list view.&lt;/p&gt;</summary><content type="html">&lt;hr&gt;
&lt;p&gt;We are big fans of the Django admin interface. It's a huge selling point for Django as it takes the load off developing a "back office" for support and day to day operations.&lt;/p&gt;
&lt;p&gt;In the &lt;a href="/bullet-proofing-django-models"&gt;last post&lt;/a&gt; we presented a pattern we use often in our Django models. We used a bank account application with an &lt;strong&gt;Account&lt;/strong&gt; and account &lt;strong&gt;Action&lt;/strong&gt; models to demonstrate the way we handle common issues such as concurrency and validation. The bank account had two operations we wanted to expose in the admin interface - &lt;strong&gt;deposit&lt;/strong&gt; and &lt;strong&gt;withdraw&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;We are going to add buttons in the Django admin interface to deposit and withdraw from an account, and we are going to do it in less than 100 lines of code!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;What will it look like at the end?&lt;/p&gt;
&lt;figure&gt;&lt;img alt="Django Admin interface with custom action buttons" src="https://hakibenita.com/images/01-how-to-add-custom-action-buttons-to-django-admin.png"&gt;&lt;figcaption&gt;Django Admin interface with custom action buttons&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Our custom actions are the nice looking deposit and withdraw buttons next to each account.&lt;/p&gt;
&lt;h3 id="why-not-use-the-existing-admin-actions"&gt;&lt;a class="toclink" href="#why-not-use-the-existing-admin-actions"&gt;Why Not Use the Existing Admin Actions?&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The &lt;a href="https://docs.djangoproject.com/en/1.10/ref/contrib/admin/actions/" rel="noopener"&gt;built-in admin actions&lt;/a&gt; operate on a queryset. They are &lt;strong&gt;hidden in a dropbox menu&lt;/strong&gt; in the top toolbar, and they are mostly useful for &lt;strong&gt;executing bulk operations&lt;/strong&gt;. A good example is the default delete action. You mark several rows and select "delete rows" form the drop down menu. This is not very fleixble, and not suitable for some use cases.&lt;/p&gt;
&lt;figure&gt;&lt;img alt="Django built in actions" src="https://hakibenita.com/images/02-how-to-add-custom-action-buttons-to-django-admin.png"&gt;&lt;figcaption&gt;Django built in actions&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Another downside, is that &lt;strong&gt;actions are not available in the detail view&lt;/strong&gt;. To add buttons to the detail view you need to override the template, A huge pain.&lt;/p&gt;
&lt;h3 id="the-forms"&gt;&lt;a class="toclink" href="#the-forms"&gt;The Forms&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;First thing first, we need some data from the user to perform the action. Naturally, &lt;strong&gt;we need a form&lt;/strong&gt;. We need one form for deposit, and one form for withdraw.&lt;/p&gt;
&lt;p&gt;In addition to performing the action, we are going to add a nifty option to send a notification email to the account owner, informing him about an action made to his account.&lt;/p&gt;
&lt;p&gt;All of our actions have common arguments like comment and send_email. The actions also handle
success and failure in a similar way.&lt;/p&gt;
&lt;p&gt;Let's start with a base form to handle a general action:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# forms.py&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;forms&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;common.utils&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;send_email&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;.&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AccountActionForm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;forms&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Form&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;comment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;forms&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;required&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;widget&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;forms&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Textarea&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;send_email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;forms&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BooleanField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;required&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nd"&gt;@property&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;email_subject_template&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;email/account/notification_subject.txt&amp;#39;&lt;/span&gt;

    &lt;span class="nd"&gt;@property&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;email_body_template&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="ne"&gt;NotImplementedError&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;form_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="ne"&gt;NotImplementedError&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;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;form_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;error_message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;error_message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cleaned_data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;send_email&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;send_email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="n"&gt;subject_template&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email_subject_template&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;body_template&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email_body_template&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="s2"&gt;&amp;quot;account&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="s2"&gt;&amp;quot;action&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;}&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;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;Every action has a comment, and an option to send a notification if the action completed successfully.&lt;/li&gt;
&lt;li&gt;The actual operation is executed when the form is saved. This is similar to how &lt;code&gt;ModelForm&lt;/code&gt; works.&lt;/li&gt;
&lt;li&gt;For logging and audit purposes, the caller must provide the user executing the action.&lt;/li&gt;
&lt;li&gt;Required properties that are not implemented by derived forms, will raise &lt;code&gt;NotImplementedError&lt;/code&gt;. This way, we make sure the developer gets an informative error message if she forgets to implement something.&lt;/li&gt;
&lt;li&gt;Errors are handled at the base exception class level. Our models define a base error class, so we can easily catch all (and only) account related exceptions, and handle them appropriately.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now that we have a simple base class, let's add a form to withdraw from an account. The withdraw we need to add an amount field:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# forms.py&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.utils&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;timezone&lt;/span&gt;

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

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WithdrawForm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AccountActionForm&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;forms&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IntegerField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;min_value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Account&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MIN_WITHDRAW&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;max_value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Account&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MAX_WITHDRAW&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;required&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;help_text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;How much to withdraw?&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;email_body_template&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;email/account/withdraw.txt&amp;#39;&lt;/span&gt;

    &lt;span class="n"&gt;field_order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;amount&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;comment&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;send_email&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;form_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&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;Account&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;withdraw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cleaned_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;amount&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="n"&gt;withdrawn_by&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;comment&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cleaned_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;comment&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="n"&gt;asof&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;We extended the base &lt;code&gt;AccountActionForm&lt;/code&gt; and added an &lt;code&gt;amount&lt;/code&gt; field with proper validations.&lt;/li&gt;
&lt;li&gt;We filled in the required attributes, &lt;code&gt;email_body_template&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;We implemented the form action using the &lt;code&gt;classmethod&lt;/code&gt; from the previous post. The model takes care of locking the record, updating any calculated fields, and adding the proper action to the log.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The next step is to add a deposit action. Deposit requires amount, reference and reference type fields:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# forms.py&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DepositForm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AccountActionForm&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;forms&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IntegerField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;min_value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Account&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MIN_DEPOSIT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;max_value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Account&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MAX_DEPOSIT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;required&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;help_text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;How much to deposit?&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;reference_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;forms&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ChoiceField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;required&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;choices&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Action&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;REFERENCE_TYPE_CHOICES&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;reference&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;forms&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CharField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;required&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;email_body_template&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;email/account/deposit.txt&amp;#39;&lt;/span&gt;

    &lt;span class="n"&gt;field_order&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;amount&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;reference_type&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;reference&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;comment&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;send_email&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;form_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&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;Account&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deposit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cleaned_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;amount&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="n"&gt;deposited_by&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;reference&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cleaned_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;reference&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="n"&gt;reference_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cleaned_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;reference_type&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="n"&gt;comment&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cleaned_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;comment&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="n"&gt;asof&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;So far we got the necessary forms to accept, validate and execute deposit and withdraw. The next step is to integrate it into Django Admin list view.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="the-admin"&gt;&lt;a class="toclink" href="#the-admin"&gt;The Admin&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Before we can add fancy buttons for actions, we need to set up a &lt;strong&gt;basic admin page&lt;/strong&gt; for our &lt;code&gt;Account&lt;/code&gt; model:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# admin.py&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.contrib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt;

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

&lt;span class="nd"&gt;@admin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Account&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AccountAdmin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ModelAdmin&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;date_heirarchy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;modified&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;list_display&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;user&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;modified&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;balance&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;account_actions&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;readonly_fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;user&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;modified&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;balance&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;account_actions&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;list_select_related&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;user&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;account_actions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# TODO: Render action buttons&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Side Note: We can make the list view much better by adding a link to the user, and to the account actions. We can add some search fields and many more. I previously wrote about &lt;a href="/things-you-must-know-about-django-admin-as-your-app-gets-bigger"&gt;performance considerations in the admin interface when scaling a Django app to hundreds of thousands of users&lt;/a&gt;. There are some nice tricks there that can make even this simple view much nicer.&lt;/p&gt;
&lt;h3 id="adding-the-action-buttons"&gt;&lt;a class="toclink" href="#adding-the-action-buttons"&gt;Adding the Action Buttons&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;We want to add action buttons for each account, and have them link to a page with a form. Django has a function to register URLs in a list view. Let's use that function to add the routes for our custom actions:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# admin.py&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.utils.html&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;format_html&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.core.urlresolvers&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;reverse&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AccountAdmin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ModelAdmin&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="c1"&gt;# ...&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_urls&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;urls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_urls&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;custom_urls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="hll"&gt;                &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;^(?P&amp;lt;account_id&amp;gt;.+)/deposit/$&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;                &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;admin_site&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;admin_view&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;process_deposit&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;                &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;account-deposit&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="hll"&gt;                &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;^(?P&amp;lt;account_id&amp;gt;.+)/withdraw/$&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;                &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;admin_site&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;admin_view&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;process_withdraw&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;                &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;account-withdraw&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;),&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;custom_urls&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;urls&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;account_actions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;obj&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;format_html&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;&amp;lt;a class=&amp;quot;button&amp;quot; href=&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s1"&gt;&amp;quot;&amp;gt;Deposit&amp;lt;/a&amp;gt;&amp;amp;nbsp;&amp;#39;&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;&amp;lt;a class=&amp;quot;button&amp;quot; href=&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s1"&gt;&amp;quot;&amp;gt;Withdraw&amp;lt;/a&amp;gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;reverse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;admin:account-deposit&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pk&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
            &lt;span class="n"&gt;reverse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;admin:account-withdraw&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pk&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;account_actions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;short_description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Account Actions&amp;#39;&lt;/span&gt;
    &lt;span class="n"&gt;account_actions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;allow_tags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_deposit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# TODO&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_withdraw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# TODO&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt;We registered two urls, one for deposit and one for withdraw.&lt;/li&gt;
&lt;li&gt;We referenced each route to a relevant function, &lt;code&gt;process_deposit&lt;/code&gt; and &lt;code&gt;process_withdraw&lt;/code&gt;. These function will render an intermediate page with the corresponding form, and will carry out the operation.&lt;/li&gt;
&lt;li&gt;We added a custom field &lt;code&gt;account_actions&lt;/code&gt; to render the buttons for each action. The benefit of using a "regular" admin field like &lt;code&gt;account_actions&lt;/code&gt;, is that it is available in both the detail and the list view.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Let's move on to implement the functions to handle the actions:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# admin.py&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.http&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;HttpResponseRedirect&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.template.response&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TemplateResponse&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;.forms&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;DepositForm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;WithdrawForm&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AccountAdmin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ModelAdmin&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

   &lt;span class="c1"&gt;# ...&lt;/span&gt;

   &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_deposit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;account_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;process_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;account_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;account_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;action_form&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;DepositForm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;action_title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Deposit&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

   &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_withdraw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;account_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;process_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;account_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;account_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;action_form&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;WithdrawForm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;action_title&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Withdraw&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

   &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;process_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;account_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;action_form&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;action_title&lt;/span&gt;
   &lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;account&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_object&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;account_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;method&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;POST&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;form&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;action_form&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;form&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;action_form&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_valid&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
                &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="c1"&gt;# If save() raised, the form will a have a non&lt;/span&gt;
                    &lt;span class="c1"&gt;# field error containing an informative message.&lt;/span&gt;
                    &lt;span class="k"&gt;pass&lt;/span&gt;
                &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Success&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;reverse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                        &lt;span class="s1"&gt;&amp;#39;admin:account_account_change&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                       &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pk&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                        &lt;span class="n"&gt;current_app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;admin_site&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&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;HttpResponseRedirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;admin_site&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;each_context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;opts&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&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;_meta&lt;/span&gt;
        &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;form&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;form&lt;/span&gt;
        &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;account&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;account&lt;/span&gt;
        &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;title&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;action_title&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;TemplateResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;admin/account/account_action.html&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Deposit and withdraw are executed in a very similar way. To cary out both actions, we want to render a form in an intermediate page, and execute the action when the user submit the form.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;process_action&lt;/code&gt; handles the form submission for both actions. The function accepts a form, the title of the action, and the account id. &lt;code&gt;process_withdraw&lt;/code&gt; and &lt;code&gt;process_deposit&lt;/code&gt;, are used to set the relevant context for each operation.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;NOTE: There is some Django admin boilerplate here that is required by the Django admin site. No point in digging too deep into it because it's not relevant to us at this point.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;To complete the process, we need a template for the intermediate page that contains the action form. We are going to base our template on an existing template used by Django Admin it self:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="x"&gt;&amp;lt;!-- templates/admin/account/account_action.html --&amp;gt;&lt;/span&gt;

&lt;span class="hll"&gt;&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;extends&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;admin/change_form.html&amp;quot;&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;/span&gt;&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;load&lt;/span&gt; &lt;span class="nv"&gt;i18n&lt;/span&gt; &lt;span class="nv"&gt;admin_static&lt;/span&gt; &lt;span class="nv"&gt;admin_modify&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;

&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;block&lt;/span&gt; &lt;span class="nv"&gt;content&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;

&lt;span class="x"&gt;&amp;lt;div id=&amp;quot;content-main&amp;quot;&amp;gt;&lt;/span&gt;

&lt;span class="x"&gt;  &amp;lt;form action=&amp;quot;&amp;quot; method=&amp;quot;POST&amp;quot;&amp;gt;&lt;/span&gt;

&lt;span class="x"&gt;    &lt;/span&gt;&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;csrf_token&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;span class="x"&gt;    &lt;/span&gt;&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nv"&gt;form.non_field_errors&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nf"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;span class="x"&gt;      &amp;lt;p class=&amp;quot;errornote&amp;quot;&amp;gt;&lt;/span&gt;
&lt;span class="x"&gt;          &amp;quot;Please correct the errors below.&amp;quot;&lt;/span&gt;
&lt;span class="x"&gt;      &amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="x"&gt;      &lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;form.non_field_errors&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;span class="x"&gt;    &lt;/span&gt;&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;endif&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;

&lt;span class="x"&gt;    &amp;lt;fieldset class=&amp;quot;module aligned&amp;quot;&amp;gt;&lt;/span&gt;
&lt;span class="x"&gt;      &lt;/span&gt;&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;field&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nv"&gt;form&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;span class="x"&gt;        &amp;lt;div class=&amp;quot;form-row&amp;quot;&amp;gt;&lt;/span&gt;
&lt;span class="x"&gt;          &lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;field.errors&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;span class="x"&gt;          &lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;field.label_tag&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;span class="x"&gt;          &lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;field&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;span class="x"&gt;          &lt;/span&gt;&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nv"&gt;field.field.help_text&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;span class="x"&gt;          &amp;lt;p class=&amp;quot;help&amp;quot;&amp;gt;&lt;/span&gt;
&lt;span class="x"&gt;            &lt;/span&gt;&lt;span class="cp"&gt;{{&lt;/span&gt; &lt;span class="nv"&gt;field.field.help_text&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nf"&gt;safe&lt;/span&gt; &lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;span class="x"&gt;          &amp;lt;/p&amp;gt;&lt;/span&gt;
&lt;span class="x"&gt;          &lt;/span&gt;&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;endif&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;span class="x"&gt;        &amp;lt;/div&amp;gt;&lt;/span&gt;
&lt;span class="x"&gt;      &lt;/span&gt;&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;endfor&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;span class="x"&gt;    &amp;lt;/fieldset&amp;gt;&lt;/span&gt;

&lt;span class="x"&gt;    &amp;lt;div class=&amp;quot;submit-row&amp;quot;&amp;gt;&lt;/span&gt;
&lt;span class="x"&gt;      &amp;lt;input type=&amp;quot;submit&amp;quot; class=&amp;quot;default&amp;quot; value=&amp;quot;Submit&amp;quot;&amp;gt;&lt;/span&gt;
&lt;span class="x"&gt;    &amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;span class="x"&gt;  &amp;lt;/form&amp;gt;&lt;/span&gt;
&lt;span class="x"&gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;span class="cp"&gt;{%&lt;/span&gt; &lt;span class="k"&gt;endblock&lt;/span&gt; &lt;span class="cp"&gt;%}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;This is it!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Staff members can now easily deposit and withdraw directly from the admin interface. No need to create an expensive dashboard or ssh to the server.&lt;/p&gt;
&lt;p&gt;I promised we will do it in 100 lines and we did it in less!&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="where-can-we-take-it-from-here"&gt;&lt;a class="toclink" href="#where-can-we-take-it-from-here"&gt;Where Can We Take It From Here&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Now that we nailed this technique we can pretty much do whatever we want with it. We have total control over the route and what's being rendered. The next step would be to abstract this functionality and put in a mixin, but, we will cross that bridge when we get there.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="credits"&gt;&lt;a class="toclink" href="#credits"&gt;Credits&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Big parts of the implementation are taken from the excellent (excellent!) package &lt;a href="https://github.com/django-import-export/django-import-export" rel="noopener"&gt;django-import-export&lt;/a&gt;. It saved us hours of "Can you just send me the data in Excel?" and we love it for it. If you are not familiar with it you should definitely &lt;a href="https://github.com/django-import-export/django-import-export" rel="noopener"&gt;check it out&lt;/a&gt;.&lt;/p&gt;</content><category term="articles"></category><category term="Django"></category><category term="Django Admin"></category></entry><entry><title>Bullet Proofing Django Models</title><link href="https://hakibenita.com/bullet-proofing-django-models" rel="alternate"></link><published>2016-10-26T00:00:00+03:00</published><updated>2016-10-26T00:00:00+03:00</updated><author><name>Haki Benita</name></author><id>tag:hakibenita.com,2016-10-26:/bullet-proofing-django-models</id><summary type="html">&lt;p&gt;We recently added a bank account like functionality into one of our products. During the development we encountered some textbook problems and I thought it can be a good opportunity to go over some of the patterns we use in our Django models.&lt;/p&gt;</summary><content type="html">&lt;p&gt;We recently added a bank account like functionality into one of our products. During the development we encountered some textbook problems and I thought it can be a good opportunity to go over some of the patterns we use in our Django models.&lt;/p&gt;
&lt;h3 id="a-bank-account"&gt;&lt;a class="toclink" href="#a-bank-account"&gt;A Bank Account&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;This article was written in the order in which we usually address new problems:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Define the business requirements.&lt;/li&gt;
&lt;li&gt;Write down a naive implementation and model definition.&lt;/li&gt;
&lt;li&gt;Challenge the solution.&lt;/li&gt;
&lt;li&gt;Refine and repeat.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id="business-requirements"&gt;&lt;a class="toclink" href="#business-requirements"&gt;Business Requirements&lt;/a&gt;&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Each user can have only one account but not every user must have one.&lt;/li&gt;
&lt;li&gt;The user can deposit and withdraw from the account up to a certain amount.&lt;/li&gt;
&lt;li&gt;The account balance cannot be negative.&lt;/li&gt;
&lt;li&gt;There is a max limit to the user's account balance.&lt;/li&gt;
&lt;li&gt;The total amount of all balances in the app cannot exceed a certain amount.&lt;/li&gt;
&lt;li&gt;There must be a record for every action on the account.&lt;/li&gt;
&lt;li&gt;Actions on the account can be executed by the user from either the mobile app or the web interface and by support personnel from the admin interface.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now that we have the business requirements we can start with a model definition.&lt;/p&gt;
&lt;h4 id="account-model"&gt;&lt;a class="toclink" href="#account-model"&gt;Account Model&lt;/a&gt;&lt;/h4&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# models.py&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;uuid&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.conf&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Account&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&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;verbose_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Account&amp;#39;&lt;/span&gt;
        &lt;span class="n"&gt;verbose_name_plural&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Accounts&amp;#39;&lt;/span&gt;

    &lt;span class="n"&gt;MAX_TOTAL_BALANCES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10000000&lt;/span&gt;

    &lt;span class="n"&gt;MAX_BALANCE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10000&lt;/span&gt;
    &lt;span class="n"&gt;MIN_BALANCE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

    &lt;span class="n"&gt;MAX_DEPOSIT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;
    &lt;span class="n"&gt;MIN_DEPOSIT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

    &lt;span class="n"&gt;MAX_WITHDRAW&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;
    &lt;span class="n"&gt;MIN_WITHDRAW&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

    &lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AutoField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;primary_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;uid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UUIDField&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="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;editable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uuid4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;verbose_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Public identifier&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OneToOneField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AUTH_USER_MODEL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;on_delete&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PROTECT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;created&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DateTimeField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;blank&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;modified&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DateTimeField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;blank&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PositiveIntegerField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;verbose_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Current balance&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Let's break it down:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;We use two unique identifiers&lt;/strong&gt;  -  A private identifier which is an auto generated number (id) and a public id which is a uuid (uid). It's a good idea to &lt;strong&gt;keep enumerators private&lt;/strong&gt;  - They expose important information about our data such as how many accounts we have and we don't want that.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;We use OneToOneField for the user&lt;/strong&gt; - It's like a ForeignKey but with a unique constraint. This ensures a user cannot have more than one account.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;We set &lt;code&gt;on_delete=models.PROTECT&lt;/code&gt;&lt;/strong&gt; - Starting with Django 2.0 &lt;a href="https://docs.djangoproject.com/en/1.10/ref/models/fields/#foreignkey" rel="noopener"&gt;this argument will be mandatory&lt;/a&gt;. The default is CASCADE - when the user is deleted the related account is deleted as well. In our case this doesn't make sense - imagine the bank "deleting your money" when you close an account. Setting on_delete=models.PROTECT will raise an IntegrityError when attempting to delete a user with an account.&lt;/li&gt;
&lt;li&gt;You probably noticed that the code is very... "vertical". Wwe write like that because it makes &lt;strong&gt;git diffs look nicer.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="account-action-model"&gt;&lt;a class="toclink" href="#account-action-model"&gt;Account Action Model&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Now that we have an account model we can create a model to log actions made to the account:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# models.py&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&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;verbose_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Account Action&amp;#39;&lt;/span&gt;
        &lt;span class="n"&gt;verbose_name_plural&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Account Actions&amp;#39;&lt;/span&gt;

    &lt;span class="n"&gt;ACTION_TYPE_CREATED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;CREATED&amp;#39;&lt;/span&gt;
    &lt;span class="n"&gt;ACTION_TYPE_DEPOSITED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;DEPOSITED&amp;#39;&lt;/span&gt;
    &lt;span class="n"&gt;ACTION_TYPE_WITHDRAWN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;WITHDRAWN&amp;#39;&lt;/span&gt;
    &lt;span class="n"&gt;ACTION_TYPE_CHOICES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ACTION_TYPE_CREATED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Created&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ACTION_TYPE_DEPOSITED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Deposited&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ACTION_TYPE_WITHDRAWN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Withdrawn&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;REFERENCE_TYPE_BANK_TRANSFER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;BANK_TRANSFER&amp;#39;&lt;/span&gt;
    &lt;span class="n"&gt;REFERENCE_TYPE_CHECK&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;CHECK&amp;#39;&lt;/span&gt;
    &lt;span class="n"&gt;REFERENCE_TYPE_CASH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;CASH&amp;#39;&lt;/span&gt;
    &lt;span class="n"&gt;REFERENCE_TYPE_NONE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;NONE&amp;#39;&lt;/span&gt;
    &lt;span class="n"&gt;REFERENCE_TYPE_CHOICES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;REFERENCE_TYPE_BANK_TRANSFER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Bank Transfer&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;REFERENCE_TYPE_CHECK&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Check&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;REFERENCE_TYPE_CASH&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Cash&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;REFERENCE_TYPE_NONE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;None&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AutoField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;primary_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;user_friendly_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CharField&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="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;editable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&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;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AUTH_USER_MODEL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;on_delete&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PROTECT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;help_text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;User who performed the action.&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;created&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DateTimeField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;blank&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;account&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;Account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&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;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;choices&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ACTION_TYPE_CHOICES&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;delta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IntegerField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;help_text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Balance delta.&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;reference&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TextField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;blank&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;reference_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&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;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;choices&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;REFERENCE_TYPE_CHOICES&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;REFERENCE_TYPE_NONE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;comment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TextField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;blank&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Fields used solely for debugging purposes.&lt;/span&gt;

    &lt;span class="n"&gt;debug_balance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IntegerField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;help_text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Balance after the action.&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;What do we have here?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Each record will hold a reference to the associated balance and the delta amount. A deposit of 100$ will have a delta of 100$, and a withdrawal of 50$ will have a delta of -50$. This way we can &lt;strong&gt;sum the deltas of all actions made to an account and get the current balance&lt;/strong&gt;. This is important for validating our calculated balance.&lt;/li&gt;
&lt;li&gt;We follow the same pattern of adding two identifiers - a private and a public one. The difference here is that reference numbers for actions are often used by users and support personnel to identify a specific action over the phone or in emails. &lt;strong&gt;A uuid is not user friendly&lt;/strong&gt;- it's very long and it's not something users are used to see. I found a nice implementation of user-friendly ID's in &lt;a href="https://github.com/simonluijk/django-invoice/blob/master/invoice/utils/friendly_id.py" rel="noopener"&gt;django-invoice.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Two of the fields are only relevant for one type of action, deposit - reference and reference type. There are a lot of ways to tackle this issue - table inheritance and down casting, JSON fields, table polymorphism and the list of &lt;strong&gt;overly complicated solutions&lt;/strong&gt; goes on. In our case we are going to &lt;strong&gt;use a sparse table&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Note about the design: Maintaining &lt;strong&gt;calculated fields in the model is usually bad design&lt;/strong&gt;. Calculated fields such as the account's balance should be avoided whenever possible.&lt;/p&gt;
&lt;p&gt;However, in our "real life" implementation there are additional action types and thousands of actions on each account - we &lt;strong&gt;treat calculated attribute as an optimization&lt;/strong&gt;. Maintaining state poses some interesting challenges and we thought it can serve the purpose of this post so we decided to present it as well.&lt;/p&gt;
&lt;h3 id="challenges"&gt;&lt;a class="toclink" href="#challenges"&gt;Challenges&lt;/a&gt;&lt;/h3&gt;
&lt;h4 id="multiple-platforms"&gt;&lt;a class="toclink" href="#multiple-platforms"&gt;Multiple Platforms&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;We have three client applications we need to support:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Mobile app  - Uses an API interface to manage the account.&lt;/li&gt;
&lt;li&gt;Web client  - Uses either an API interface (if we have some sort of SPA), or good old server side rendering with Django forms.&lt;/li&gt;
&lt;li&gt;Admin interface  - Uses Django's admin module with Django forms.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Our motivation is to keep things DRY and self contained as possible.&lt;/p&gt;
&lt;h4 id="validation"&gt;&lt;a class="toclink" href="#validation"&gt;Validation&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;We have two types of validations hiding in the business requirements:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Input validation&lt;/strong&gt; such as "amount must be between X and Y", "balance cannot exceed Z", etc - these types of validation are well supported by Django and can usually be expressed as database constraints or django validations.&lt;/p&gt;
&lt;p&gt;The second validation is a bit more complicated. We need to ensure the total amount of all balances in the entire system does not exceed a certain amount. This forces us to &lt;strong&gt;validate an instance against all other instances of the model&lt;/strong&gt;.&lt;/p&gt;
&lt;h4 id="atomicity"&gt;&lt;a class="toclink" href="#atomicity"&gt;Atomicity&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Race conditions are a very common issue in distributed systems and ever more so in models that maintain state such as bank account (you can read more about &lt;a href="https://en.wikipedia.org/wiki/Race_condition" rel="noopener"&gt;race conditions in Wikipedia&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;To illustrate the problem consider an account with a balance of 100$. The user connects from two different devices at the exact same time and issue a withdraw of 100$. Since the two actions were executed at the exact same time it is possible that both of them get a current balance of 100$. Given that both session see sufficient balance they will both get approved and update the new balance to 0$. The user withdrawn a total of 200$ and the current balance is now 0$ - &lt;strong&gt;we have a race condition&lt;/strong&gt; and we are down 100$.&lt;/p&gt;
&lt;h4 id="logging-history"&gt;&lt;a class="toclink" href="#logging-history"&gt;Logging / History&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;The log serves two purposes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Log and Audit &lt;/strong&gt;- Information about historical actions - dates, amounts, users etc.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Check Consistency &lt;/strong&gt;- We maintain state in the model so we want to be able to validate the calculated balance by aggregating the action deltas.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The history records must be 100% immutable.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="the-naive-implementation"&gt;&lt;a class="toclink" href="#the-naive-implementation"&gt;The Naive Implementation&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Let's start with a naive implementation of deposit (this is &lt;em&gt;not&lt;/em&gt; a good implementation):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Account&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="c1"&gt;# ...&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;deposit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;deposited_by&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;asof&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MIN_DEPOSIT&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MAX_DEPOSIT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;InvalidAmount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MAX_BALANCE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;ExceedsLimit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Account&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;aggregate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;balance&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;total&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MAX_TOTAL_BALANCES&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;ExceedsLimit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="hll"&gt;        &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;actions&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;            &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;deposited_by&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Action&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ACTION_TYPE_DEPOSITED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;delta&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;asof&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;asof&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="hll"&gt;    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;
&lt;/span&gt;    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;modified&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;asof&lt;/span&gt;

&lt;span class="hll"&gt;    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;And let's add a simple endpoint for it using DRF @api_view:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# api.py&lt;/span&gt;

&lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.db.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt;
&lt;span class="c1"&gt;# ...&lt;/span&gt;

&lt;span class="nd"&gt;@api_view&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;POST&amp;#39;&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;deposit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;\&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;amount&amp;#39;&lt;/span&gt;\&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ne"&gt;KeyError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ne"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTP_400_BAD_REQUEST&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="hll"&gt;    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;atomic&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;account&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;Account&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;
&lt;span class="hll"&gt;                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;select_for_update&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;Account&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DoesNotExist&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTP_404_NOT_FOUND&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="hll"&gt;            &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deposit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;                &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;                &lt;span class="n"&gt;deposited_by&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;                &lt;span class="n"&gt;asof&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;            &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ExceedsLimit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;InvalidAmount&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTP_400_BAD_REQUEST&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTP_200_OK&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;So what is the problem?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Locking the account&lt;/strong&gt;  -  An instance cannot lock itself because it had already been fetched. We gave up control over the locking and fetching so we have to trust the caller to properly obtain a lock - &lt;strong&gt;this is very bad design&lt;/strong&gt;. Don't take my word for it, let's take a glimpse at &lt;a href="https://docs.djangoproject.com/en/1.10/misc/design-philosophies/" rel="noopener"&gt;Django's design philosophy&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Loose coupling
&lt;/strong&gt;A fundamental goal of Django's stack is loose coupling and tight cohesion. The various layers of the framework shouldn't "know" about each other unless absolutely necessary.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So is it really the business of our API, forms and django admin to fetch the account for us and obtain a proper lock? &lt;em&gt;I think not&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Validation&lt;/strong&gt;  -  The account has to validate itself against all other accounts - this just feels awkward.&lt;/p&gt;
&lt;h3 id="a-better-approach"&gt;&lt;a class="toclink" href="#a-better-approach"&gt;A Better Approach&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;We need to hook into the process before the account is fetched (to obtain a lock) and in a place where it makes sense to validate and process more than one account.&lt;/p&gt;
&lt;p&gt;Let's start with a function to create an Action instance and write it as a &lt;code&gt;classmethod&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# models.py&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.core.exceptions&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ValidationError&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="c1"&gt;# ...&lt;/span&gt;

&lt;span class="hll"&gt;    &lt;span class="nd"&gt;@classmethod&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;        &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;        &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;asof&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;reference&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;reference_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;comment&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Create Action.&lt;/span&gt;

&lt;span class="sd"&gt;        user (User):&lt;/span&gt;
&lt;span class="sd"&gt;            User who executed the action.&lt;/span&gt;
&lt;span class="sd"&gt;        account (Account):&lt;/span&gt;
&lt;span class="sd"&gt;            Account the action executed on.&lt;/span&gt;
&lt;span class="sd"&gt;        type (str, one of Action.ACTION_TYPE_\*):&lt;/span&gt;
&lt;span class="sd"&gt;            Type of action.&lt;/span&gt;
&lt;span class="sd"&gt;        delta (int):&lt;/span&gt;
&lt;span class="sd"&gt;            Change in balance.&lt;/span&gt;
&lt;span class="sd"&gt;        asof (datetime.datetime):&lt;/span&gt;
&lt;span class="sd"&gt;            When was the action executed.&lt;/span&gt;
&lt;span class="sd"&gt;        reference (str or None):&lt;/span&gt;
&lt;span class="sd"&gt;            Reference number when appropriate.&lt;/span&gt;
&lt;span class="sd"&gt;        reference_type(str or None):&lt;/span&gt;
&lt;span class="sd"&gt;            Type of reference.&lt;/span&gt;
&lt;span class="sd"&gt;            Defaults to &amp;quot;NONE&amp;quot;.&lt;/span&gt;
&lt;span class="sd"&gt;        comment (str or None):&lt;/span&gt;
&lt;span class="sd"&gt;            Optional comment on the action.&lt;/span&gt;

&lt;span class="sd"&gt;        Raises:&lt;/span&gt;
&lt;span class="sd"&gt;            ValidationError&lt;/span&gt;

&lt;span class="sd"&gt;        Returns (Action)&lt;/span&gt;
&lt;span class="sd"&gt;        &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;asof&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ACTION_TYPE_DEPOSITED&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt;
            &lt;span class="n"&gt;reference_type&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ValidationError&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
                &lt;span class="s1"&gt;&amp;#39;reference_type&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;required for deposit.&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;})&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;reference_type&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;reference_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;REFERENCE_TYPE_NONE&lt;/span&gt;

        &lt;span class="c1"&gt;# Don&amp;#39;t store null in text field.&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;reference&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;reference&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;comment&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;comment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;

        &lt;span class="n"&gt;user_friendly_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;generate_user_friendly_id&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="hll"&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;            &lt;span class="n"&gt;user_friendly_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;user_friendly_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;created&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;asof&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;delta&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;reference&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;reference&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;reference_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;reference_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;comment&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;comment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;debug_balance&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;balance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;What do we have here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We used a classmethod that accepts all necessary data to validate and create the new instance. By *not* using the default manager's create function (Action.objects.create) we &lt;strong&gt;encapsulate all the business logic in the creation process&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;We easily introduced &lt;strong&gt;custom validation&lt;/strong&gt; and raise proper ValidationError.&lt;/li&gt;
&lt;li&gt;We accept the creation time as an argument. That might seem a bit strange at first glance - why not use the built in auto_time_add? For starters &lt;strong&gt;It's much easier to test with predictable values&lt;/strong&gt;. Second, as we are going to see in just a bit, we can make sure the modified time of the account is exactly the same as the action created time.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Before moving over to the implementation of the account methods let's define &lt;strong&gt;custom exceptions&lt;/strong&gt; for our Account module:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# errors.py&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ne"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ExceedsLimit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;InvalidAmount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__str__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Invalid Amount: &lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&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;InsufficientFunds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;balance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;balance&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__str__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;amount: &lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s1"&gt;, current balance: &lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;balance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We &lt;strong&gt;define a base Error class&lt;/strong&gt; that inherits from Exception. This is something we found very useful and we use it a lot. A base error class allows us to catch all errors coming from a certain module:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;account.errors&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Error&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;AccountError&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
   &lt;span class="c1"&gt;# action on account&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;AccountError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
   &lt;span class="c1"&gt;# Handle all errors from account&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;A &lt;a href="https://github.com/kennethreitz/requests/blob/master/requests/exceptions.py#L12" rel="noopener"&gt;similar pattern&lt;/a&gt; can be found in the popular requests package.&lt;/p&gt;
&lt;p&gt;Let's implement the method to create a new Account:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Account&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="c1"&gt;# ...&lt;/span&gt;

&lt;span class="hll"&gt;    &lt;span class="nd"&gt;@classmethod&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;created_by&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;asof&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Create account.&lt;/span&gt;

&lt;span class="sd"&gt;        user (User):&lt;/span&gt;
&lt;span class="sd"&gt;            Owner of the account.&lt;/span&gt;
&lt;span class="sd"&gt;        created_by (User):&lt;/span&gt;
&lt;span class="sd"&gt;            User that created the account.&lt;/span&gt;
&lt;span class="sd"&gt;        asof (datetime.datetime):&lt;/span&gt;
&lt;span class="sd"&gt;            Time of creation.&lt;/span&gt;

&lt;span class="sd"&gt;        Returns (tuple):&lt;/span&gt;
&lt;span class="sd"&gt;            [0] Account&lt;/span&gt;
&lt;span class="sd"&gt;            [1] Action&lt;/span&gt;
&lt;span class="sd"&gt;        &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="hll"&gt;        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;atomic&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;            &lt;span class="n"&gt;account&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;                &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;created&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;asof&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;modified&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;asof&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;balance&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="hll"&gt;            &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Action&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;                &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;created_by&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Action&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ACTION_TYPE_CREATED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;delta&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;asof&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;asof&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="hll"&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Pretty straight forward - create the instance, create the action and return them both.&lt;/p&gt;
&lt;p&gt;Notice how we accept asof here as well - modified, created and the action creation time are all equal - you cant do that with auto_add and auto_add_now.&lt;/p&gt;
&lt;p&gt;Now to the business logic:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# models.py&lt;/span&gt;

&lt;span class="hll"&gt;&lt;span class="nd"&gt;@classmethod&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;deposit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;    &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;deposited_by&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;asof&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;comment&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Deposit to account.&lt;/span&gt;

&lt;span class="sd"&gt;    uid (uuid.UUID):&lt;/span&gt;
&lt;span class="sd"&gt;        Account public identifier.&lt;/span&gt;
&lt;span class="sd"&gt;    deposited_by (User):&lt;/span&gt;
&lt;span class="sd"&gt;        Deposited by.&lt;/span&gt;
&lt;span class="sd"&gt;    amount (positive int):&lt;/span&gt;
&lt;span class="sd"&gt;        Amount to deposit.&lt;/span&gt;
&lt;span class="sd"&gt;    asof (datetime.datetime):&lt;/span&gt;
&lt;span class="sd"&gt;        Time of deposit.&lt;/span&gt;
&lt;span class="sd"&gt;    comment(str or None):&lt;/span&gt;
&lt;span class="sd"&gt;       Optional comment.&lt;/span&gt;

&lt;span class="sd"&gt;    Raises&lt;/span&gt;
&lt;span class="sd"&gt;        Account.DoesNotExist&lt;/span&gt;
&lt;span class="sd"&gt;        InvalidAmount&lt;/span&gt;
&lt;span class="sd"&gt;        ExceedsLimit&lt;/span&gt;

&lt;span class="sd"&gt;    Returns (tuple):&lt;/span&gt;
&lt;span class="sd"&gt;        [0] (Account) Updated account instance.&lt;/span&gt;
&lt;span class="sd"&gt;        [1] (Action) Deposit action.&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

&lt;span class="hll"&gt;    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;atomic&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;        &lt;span class="n"&gt;account&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;select_for_update&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MIN_DEPOSIT&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MAX_DEPOSIT&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InvalidAmount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MAX_BALANCE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ExceedsLimit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;aggregate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;balance&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;\&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;total&amp;#39;&lt;/span&gt;\&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MAX_TOTAL_BALANCES&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ExceedsLimit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="hll"&gt;        &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;
&lt;/span&gt;        &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;modified&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;asof&lt;/span&gt;

&lt;span class="hll"&gt;        &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;update_fields&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;            &lt;span class="s1"&gt;&amp;#39;balance&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;modified&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="hll"&gt;        &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Action&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;            &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;deposited_by&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Action&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ACTION_TYPE_DEPOSITED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;delta&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;asof&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;asof&lt;/span&gt;&lt;span class="p"&gt;,&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;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;

&lt;span class="hll"&gt;&lt;span class="nd"&gt;@classmethod&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;withdraw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;    &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;withdrawn_by&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;asof&lt;/span&gt;
    &lt;span class="n"&gt;comment&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Withdraw from account.&lt;/span&gt;

&lt;span class="sd"&gt;    uid (uuid.UUID):&lt;/span&gt;
&lt;span class="sd"&gt;        Account public identifier.&lt;/span&gt;
&lt;span class="sd"&gt;    withdrawn_by (User):&lt;/span&gt;
&lt;span class="sd"&gt;        The withdrawing user.&lt;/span&gt;
&lt;span class="sd"&gt;    amount (positive int):&lt;/span&gt;
&lt;span class="sd"&gt;        Amount to withdraw.&lt;/span&gt;
&lt;span class="sd"&gt;    asof (datetime.datetime):&lt;/span&gt;
&lt;span class="sd"&gt;        Time of withdraw.&lt;/span&gt;
&lt;span class="sd"&gt;    comment (str or None):&lt;/span&gt;
&lt;span class="sd"&gt;       Optional comment.&lt;/span&gt;

&lt;span class="sd"&gt;    Raises:&lt;/span&gt;
&lt;span class="sd"&gt;        Account.DoesNotExist&lt;/span&gt;
&lt;span class="sd"&gt;        InvalidAmount&lt;/span&gt;
&lt;span class="sd"&gt;        InsufficientFunds&lt;/span&gt;

&lt;span class="sd"&gt;    Returns (tuple):&lt;/span&gt;
&lt;span class="sd"&gt;        [0] (Account) Updated account instance.&lt;/span&gt;
&lt;span class="sd"&gt;        [1] (Action) Withdraw action.&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

&lt;span class="hll"&gt;    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;atomic&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;        &lt;span class="n"&gt;account&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;select_for_update&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MIN_WITHDRAW&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MAX_WITHDRAW&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;InvalidAmount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MIN_BALANCE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="n"&gt;InsufficientFunds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;balance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="hll"&gt;        &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;balance&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;
&lt;/span&gt;        &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;modified&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;asof&lt;/span&gt;
&lt;span class="hll"&gt;
&lt;/span&gt;        &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;update_fields&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;balance&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;modified&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="hll"&gt;        &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Action&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;            &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;withdrawn_by&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Action&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ACTION_TYPE_WITHDRAWN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;delta&lt;/span&gt;&lt;span class="o"&gt;=-&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;asof&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;asof&lt;/span&gt;&lt;span class="p"&gt;,&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;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;We can start to see the pattern here:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Acquire a lock on the account&lt;/strong&gt; using select_for_update. This will lock the account row in the database and make sure no one can update the account instance until the transaction is completed (either committed or rolled-back).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Perform validation checks&lt;/strong&gt; and raise proper exceptions - raising an exception will cause the transaction to rollback.&lt;/li&gt;
&lt;li&gt;If all the validations passed &lt;strong&gt;update the state (&lt;/strong&gt;current balance), set the modification time, save the instance and &lt;strong&gt;create the log&lt;/strong&gt; (action).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;So how does the model hold up to our challenges?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Multiple Platforms and Validation&lt;/strong&gt; &lt;strong&gt;- &lt;/strong&gt;We encapsulated all of our business logic including input and system wide validation inside the model method so consumers such as API, admin action or forms only need to handle exceptions and serialization / UI.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Atomicity - &lt;/strong&gt;Each method obtains its own lock so there is no risk of race condition.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Logging / History - &lt;/strong&gt;We created an action model and made sure each function registers the proper action.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Profit!&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="testing"&gt;&lt;a class="toclink" href="#testing"&gt;Testing&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Our app will be incomplete without proper tests. I previously wrote about &lt;a href="/keeping-tests-dry-with-class-based-tests-in-python"&gt;class based testings&lt;/a&gt; - we are going to take a slightly different approach but still have a base class with utility functions:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# tests/common.py&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TestAccountBase&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;DEFAULT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="nd"&gt;@classmethod&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default_value&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;default_value&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DEFAULT&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;

    &lt;span class="nd"&gt;@classmethod&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;setUpTestData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setUpTestData&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="c1"&gt;# Set up some default values&lt;/span&gt;
        &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;admin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_superuser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;Admin&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;admin&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;admin@testing.test&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_A&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;user_A&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;user_A&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;A@testing.test&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nd"&gt;@classmethod&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;DEFAULT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;created_by&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;DEFAULT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;asof&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;DEFAULT&lt;/span&gt;
    &lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_A&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;created_by&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;created_by&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;asof&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;asof&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

        &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Account&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;created_by&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;asof&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;deposit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;DEFAULT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;deposited_by&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;DEFAULT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;asof&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;DEFAULT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;comment&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;DEFAULT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;account&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;deposited_by&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;deposited_by&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;asof&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;asof&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="n"&gt;comment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;comment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;deposit comment&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Account&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deposit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;deposited_by&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;deposited_by&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;asof&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;asof&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Action&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ACTION_TYPE_DEPOSITED&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertIsNotNone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_friendly_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;created&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;asof&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;deposited_by&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;action&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;withdraw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;DEFAULT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;withdrawn_by&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;DEFAULT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;asof&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;DEFAULT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;comment&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;DEFAULT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;account&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;withdrawn_by&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;withdrawn_by&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;asof&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;asof&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="n"&gt;comment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;comment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;withdraw comment&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Account&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;withdraw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;withdrawn_by&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;withdrawn_by&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;asof&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;asof&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Action&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ACTION_TYPE_WITHDRAWN&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertIsNotNone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_friendly_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;created&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;asof&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;withdrawn_by&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;action&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;To make testing easier to write we use utility functions to reduce the boilerplate of specifying the user, the account etc each time by providing default values and operating on self.account.&lt;/p&gt;
&lt;p&gt;Lets use our base class to write some tests:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# tests/test_account.py&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;unittest&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;mock&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.test&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TestCase&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;.common&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TestAccoutBase&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;..models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Action&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;..errors&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;InvalidAmount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ExceedsLimit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;InsuficientFunds&lt;/span&gt;&lt;span class="p"&gt;,&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;TestAccount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TestAccountBase&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;setUp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
 &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_should_start_with_zero_balance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;balance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&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_should_deposit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deposit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;balance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deposit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;balance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;250&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_should_fail_to_deposit_less_than_minimum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertRaises&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;InvalidAmount&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deposit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Account&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MIN_DEPOSIT&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;balance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&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_should_fail_to_deposit_more_than_maximum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertRaises&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;InvalidAmount&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deposit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Account&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MAX_DEPOSIT&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;balance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nd"&gt;@mock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;account.models.Account.MAX_BALANCE&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nd"&gt;@mock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;account.models.Account.MAX_DEPOSIT&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;502&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_should_fail_to_deposit_more_than_max_balance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertRaises&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ExceedsLimit&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deposit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;501&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;balance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nd"&gt;@mock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;account.models.Account.MAX_BALANCE&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nd"&gt;@mock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;account.models.Account.MAX_DEPOSIT&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nd"&gt;@mock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;account.models.Account.MAX_TOTAL_BALANCES&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;600&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_should_fail_when_exceed_max_total_balances&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

        &lt;span class="c1"&gt;# Exceed max total balances for the same account&lt;/span&gt;

        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deposit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertRaises&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ExceedsLimit&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deposit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;balance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Exceed max total balances in other account&lt;/span&gt;

        &lt;span class="n"&gt;other_user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;foo&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;bar&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;baz&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;other_account&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;other_user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertRaises&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ExceedsLimit&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deposit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;other_account&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;other_account&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;balance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&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_should_withdraw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deposit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;withdraw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;balance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;withdraw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;balance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&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_should_fail_when_insufficient_funds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deposit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertRaises&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;InsufficientFunds&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;withdraw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;101&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;account&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;balance&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;h3 id="final-words"&gt;&lt;a class="toclink" href="#final-words"&gt;Final Words&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The classmethod approach has proved itself in our development for quite some time now. We found that it provides the necessary &lt;strong&gt;flexibility, readability and testability with very little overhead&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;In this article we presented two common issues we encounter frequently - validation and concurrency. This method can be extended to handle &lt;strong&gt;access control&lt;/strong&gt; (permissions) and &lt;strong&gt;caching&lt;/strong&gt; (we have total control over the fetch, remember?), &lt;strong&gt;performance optimization&lt;/strong&gt; (use select_related and update_fields...), &lt;strong&gt;audit and monitoring&lt;/strong&gt; and additional business logic.&lt;/p&gt;
&lt;p&gt;We usually support several interfaces for each model - admin interface for support, API for mobile and SPA clients, and a dashboard. Encapsulating the business logic inside the model reduced the amount of code duplication and required tests which leads to overall &lt;strong&gt;quality code that is easy to maintain&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;In a follow up post I (might) present the admin interface for this model with some neat tricks (such as custom actions, intermediate pages etc) and possibly an RPC implementation using DRF to interact with the account as an API.&lt;/p&gt;</content><category term="articles"></category><category term="Django"></category><category term="ORM"></category></entry><entry><title>Keeping Tests DRY with Class Based Tests In Python</title><link href="https://hakibenita.com/keeping-tests-dry-with-class-based-tests-in-python" rel="alternate"></link><published>2016-08-20T00:00:00+03:00</published><updated>2016-08-20T00:00:00+03:00</updated><author><name>Haki Benita</name></author><id>tag:hakibenita.com,2016-08-20:/keeping-tests-dry-with-class-based-tests-in-python</id><summary type="html">&lt;p&gt;Tests can be a bummer to write but even a bigger nightmare to maintain. When we noticed we are putting off simple tasks just because we were afraid to update some monster test case, we started looking for more creative ways to simplify the process of writing and maintaining tests. In this article I will describe a class based approach to writing tests.&lt;/p&gt;</summary><content type="html">&lt;hr&gt;
&lt;p&gt;Tests can be a bummer to write but even a bigger nightmare to maintain. When we noticed we are putting off simple tasks just because we were afraid to update some monster test case, we started looking for more creative ways to simplify the process of writing and maintaining tests.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;In this article I will describe a class based approach to writing tests.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Before we start writing code let's set some goals:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Extensive&lt;/strong&gt; - We want our tests to cover as many scenarios as possible. We hope a solid platform for writing tests will make it easier for us to adapt to changes and cover more grounds.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Expressive&lt;/strong&gt; - Good tests tell a story. Issues become irrelevant and documents get lost but tests must always pass - this is why &lt;strong&gt;we treat our tests as specs&lt;/strong&gt;. Writing good tests can help newcomers (and future self) to understand all the edge cases and micro-decisions made during development.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Maintainable&lt;/strong&gt; - As requirements and implementations change we want to adapt quickly with as little effort as possible.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="enter-class-based-tests"&gt;&lt;a class="toclink" href="#enter-class-based-tests"&gt;Enter Class Based Tests&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Articles and tutorials about testing always give simple examples such as &lt;code&gt;add&lt;/code&gt; and &lt;code&gt;sub&lt;/code&gt;. I rarely have the pleasure of testing such simple functions. I'll take a more realistic example and test an API endpoint that does login:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="go"&gt;POST /api/account/login&lt;/span&gt;
&lt;span class="go"&gt;{&lt;/span&gt;
&lt;span class="go"&gt;    username: &amp;lt;str&amp;gt;,&lt;/span&gt;
&lt;span class="go"&gt;    password: &amp;lt;str&amp;gt;&lt;/span&gt;
&lt;span class="go"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The scenarios we want to test are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;User logins successfully.&lt;/li&gt;
&lt;li&gt;User does not exist.&lt;/li&gt;
&lt;li&gt;Incorrect password.&lt;/li&gt;
&lt;li&gt;Missing or malformed data.&lt;/li&gt;
&lt;li&gt;User already authenticated.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The input to our test is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A payload, &lt;code&gt;username&lt;/code&gt; and &lt;code&gt;password&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The client performing the action, anonymous or authenticated.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The output we want to test is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The return value, error or payload.&lt;/li&gt;
&lt;li&gt;The response status code.&lt;/li&gt;
&lt;li&gt;Side effects. For example, last login date after successful login.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;After properly defining the input and output, we can write a base test class:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;unittest&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;TestCase&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;requests&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TestLogin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Base class for testing login endpoint.&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;

    &lt;span class="nd"&gt;@property&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Session&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="nd"&gt;@property&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;username&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="ne"&gt;NotImplementedError&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="nd"&gt;@property&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;password&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="ne"&gt;NotImplementedError&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="nd"&gt;@property&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;username&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;password&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;expected_status_code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
    &lt;span class="n"&gt;expected_return_payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;setUp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/api/account/login&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;payload&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_should_return_expected_status_code&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;expected_status_code&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_should_return_expected_payload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;expected_return_payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;We defined the input, &lt;code&gt;client&lt;/code&gt; and &lt;code&gt;payload&lt;/code&gt;, and the expected output &lt;code&gt;expected_*&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;We performed the login action during test &lt;code&gt;setUp&lt;/code&gt;. To let specific test cases access the result, we kept the response on the class instance.&lt;/li&gt;
&lt;li&gt;We implemented two common test cases:&lt;ul&gt;
&lt;li&gt;Test the expected status code.&lt;/li&gt;
&lt;li&gt;Test the expected return value.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The observant reader might notice we raise a &lt;code&gt;NotImplementedError&lt;/code&gt; exception from the properties. This way, if the test author forgets to set one of the required values for the test, they get a useful exception.&lt;/p&gt;
&lt;p&gt;Lets use our &lt;code&gt;TestLogin&lt;/code&gt; class to write a test for a successful login:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TestSuccessfulLogin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TestLogin&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&gt;    &lt;span class="n"&gt;username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Haki&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;correct-password&amp;#39;&lt;/span&gt;
    &lt;span class="n"&gt;expected_status_code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
    &lt;span class="n"&gt;expected_return_payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;username&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Haki&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;full_name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Haki Benita&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_should_update_last_login_date_in_user_model&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;objects&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertIsNotNone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;last_login_date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;By just reading the code we can tell that a &lt;code&gt;username&lt;/code&gt; and &lt;code&gt;password&lt;/code&gt; are sent. We expect a response with a 200 status code, and additional data about the user. We extended the test to also check the &lt;code&gt;last_login_date&lt;/code&gt; in our user model. This specific test might not be relevant to all test cases, so we add it only to the successful test case.&lt;/p&gt;
&lt;p&gt;Lets test a failed login scenario:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TestInvalidPassword&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TestLogin&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="n"&gt;username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Haki&amp;#39;&lt;/span&gt;
    &lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;wrong-password&amp;#39;&lt;/span&gt;
    &lt;span class="n"&gt;expected_status_code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TestMissingPassword&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TestLogin&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="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;username&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Haki&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;expected_status_code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TestMalformedData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TestLogin&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="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;username&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;
    &lt;span class="n"&gt;expected_status_code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;A developer that stumbles upon this piece of code will be able to tell exactly what should happen for any type of input. The name of the class describe the scenario, and the names of the attributes describe the input. Together, &lt;strong&gt;the class tells a story which is easy to read and understand&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The last two tests set the payload directly (without setting username and password). This won't raise a NotImplementedError because we override the payload property directly, which is the one calling username and password.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A good test should help you find where the problem is.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Let's see the output of a failed test case:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="go"&gt;FAIL: test_should_return_expected_status_code (tests.test_login.TestInvalidPassword)&lt;/span&gt;
&lt;/span&gt;&lt;span class="go"&gt;------------------------------------------------------&lt;/span&gt;
&lt;span class="go"&gt;Traceback (most recent call last):&lt;/span&gt;
&lt;span class="go"&gt;  File &amp;quot;../tests/test_login.py&amp;quot;, line 28, in test_should_return_expected_status_code&lt;/span&gt;
&lt;span class="go"&gt;    self.assertEqual(self.response.status_code, self.expected_status_code)&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="go"&gt;AssertionError: 400 != 401&lt;/span&gt;
&lt;/span&gt;&lt;span class="go"&gt;------------------------------------------------------&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Looking at the failed test report, it is clear what went wrong. When the password is invalid we expect status code 401, but we received 400.&lt;/p&gt;
&lt;p&gt;Let's make things a bit harder, and test an authenticated user attempting to login:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TestAuthenticatedUserLogin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TestLogin&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="n"&gt;username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Haki&amp;#39;&lt;/span&gt;
    &lt;span class="n"&gt;password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;correct-password&amp;#39;&lt;/span&gt;

    &lt;span class="nd"&gt;@property&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Haki&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;correct-password&amp;#39;&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;session&lt;/span&gt;

    &lt;span class="n"&gt;expected_status_code&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This time we had to override the client property to authenticate the session.&lt;/p&gt;
&lt;h3 id="putting-our-test-to-the-test"&gt;&lt;a class="toclink" href="#putting-our-test-to-the-test"&gt;Putting Our Test To The Test&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To illustrate how resilient our new test cases are lets see how we can modify the base class as we introduce new requirements and changes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We have made some refactoring and the &lt;strong&gt;endpoint changed&lt;/strong&gt; to &lt;code&gt;/api/user/login&lt;/code&gt;:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TestLogin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# ...&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;setUp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="hll"&gt;            &lt;span class="s1"&gt;&amp;#39;/api/user/login&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;            &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;Someone decided it can speed things up if we &lt;strong&gt;use a different serialization format&lt;/strong&gt; (msgpack, xml, yaml):&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TestLogin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# ...&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;setUp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;/api/account/login&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="hll"&gt;            &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;        &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;The product guys want to go global, and now we need to test &lt;strong&gt;different languages&lt;/strong&gt;:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TestLogin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;language&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;en&amp;#39;&lt;/span&gt;

    &lt;span class="c1"&gt;# ...&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;setUp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="hll"&gt;            &lt;span class="s1"&gt;&amp;#39;/&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s1"&gt;/api/account/login&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;language&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;            &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;None of the changes above managed to break our existing tests.&lt;/p&gt;
&lt;h3 id="taking-it-a-step-further"&gt;&lt;a class="toclink" href="#taking-it-a-step-further"&gt;Taking it a Step Further&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;A few things to consider when employing this technique.&lt;/p&gt;
&lt;h4 id="speed-things-up"&gt;&lt;a class="toclink" href="#speed-things-up"&gt;Speed Things Up&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;setUp&lt;/code&gt; is executed for each test case in the class (test cases are the functions beginning with &lt;code&gt;test_*&lt;/code&gt;). To speed things up, it is &lt;strong&gt;better to perform the action in &lt;code&gt;setUpClass&lt;/code&gt;&lt;/strong&gt;. This changes a few things. For example, the properties we used should be set as attributes on the class or as &lt;code&gt;@classmethod&lt;/code&gt;s.&lt;/p&gt;
&lt;h4 id="using-fixtures"&gt;&lt;a class="toclink" href="#using-fixtures"&gt;Using Fixtures&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;When using &lt;strong&gt;Django with fixtures&lt;/strong&gt;, the action should go in &lt;strong&gt;setUpTestData&lt;/strong&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TestLogin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;fixtures&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;test/users&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nd"&gt;@classmethod&lt;/span&gt;
&lt;span class="hll"&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;setUpTestData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;        &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setUpTestData&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/api/account/login&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Django loads fixtures at &lt;code&gt;setUpTestData&lt;/code&gt; so by calling super the action is executed after the fixtures were loaded.&lt;/p&gt;
&lt;p&gt;Another quick note about Django and requests. I've used the &lt;code&gt;requests&lt;/code&gt; package but Django, and the popular Django &lt;code&gt;restframework&lt;/code&gt;, provide their own clients. &lt;a href="https://docs.djangoproject.com/en/2.1/topics/testing/tools/#default-test-client" rel="noopener"&gt;&lt;code&gt;django.test.Client&lt;/code&gt;&lt;/a&gt; in Django's client, and &lt;a href="https://www.django-rest-framework.org/api-guide/testing/#apiclient" rel="noopener"&gt;&lt;code&gt;rest_framework.test.APIClient&lt;/code&gt;&lt;/a&gt; is DRF's client.&lt;/p&gt;
&lt;h4 id="testing-exceptions"&gt;&lt;a class="toclink" href="#testing-exceptions"&gt;Testing Exceptions&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;When a function raise an exception, we can extend the base class and wrap the action with &lt;code&gt;try ... catch&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TestLoginFailure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TestLogin&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="nd"&gt;@property&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;expected_exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="ne"&gt;NotImplementedError&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;setUp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setUp&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="ne"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="hll"&gt;            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exception&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_should_raise_expected_exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;assertIsInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exception&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;expected_exception&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;If you are familiar with the &lt;a href="https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertRaises" rel="noopener"&gt;&lt;code&gt;assertRaises&lt;/code&gt;&lt;/a&gt; context, I haven't used it in this case because the test should not fail during &lt;code&gt;setUp&lt;/code&gt;.&lt;/p&gt;
&lt;h4 id="create-mixins"&gt;&lt;a class="toclink" href="#create-mixins"&gt;Create Mixins&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Test cases are repetitive by nature. With mixins, we can abstract common parts of tests cases and compose new ones. For example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;TestAnonymousUserMixin&lt;/code&gt; - populates the test with anonymous API client.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;TestRemoteResponseMixin&lt;/code&gt; - mock response from remote service.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The later, might look something like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;unittest&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;mock&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TestRemoteServiceXResponseMixin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;mock_response_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;

    &lt;span class="nd"&gt;@classmethod&lt;/span&gt;
    &lt;span class="nd"&gt;@mock&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;patch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;path.to.function.making.remote.request&amp;#39;&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;setUpTestData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mock_remote&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;mock_remote&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;return_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;cls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mock_response_data&lt;/span&gt;
        &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setUpTestData&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;h3 id="conclusion"&gt;&lt;a class="toclink" href="#conclusion"&gt;Conclusion&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Someone once said that &lt;em&gt;duplication is cheaper than the wrong abstraction&lt;/em&gt;. I couldn't agree more. &lt;strong&gt;If your tests do not fit easily into a pattern then this solution is probably not the right one&lt;/strong&gt;. It's important to carefully decide what to abstract. The more you abstract, the more flexible your tests are. But, as parameters pile up in base classes, tests are becoming harder to write and maintain, and we go back to square one.&lt;/p&gt;
&lt;p&gt;Having said that, we found this technique to be useful in various situations and with different frameworks (such as Tornado and Django). Over time it has proven itself as being resilient to changes and easy to maintain. This is what we set out to achieve and we consider it a success!&lt;/p&gt;</content><category term="articles"></category><category term="Python"></category><category term="Django"></category><category term="Testing"></category></entry><entry><title>Things You Must Know About Django Admin As Your App Gets Bigger</title><link href="https://hakibenita.com/things-you-must-know-about-django-admin-as-your-app-gets-bigger" rel="alternate"></link><published>2016-08-05T00:00:00+03:00</published><updated>2016-08-05T00:00:00+03:00</updated><author><name>Haki Benita</name></author><id>tag:hakibenita.com,2016-08-05:/things-you-must-know-about-django-admin-as-your-app-gets-bigger</id><summary type="html">&lt;p&gt;The Django admin is a very powerful tool. We use it for day to day operations, browsing data and support. As we grew some of our projects from zero to 100K+ users we started experiencing some of Django's admin pain points - long response times and heavy load on the database.&lt;/p&gt;</summary><content type="html">&lt;hr&gt;
&lt;p&gt;The Django admin is a very powerful tool. We use it for day to day operations,browsing data and support. As we grew some of our projects from zero to 100K+users we started experiencing some of Django's admin pain points - long response times and heavy load on the database.&lt;/p&gt;
&lt;p&gt;In this short article I am going to share some simple techniques we use in our projects to make the Django admin behave as apps grow in size and complexity.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;We use Django 1.8, Python 3.4 and PostgreSQL 9.4. The code samples are for Python 3.4 but they can be easily modified to work on 2.7 and other Django versions.&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="before-we-start"&gt;&lt;a class="toclink" href="#before-we-start"&gt;Before We Start&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;These are the main components in a Django Admin list view:&lt;/p&gt;
&lt;figure&gt;&lt;img alt="Example admin list view including some of the components discussed in the article" src="https://hakibenita.com/images/01-things-you-must-know-about-django-admin-as-your-app-gets-bigger.png"&gt;&lt;figcaption&gt;Example admin list view including some of the components discussed in the article&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h4 id="logging"&gt;&lt;a class="toclink" href="#logging"&gt;Logging&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Most of Django's work is performing SQL queries so our main focus will be on
&lt;strong&gt;minimizing the amount of queries&lt;/strong&gt;. To keep track of query execution you can
use one of the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://django-debug-toolbar.readthedocs.io/en/stable/" rel="noopener"&gt;django-debug-toolbar&lt;/a&gt; - Very nice utility that adds a little panel on the side of the screen with a list of SQL queries executed and other useful metrics.&lt;/li&gt;
&lt;li&gt;If you don't like dependencies (like us) you can log SQL queries to the console by adding the following logger in settings.py:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="n"&gt;LOGGING&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;# ...&lt;/span&gt;
    &lt;span class="s1"&gt;&amp;#39;loggers&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="hll"&gt;        &lt;span class="s1"&gt;&amp;#39;django.db.backends&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;            &lt;span class="s1"&gt;&amp;#39;level&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;DEBUG&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;hr&gt;
&lt;h3 id="the-n1-problem"&gt;&lt;a class="toclink" href="#the-n1-problem"&gt;The N+1 Problem&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The N+1 problem is a well known problem in ORMs. To illustrate the problem let's say we have this schema:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Category&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&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;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&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;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;def__str__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&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;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&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;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;category&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ForeignKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Category&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;By implementing &lt;code&gt;__str__&lt;/code&gt; we tell Django that we want the name of the category to be used as the default description of the object. Whenever we print a category object, Django will fetch the name of the category.&lt;/p&gt;
&lt;p&gt;A simple admin page for our Product model might look like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nd"&gt;@admin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Product&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;ProductAdmin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ModelAdmin&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;list_display&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;category&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This seems innocent enough but the SQL log reveals the horror:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.000&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;SELECT&lt;span class="w"&gt; &lt;/span&gt;COUNT&lt;span class="o"&gt;(&lt;/span&gt;*&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;AS&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;__count&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;FROM&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;app_product&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;args&lt;/span&gt;&lt;span class="o"&gt;=()&lt;/span&gt;
&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.002&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;SELECT&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;app_product&amp;quot;&lt;/span&gt;.&lt;span class="s2"&gt;&amp;quot;id&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;app_product&amp;quot;&lt;/span&gt;.&lt;span class="s2"&gt;&amp;quot;name&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;app_product&amp;quot;&lt;/span&gt;.&lt;span class="s2"&gt;&amp;quot;category_id&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;FROM&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;app_product&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ORDER&lt;span class="w"&gt; &lt;/span&gt;BY&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;app_product&amp;quot;&lt;/span&gt;.&lt;span class="s2"&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;DESC&lt;span class="w"&gt; &lt;/span&gt;LIMIT&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;args&lt;/span&gt;&lt;span class="o"&gt;=()&lt;/span&gt;
&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.000&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;SELECT&lt;span class="w"&gt; &lt;/span&gt;...&lt;span class="w"&gt; &lt;/span&gt;FROM&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;app_category&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;where&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;app_category&amp;quot;&lt;/span&gt;.&lt;span class="s2"&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;args&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.000&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;SELECT&lt;span class="w"&gt; &lt;/span&gt;...&lt;span class="w"&gt; &lt;/span&gt;FROM&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;app_category&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;where&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;app_category&amp;quot;&lt;/span&gt;.&lt;span class="s2"&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;args&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.000&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;SELECT&lt;span class="w"&gt; &lt;/span&gt;...&lt;span class="w"&gt; &lt;/span&gt;FROM&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;app_category&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;where&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;app_category&amp;quot;&lt;/span&gt;.&lt;span class="s2"&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;args&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.000&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;SELECT&lt;span class="w"&gt; &lt;/span&gt;...&lt;span class="w"&gt; &lt;/span&gt;FROM&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;app_category&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;where&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;app_category&amp;quot;&lt;/span&gt;.&lt;span class="s2"&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;args&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;&lt;span class="m"&gt;4&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.000&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;SELECT&lt;span class="w"&gt; &lt;/span&gt;...&lt;span class="w"&gt; &lt;/span&gt;FROM&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;app_category&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;where&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;app_category&amp;quot;&lt;/span&gt;.&lt;span class="s2"&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;args&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
...
&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.000&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;SELECT&lt;span class="w"&gt; &lt;/span&gt;...&lt;span class="w"&gt; &lt;/span&gt;FROM&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;app_category&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;where&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;app_category&amp;quot;&lt;/span&gt;.&lt;span class="s2"&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;args&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.000&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;SELECT&lt;span class="w"&gt; &lt;/span&gt;...&lt;span class="w"&gt; &lt;/span&gt;FROM&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;app_category&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;where&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;app_category&amp;quot;&lt;/span&gt;.&lt;span class="s2"&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;99&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;args&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;&lt;span class="m"&gt;99&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;.000&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;SELECT&lt;span class="w"&gt; &lt;/span&gt;...&lt;span class="w"&gt; &lt;/span&gt;FROM&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;app_category&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;where&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;app_category&amp;quot;&lt;/span&gt;.&lt;span class="s2"&gt;&amp;quot;id&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;104&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;args&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;&lt;span class="m"&gt;104&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Django first counts the objects (more on that later), then fetches the actual objects (limiting to the default page size of 100) and then passes the data on to the template for rendering. We used the category name as the description of the &lt;code&gt;Category&lt;/code&gt; object, so for each product Django has to fetch the category name. This results in &lt;strong&gt;100 additional queries&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;To tell Django we want to perform a join instead of fetching the names of the categories one by one, we can use &lt;a href="https://docs.djangoproject.com/en/2.1/ref/contrib/admin/#django.contrib.admin.ModelAdmin.list_select_related" rel="noopener"&gt;&lt;code&gt;list_select_related&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nd"&gt;@admin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Product&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;ProductAdmin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ModelAdmin&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;list_display&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;category&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="hll"&gt;    &lt;span class="n"&gt;list_select_related&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;        &lt;span class="s1"&gt;&amp;#39;category&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Now, the SQL log looks much nicer. Instead of 101 queries we have only 1:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;(0.004) SELECT &amp;quot;app_product&amp;quot;.&amp;quot;id&amp;quot;, &amp;quot;app_product&amp;quot;.&amp;quot;name&amp;quot;,
&amp;quot;app_product&amp;quot;.&amp;quot;category_id&amp;quot;, &amp;quot;app_category&amp;quot;.&amp;quot;id&amp;quot;, &amp;quot;app_category&amp;quot;.&amp;quot;name&amp;quot;
FROM &amp;quot;app_product&amp;quot;
INNER JOIN &amp;quot;app_category&amp;quot; on (&amp;quot;app_product&amp;quot;.&amp;quot;category_id&amp;quot; = &amp;quot;app_category&amp;quot;.&amp;quot;id&amp;quot;)
ORDER BY &amp;quot;app_product&amp;quot;.&amp;quot;id&amp;quot; DESC LIMIT 100; args=()
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;To understand the real impact of this setting consider the following. Django default page size is 100 objects. If you have one related fields you have ~101 queries. If you have two related objects displayed in the list view, you have ~201 queries and so on.&lt;/p&gt;
&lt;p&gt;Fetching related fields in a join can only work for &lt;code&gt;ForeignKey&lt;/code&gt; relations. If you wish to display &lt;code&gt;ManyToMany&lt;/code&gt; relations it's a bit more complicated (and most of the time wrong, but keep reading).&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="related-fields"&gt;&lt;a class="toclink" href="#related-fields"&gt;Related Fields&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Sometimes it can be useful to quickly navigate between objects. After trying for a while to teach support personnel to filter using URL parameters, we finally gave up and created two simple decorators.&lt;/p&gt;
&lt;h4 id="admin_link"&gt;&lt;a class="toclink" href="#admin_link"&gt;&lt;code&gt;admin_link&lt;/code&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Create a link to a detail page of a related model:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;admin_change_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;app_label&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_meta&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;app_label&lt;/span&gt;
    &lt;span class="n"&gt;model_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_meta&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lower&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;reverse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;admin:&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s1"&gt;_&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s1"&gt;_change&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;app_label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model_name&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pk&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;admin_link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;short_description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;empty_description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;-&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Decorator used for rendering a link to a related model in&lt;/span&gt;
&lt;span class="sd"&gt;    the admin detail page.&lt;/span&gt;

&lt;span class="sd"&gt;    attr (str):&lt;/span&gt;
&lt;span class="sd"&gt;        Name of the related field.&lt;/span&gt;
&lt;span class="sd"&gt;    short_description (str):&lt;/span&gt;
&lt;span class="sd"&gt;        Name if the field.&lt;/span&gt;
&lt;span class="sd"&gt;    empty_description (str):&lt;/span&gt;
&lt;span class="sd"&gt;        Value to display if the related field is None.&lt;/span&gt;

&lt;span class="sd"&gt;    The wrapped method receives the related object and should&lt;/span&gt;
&lt;span class="sd"&gt;    return the link text.&lt;/span&gt;

&lt;span class="sd"&gt;    Usage:&lt;/span&gt;
&lt;span class="sd"&gt;        @admin_link(&amp;#39;credit_card&amp;#39;, _(&amp;#39;Credit Card&amp;#39;))&lt;/span&gt;
&lt;span class="sd"&gt;        def credit_card_link(self, credit_card):&lt;/span&gt;
&lt;span class="sd"&gt;            return credit_card.name&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;wrap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;func&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;field_func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;related_obj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;related_obj&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="kc"&gt;None&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;empty_description&lt;/span&gt;
            &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;admin_change_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;related_obj&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;format_html&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;lt;a href=&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s1"&gt;&amp;quot;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;/a&amp;gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;related_obj&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;field_func&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;short_description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;short_description&lt;/span&gt;
        &lt;span class="n"&gt;field_func&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;allow_tags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;field_func&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;wrap&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The decorator will render a link (&lt;code&gt;&amp;lt;a href="..."&amp;gt;...&amp;lt;/a&amp;gt;&lt;/code&gt;) to the related model in both the list view and the detail view. If for example, we want to add a link from each product to its category detail page, we use the decorator like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nd"&gt;@admin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Product&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;ProductAdmin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ModelAdmin&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;list_display&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;id&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;name&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;category_link&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;admin_select_related&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;category&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="hll"&gt;    &lt;span class="nd"&gt;@admin_link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;category&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Category&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;category_link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt;
&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;h4 id="admin_changelist_link"&gt;&lt;a class="toclink" href="#admin_changelist_link"&gt;&lt;code&gt;admin_changelist_link&lt;/code&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;More complicated links such as "all the products of a category" require a different implementation. We created a decorator that accepts a query string, and link to the list view of a related model:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;admin_changelist_url&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;app_label&lt;/span&gt; &lt;span class="o"&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;_meta&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;app_label&lt;/span&gt;
    &lt;span class="n"&gt;model_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lower&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;reverse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;admin:&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s1"&gt;_&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s1"&gt;_changelist&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app_label&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model_name&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;admin_changelist_link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;short_description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;empty_description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;-&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;query_string&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;
&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Decorator used for rendering a link to the list display of&lt;/span&gt;
&lt;span class="sd"&gt;    a related model in the admin detail page.&lt;/span&gt;

&lt;span class="sd"&gt;    attr (str):&lt;/span&gt;
&lt;span class="sd"&gt;        Name of the related field.&lt;/span&gt;
&lt;span class="sd"&gt;    short_description (str):&lt;/span&gt;
&lt;span class="sd"&gt;        Field display name.&lt;/span&gt;
&lt;span class="sd"&gt;    empty_description (str):&lt;/span&gt;
&lt;span class="sd"&gt;        Value to display if the related field is None.&lt;/span&gt;
&lt;span class="sd"&gt;    query_string (function):&lt;/span&gt;
&lt;span class="sd"&gt;        Optional callback for adding a query string to the link.&lt;/span&gt;
&lt;span class="sd"&gt;        Receives the object and should return a query string.&lt;/span&gt;

&lt;span class="sd"&gt;    The wrapped method receives the related object and&lt;/span&gt;
&lt;span class="sd"&gt;    should return the link text.&lt;/span&gt;

&lt;span class="sd"&gt;    Usage:&lt;/span&gt;

&lt;span class="sd"&gt;        @admin_changelist_link(&amp;#39;credit_card&amp;#39;, _(&amp;#39;Credit Card&amp;#39;))&lt;/span&gt;
&lt;span class="sd"&gt;        def credit_card_link(self, credit_card):&lt;/span&gt;
&lt;span class="sd"&gt;            return credit_card.name&lt;/span&gt;
&lt;span class="sd"&gt;    &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;wrap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;func&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;field_func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;related_obj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;getattr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;related_obj&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="kc"&gt;None&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;empty_description&lt;/span&gt;
            &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;admin_changelist_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;related_obj&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;query_string&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;?&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;query_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;obj&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;format_html&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;lt;a href=&amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s1"&gt;&amp;quot;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s1"&gt;&amp;lt;/a&amp;gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;related_obj&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;field_func&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;short_description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;short_description&lt;/span&gt;
        &lt;span class="n"&gt;field_func&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;allow_tags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;field_func&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;wrap&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;To add a link from a category to of its products, we do the following in &lt;code&gt;CategoryAdmin&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;@admin.register(models.Category)
class CategoryAdmin(admin.ModelAdmin):
    list_display = (
        &amp;#39;id&amp;#39;,
        &amp;#39;name&amp;#39;,
        &amp;#39;products_link&amp;#39;,
    )

    @admin_changelist_link(&amp;#39;products&amp;#39;, _(&amp;#39;Products&amp;#39;),
                            query_string=lambda c: &amp;#39;category_id={}&amp;#39;.format(c.pk))
    def products_link(self, products):
        return _(&amp;#39;Products&amp;#39;)
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Be careful with the products argument. It is very tempting to do something like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# Bad example&lt;/span&gt;

&lt;span class="nd"&gt;@admin_changelist_link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;products&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Products&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                        &lt;span class="n"&gt;query_string&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;category_id=&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pk&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;products_link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Dont do that!&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;see &lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s1"&gt; products&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;products&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;The example above will result in additional queries.&lt;/p&gt;
&lt;h4 id="readonly_fields"&gt;&lt;a class="toclink" href="#readonly_fields"&gt;&lt;code&gt;readonly_fields&lt;/code&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;In the detail page, Django creates an editable element for each field. Text and numeric fields will be rendered as regular input field. Choice fields and foreign key fields will be rendered as a &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt; element. To render a select box Django has to do the following:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Fetch the options - the entire related model and their descriptions (&lt;em&gt;remember the N+1 problem?&lt;/em&gt;).&lt;/li&gt;
&lt;li&gt;Render the option list - one option for each related model instance.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;A common scenario that is often overlooked, is foreign key to the &lt;code&gt;User&lt;/code&gt; model. When you have 100 users you might not notice the load, but what happens when you suddenly have 100K users? &lt;strong&gt;The detail page will fetch the entire users table, and the option list will make the resulting HTML huge&lt;/strong&gt;. We pay twice, first for the full table scan, and then for downloading the html file. Not to mention the memory required to generate the html file in the first place.&lt;/p&gt;
&lt;p&gt;Having a select element with 100K options is not really usable. The easiest way to prevent Django from rendering a field as a &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt; element is to mark it as &lt;a href="https://docs.djangoproject.com/en/2.1/ref/contrib/admin/#django.contrib.admin.ModelAdmin.readonly_fields" rel="noopener"&gt;readonly_fields&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nd"&gt;@admin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SomeModel&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;SomeModelAdmin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ModelAdmin&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;readonly_fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;user&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;This will render the description of the related model, without being able to change it in the admin.&lt;/p&gt;
&lt;p&gt;Another option to prevent Django from rendering a select box, is to mark the field as &lt;a href="https://docs.djangoproject.com/en/2.1/ref/contrib/admin/#django.contrib.admin.ModelAdmin.raw_id_fields" rel="noopener"&gt;&lt;code&gt;raw_id fields&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="nd"&gt;@admin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SomeModel&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;SomeModelAdmin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ModelAdmin&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;raw_id_fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s1"&gt;&amp;#39;user&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Using &lt;code&gt;raw_id_fields&lt;/code&gt;, Django will render a special widget that shows the id of the value, and an option to open a list of all values in a popup window. &lt;strong&gt;This option is very useful when you want to edit a foreign key value&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="filters"&gt;&lt;a class="toclink" href="#filters"&gt;Filters&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;We often use the admin interface as a day to day tool for general support. We found that most of the times we use the same filters: only active users, users registered in the last month, successful transactions ans so on. Once we realized that, we asked ourselves, why fetch the entire dataset if we are most likely to immediately apply a filter to it?. We started to look for a way to &lt;strong&gt;apply a default filter when entering the model list view&lt;/strong&gt;.&lt;/p&gt;
&lt;h4 id="defaultfiltermixin"&gt;&lt;a class="toclink" href="#defaultfiltermixin"&gt;&lt;code&gt;DefaultFilterMixin&lt;/code&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;There are many approaches to apply default filters. Some approaches involve custom filters or injecting special query parameters to the request. We wanted to avoid those.&lt;/p&gt;
&lt;p&gt;We found that the following approach to be simple and straightforward:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;urllib.parse&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;urlencode&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.shortcuts&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;redirect&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DefaultFilterMixin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_default_filters&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;Set default filters to the page.&lt;/span&gt;

&lt;span class="sd"&gt;        request (Request)&lt;/span&gt;

&lt;span class="sd"&gt;        Returns (dict):&lt;/span&gt;
&lt;span class="sd"&gt;            Default filter to encode.&lt;/span&gt;
&lt;span class="sd"&gt;        &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="ne"&gt;NotImplementedError&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;changelist_view&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;extra_context&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;ref&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;META&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;HTTP_REFERER&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;META&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;PATH_INFO&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# If already have query parameters or if the page&lt;/span&gt;
    &lt;span class="c1"&gt;# was referred from it self (by drilldown or redirect)&lt;/span&gt;
    &lt;span class="c1"&gt;# don&amp;#39;t apply default filter.&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GET&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;endswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;changelist_view&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;extra_context&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;extra_context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;urlencode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_default_filters&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s1"&gt;?&lt;/span&gt;&lt;span class="si"&gt;{}&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;If the list view was accessed from a different view, and no query params were specified, we generate a default query and redirect.&lt;/p&gt;
&lt;p&gt;Let's apply a default filter to our product page to show only products created in the last month:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.utils&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;timezone&lt;/span&gt;

&lt;span class="nd"&gt;@admin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Product&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;ProductAdmin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;DefaultFilterMixin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ModelAdmin&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;date_hierarchy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;created&amp;#39;&lt;/span&gt;

&lt;span class="hll"&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_default_filters&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;        &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;created__year&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s1"&gt;&amp;#39;created__month&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;month&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;If we drill down from within the page, or if we get to the page with query parameters, the default filter will not be applied.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id="quick-bits"&gt;&lt;a class="toclink" href="#quick-bits"&gt;Quick Bits&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Some neat tricks we gathered over time.&lt;/p&gt;
&lt;h4 id="show_full_result_count"&gt;&lt;a class="toclink" href="#show_full_result_count"&gt;&lt;code&gt;show_full_result_count&lt;/code&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Prevent Django from showing the total number of rows in the list view. Setting &lt;a href="(https://docs.djangoproject.com/ja/1.9/ref/contrib/admin/#django.contrib.admin.ModelAdmin.show_full_result_count)"&gt;&lt;code&gt;show_full_result_count=False&lt;/code&gt;&lt;/a&gt; saves a &lt;code&gt;count(*)&lt;/code&gt; query on the queryset on every page load.&lt;/p&gt;
&lt;h4 id="defer"&gt;&lt;a class="toclink" href="#defer"&gt;&lt;code&gt;defer&lt;/code&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;When performing a query the entire resultset is put into memory for processing. If you have large columns in your model such as JSON or Text fields, it might be a good idea to &lt;a href="(https://docs.djangoproject.com/en/1.9/ref/models/querysets/#django.db.models.query.QuerySet.defer)"&gt;defer&lt;/a&gt; them until you really need to use them. To defer fields override &lt;a href="https://docs.djangoproject.com/ja/1.9/ref/contrib/admin/#django.contrib.admin.ModelAdmin.get_queryset" rel="noopener"&gt;&lt;code&gt;get_queryset&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id="change-the-admin-default-url-route"&gt;&lt;a class="toclink" href="#change-the-admin-default-url-route"&gt;Change the Admin Default URL Route&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;This is definitely not the only precaution you should take to protect your admin page, but it can make it harder for "curious" users to reach the login page.&lt;/p&gt;
&lt;p&gt;In your main &lt;code&gt;urls.py&lt;/code&gt; override the default admin route:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# urls.py&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.conf.urls&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;include&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;django.contrib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;admin&lt;/span&gt;

&lt;span class="n"&gt;urlpatterns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;^foo/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;admin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;site&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;div class="admonition tip"&gt;
&lt;p class="admonition-title"&gt;see also&lt;/p&gt;
&lt;p&gt;I wrote a bunch of tips on &lt;a href="5-ways-to-make-django-admin-safer"&gt;how to make Django admin safer&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;h4 id="date_hierarchy"&gt;&lt;a class="toclink" href="#date_hierarchy"&gt;&lt;code&gt;date_hierarchy&lt;/code&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;We found that this index can be used to improve queries generate with &lt;a href="https://docs.djangoproject.com/ja/1.9/ref/contrib/admin/#django.contrib.admin.ModelAdmin.date_hierarchy" rel="noopener"&gt;date hierarchy&lt;/a&gt; predicate in PostgresSQL 9.4:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;INDEX&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;yourmodel_date_hierarchy_ix&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;yourmodel_table&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;extract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;day&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;created&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;at&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;time&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;zone&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;America/New_York&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;extract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;month&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;created&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;at&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;time&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;zone&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;America/New_York&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;extract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;year&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;created&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;at&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;time&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;zone&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;America/New_York&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;


&lt;p&gt;Make sure to change table name, index name, the date hierarchy column and the time zone.&lt;/p&gt;
&lt;div class="admonition tip"&gt;
&lt;p class="admonition-title"&gt;see also&lt;/p&gt;
&lt;p&gt;I wrote about &lt;a href="scaling-django-admin-date-hierarchy"&gt;scaling Django admin &lt;code&gt;date_hierarchy&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;hr&gt;
&lt;h3 id="conclusion"&gt;&lt;a class="toclink" href="#conclusion"&gt;Conclusion&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Even if you don't have 100K users and millions of records in the database, it is still important to keep the admin tidy. Bad code has this nasty tendency of biting you in the ass when you least expect it.&lt;/p&gt;</content><category term="articles"></category><category term="Django"></category><category term="Django Admin"></category></entry></feed>